3

我需要捕获生成的 HTML 的图像。我在这里使用 Alex Filipovici 的出色解决方案:Convert HTML string to image。它工作得很好,除非我尝试加载具有使用某些 Javascript 加载的 iframe 的页面。

        静态 int 宽度 = 1024;
        静态 int 高度 = 768;

        公共静态无效捕获()
        {
            变量 html = @"
<!DOCTYPE html>
<meta http-equiv='X-UA-Compatible' 内容='IE=Edge'>
<html>
<iframe id='forecast_embed' type='text/html' frameborder='0' height='245' width='100%' src='http://forecast.io/embed/#lat=42.3583&lon=- 71.0603&name=波士顿市中心'> </iframe>
</html>
";
            开始浏览器(html);
        }

        私有静态无效StartBrowser(字符串源)
        {
            var th = 新线程(() =>
            {
                var webBrowser = new WebBrowser();
                webBrowser.Width = 宽度;
                webBrowser.Height = 高度;
                webBrowser.ScrollBarsEnabled = false;
                webBrowser.DocumentCompleted += webBrowser_DocumentCompleted;
                webBrowser.DocumentText = 源;
                应用程序.运行();
            });
            th.SetApartmentState(ApartmentState.STA);
            th.Start();
        }

        静态无效 webBrowser_DocumentCompleted(对象发送者,WebBrowserDocumentCompletedEventArgs e)
        {
            var webBrowser = (WebBrowser)sender;
            使用(位图位图 = 新位图(宽度,高度))
            {
                webBrowser.DrawToBitmap(bitmap, new System.Drawing.Rectangle(0, 0, width, height));
                bitmap.Save(@"image.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
            }
            应用程序.Exit();
        }

我知道可能没有明确的方法可以知道所有 javascript 是否已经结束以及 iframe 加载的变幻莫测以及 DocumentCompleted get 被调用的次数与 frame/iframe + 1 一样多。我可以用计数器处理 iframe 加载什么的,但我想要的只是一个合理的延迟,所以加载了javascript,我没有得到一个像这样的“正在加载”的图像:http: //imgur.com/FiFMTmm

4

2 回答 2

3

如果您正在处理大量使用框架和 AJAX 的动态网页,那么没有完美的解决方案可以找到特定页面何时完成加载资源。您可以通过执行以下两件事来接近:

  • 处理页面的window.onload事件;
  • 然后异步轮询WebBrowser Busy属性,并带有一些预定义的合理短超时。

例如,(查看https://stackoverflow.com/a/19283143/1768303以获取完整示例):

const int AJAX_DELAY = 2000; // non-deterministic wait for AJAX dynamic code
const int AJAX_DELAY_STEP = 500;

// wait until webBrowser.Busy == false or timed out
async Task<bool> AjaxDelay(CancellationToken ct, int timeout)
{
    using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
    {
        cts.CancelAfter(timeout);
        while (true)
        {
            try
            {
                await Task.Delay(AJAX_DELAY_STEP, cts.Token);
                var busy = (bool)this.webBrowser.ActiveXInstance.GetType().InvokeMember("Busy", System.Reflection.BindingFlags.GetProperty, null, this.webBrowser.ActiveXInstance, new object[] { });
                if (!busy)
                    return true;
            }
            catch (OperationCanceledException)
            {
                if (cts.IsCancellationRequested && !ct.IsCancellationRequested)
                    return false;
                throw;
            }
        }
    }
}

如果您不想使用async/await,您可以使用计时器来实现相同的逻辑。

于 2013-10-15T07:39:21.563 回答
0

这是我在与各种其他想法混在一起后一直在使用的东西,这些想法最终变得复杂并且有竞争条件或需要.Net 4.5(例如this question的答案)。

诀窍是在每个 DocumentCompleted 上重新启动 Stopwatch 并等待直到在某个阈值内没有完成任何文档。

为了更容易使用,我加入了一个扩展方法:

browser.NavigateAndWaitUntilComplete(uri);

我应该称它为 NavigateUntilProbablyComplete()。这种方法的缺点是每次导航都会有 250 毫秒的延迟。我见过的许多解决方案都依赖于最终页面与我的场景中不能保证的 url 相同。

using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

namespace MyProject.Extensions
{
    public static class WebBrowserExtensions
    {
        const int CompletionDelay = 250;

        private class WebBrowserCompletionHelper
        {
            public Stopwatch LastCompletion;

            public WebBrowserCompletionHelper()
            {
                // create but don't start.
                LastCompletion = new Stopwatch();
            }

            public void DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
            {
                WebBrowser browser = sender as WebBrowser;
                if (browser != null)
                {
                    LastCompletion.Restart();
                }
            }
        }

        public static void NavigateAndWaitUntilComplete(this WebBrowser browser, Uri uri)
        {
            WebBrowserCompletionHelper helper = new WebBrowserCompletionHelper();
            try
            {
                browser.DocumentCompleted += helper.DocumentCompleted;
                browser.Navigate(uri);

                Thread.Sleep(CompletionDelay);
                Application.DoEvents();

                while (browser.ReadyState != WebBrowserReadyState.Complete && helper.LastCompletion.ElapsedMilliseconds < CompletionDelay)
                {
                    Thread.Sleep(CompletionDelay);
                    Application.DoEvents();
                }
            }
            finally
            {
                browser.DocumentCompleted -= helper.DocumentCompleted;
            }
        }
    }
}
于 2014-02-06T06:01:56.480 回答