在iframe和Javascript完成后完成WebBrowser控制文档

我需要捕获生成的HTML的图像。 我正在使用Alex Filipovici的优秀解决方案: 将HTML字符串转换为图像 。 除非我正在尝试加载具有使用某些Javascript加载的iframe的页面,否则它的效果很好。

         static int width = 1024;
         static int height = 768;

         public static void Capture()
         {
             var html = @“
 
 
 
  
 
 “;
             StartBrowser(HTML);
         }

         private static void StartBrowser(string source)
         {
             var th = new Thread(()=>
             {
                 var webBrowser = new WebBrowser();
                 webBrowser.Width = width;
                 webBrowser.Height = height;
                 webBrowser.ScrollBarsEnabled = false;
                 webBrowser.DocumentCompleted + = webBrowser_DocumentCompleted;
                 webBrowser.DocumentText = source;
                 Application.Run();
             });
             th.SetApartmentState(ApartmentState.STA);
             th.Start();
         }

         static void webBrowser_DocumentCompleted(object sender,WebBrowserDocumentCompletedEventArgs e)
         {
             var webBrowser =(WebBrowser)sender;
            使用(位图位图=新位图(宽度,高度))
             {
                 webBrowser.DrawToBitmap(位图,新的System.Drawing.Rectangle(0,0,width,height));
                 bitmap.Save(@“image.jpg”,System.Drawing.Imaging.ImageFormat.Jpeg);
             }
             Application.Exit();
         }

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

如果您正在处理使用框架和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 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 ,则可以使用计时器实现相同的逻辑。

这是我在经历了很多其他想法之后一直在使用的东西,这些想法最终变得复杂并且有竞争条件或者需要.Net 4.5(例如这个问题的答案)。

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

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

 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; } } } }