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