Obtenga ReadyState del control WebBrowser sin DoEvents

Esto ha sido repetido muchas veces aquí y en otros sitios y su funcionamiento, pero me gustaría ideas para otras formas de:

obtener ReadyState = Complete después de usar una navegación o publicación, sin utilizar DoEvents debido a todos sus inconvenientes.

También me gustaría señalar que usar el evento DocumentComplete no ayudaría aquí ya que no navegaré en una sola página, sino una tras otra como esta.

wb.navigate("www.microsoft.com") //dont use DoEvents loop here wb.Document.Body.SetAttribute(textbox1, "login") //dont use DoEvents loop here if (wb.documenttext.contais("text")) //do something 

La forma en que es hoy en día funciona usando DoEvents. Me gustaría saber si alguien tiene una forma adecuada de esperar la llamada asincrónica de los métodos del navegador para luego continuar con el rest de la lógica. Solo por el bien de eso.

Gracias por adelantado.

A continuación se muestra un código básico de la aplicación WinForms, que ilustra cómo esperar el evento DocumentCompleted forma asíncrona, utilizando async/await . Navega a varias páginas, una tras otra. Todo está teniendo lugar en el hilo principal de UI.

En lugar de llamar this.webBrowser.Navigate(url) , podría estar simulando un clic de botón de formulario para activar una navegación POST.

La lógica de bucle asincrónico webBrowser.IsBusy es opcional, su propósito es contabilizar (de manera no determinista) el código AJAX dynamic de la página que puede tener lugar después del evento window.onload .

 using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WebBrowserApp { public partial class MainForm : Form { WebBrowser webBrowser; public MainForm() { InitializeComponent(); // create a WebBrowser this.webBrowser = new WebBrowser(); this.webBrowser.Dock = DockStyle.Fill; this.Controls.Add(this.webBrowser); this.Load += MainForm_Load; } // Form Load event handler async void MainForm_Load(object sender, EventArgs e) { // cancel the whole operation in 30 sec var cts = new CancellationTokenSource(30000); var urls = new String[] { "http://www.example.com", "http://www.gnu.org", "http://www.debian.org" }; await NavigateInLoopAsync(urls, cts.Token); } // navigate to each URL in a loop async Task NavigateInLoopAsync(string[] urls, CancellationToken ct) { foreach (var url in urls) { ct.ThrowIfCancellationRequested(); var html = await NavigateAsync(ct, () => this.webBrowser.Navigate(url)); Debug.Print("url: {0}, html: \n{1}", url, html); } } // asynchronous navigation async Task NavigateAsync(CancellationToken ct, Action startNavigation) { var onloadTcs = new TaskCompletionSource(); EventHandler onloadEventHandler = null; WebBrowserDocumentCompletedEventHandler documentCompletedHandler = delegate { // DocumentCompleted may be called several time for the same page, // if the page has frames if (onloadEventHandler != null) return; // so, observe DOM onload event to make sure the document is fully loaded onloadEventHandler = (s, e) => onloadTcs.TrySetResult(true); this.webBrowser.Document.Window.AttachEventHandler("onload", onloadEventHandler); }; this.webBrowser.DocumentCompleted += documentCompletedHandler; try { using (ct.Register(() => onloadTcs.TrySetCanceled(), useSynchronizationContext: true)) { startNavigation(); // wait for DOM onload event, throw if cancelled await onloadTcs.Task; } } finally { this.webBrowser.DocumentCompleted -= documentCompletedHandler; if (onloadEventHandler != null) this.webBrowser.Document.Window.DetachEventHandler("onload", onloadEventHandler); } // the page has fully loaded by now // optional: let the page run its dynamic AJAX code, // we might add another timeout for this loop do { await Task.Delay(500, ct); } while (this.webBrowser.IsBusy); // return the page's HTML content return this.webBrowser.Document.GetElementsByTagName("html")[0].OuterHtml; } } } 

Si está buscando hacer algo similar desde una aplicación de consola, aquí hay un ejemplo de eso .

La solución es simple:

  // MAKE SURE ReadyState = Complete while (WebBrowser1.ReadyState.ToString() != "Complete") { Application.DoEvents(); } 

// Pasa al código de tu secuencia secundaria …


Sucio y rápido … Soy un tipo de VBA, esta lógica ha funcionado para siempre, solo me llevó días y no encontré ninguno para C # pero me di cuenta de esto.

A continuación está mi función completa, el objective es obtener un segmento de información de una página web:

 private int maxReloadAttempt = 3; private int currentAttempt = 1; private string GetCarrier(string webAddress) { WebBrowser WebBrowser_4MobileCarrier = new WebBrowser(); string innerHtml; string strStartSearchFor = "subtitle block pull-left\">"; string strEndSearchFor = "<"; try { WebBrowser_4MobileCarrier.ScriptErrorsSuppressed = true; WebBrowser_4MobileCarrier.Navigate(webAddress); // MAKE SURE ReadyState = Complete while (WebBrowser_4MobileCarrier.ReadyState.ToString() != "Complete") { Application.DoEvents(); } // LOAD HTML innerHtml = WebBrowser_4MobileCarrier.Document.Body.InnerHtml; // ATTEMPT (x3) TO EXTRACT CARRIER STRING while (currentAttempt <= maxReloadAttempt) { if (innerHtml.IndexOf(strStartSearchFor) >= 0) { currentAttempt = 1; // Reset attempt counter return Sub_String(innerHtml, strStartSearchFor, strEndSearchFor, "0"); // Method: "Sub_String" is my custom function } else { currentAttempt += 1; // Increment attempt counter GetCarrier(webAddress); // Recursive method call } // End if } // End while } // End Try catch //(Exception ex) { } return "Unavailable"; } 

Aquí hay una solución “rápida y sucia”. No es 100% infalible pero no bloquea el hilo de UI y debería ser satisfactorio para prototipar el control WebBrowser. Procedimientos de automatización:

  private async void testButton_Click(object sender, EventArgs e) { await Task.Factory.StartNew( () => { stepTheWeb(() => wb.Navigate("www.yahoo.com")); stepTheWeb(() => wb.Navigate("www.microsoft.com")); stepTheWeb(() => wb.Navigate("asp.net")); stepTheWeb(() => wb.Document.InvokeScript("eval", new[] { "$('p').css('background-color','yellow')" })); bool testFlag = false; stepTheWeb(() => testFlag = wb.DocumentText.Contains("Get Started")); if (testFlag) { /* TODO */ } // ... } ); } private void stepTheWeb(Action task) { this.Invoke(new Action(task)); WebBrowserReadyState rs = WebBrowserReadyState.Interactive; while (rs != WebBrowserReadyState.Complete) { this.Invoke(new Action(() => rs = wb.ReadyState)); System.Threading.Thread.Sleep(300); } } 

Aquí hay una versión un poco más genérica del método testButton_Click :

  private async void testButton_Click(object sender, EventArgs e) { var actions = new List() { () => wb.Navigate("www.yahoo.com"), () => wb.Navigate("www.microsoft.com"), () => wb.Navigate("asp.net"), () => wb.Document.InvokeScript("eval", new[] { "$('p').css('background-color','yellow')" }), () => { bool testFlag = false; testFlag = wb.DocumentText.Contains("Get Started"); if (testFlag) { /* TODO */ } } //... }; await Task.Factory.StartNew(() => actions.ForEach((x)=> stepTheWeb (x))); } 

[Actualizar]

He adaptado mi muestra “rápida y sucia” tomando prestado y refactorizando ligeramente el método NavigateAsync de @Noseratio de este tema . La nueva versión de código automatizaría / ejecutaría de forma asíncrona en el contexto de la interfaz de usuario, no solo las operaciones de navegación, sino también las llamadas de Javascript / AJAX, cualquier método de implementación de tareas de paso de automatización o “lamdas”.

Todos y cada comentarios / revisiones de código son bienvenidos. Especialmente, desde @Noseratio . Juntos, haremos que este mundo sea mejor;)

  public enum ActionTypeEnumeration { Navigation = 1, Javascript = 2, UIThreadDependent = 3, UNDEFINED = 99 } public class ActionDescriptor { public Action Action { get; set; } public ActionTypeEnumeration ActionType { get; set; } } ///  /// Executes a set of WebBrowser control's Automation actions ///  ///  /// Test form shoudl ahve the following controls: /// webBrowser1 - WebBrowser, /// testbutton - Button, /// testCheckBox - CheckBox, /// totalHtmlLengthTextBox - TextBox ///  private async void testButton_Click(object sender, EventArgs e) { try { var cts = new CancellationTokenSource(60000); var actions = new List() { new ActionDescriptor() { Action = ()=> wb.Navigate("www.yahoo.com"), ActionType = ActionTypeEnumeration.Navigation} , new ActionDescriptor() { Action = () => wb.Navigate("www.microsoft.com"), ActionType = ActionTypeEnumeration.Navigation} , new ActionDescriptor() { Action = () => wb.Navigate("asp.net"), ActionType = ActionTypeEnumeration.Navigation} , new ActionDescriptor() { Action = () => wb.Document.InvokeScript("eval", new[] { "$('p').css('background-color','yellow')" }), ActionType = ActionTypeEnumeration.Javascript}, new ActionDescriptor() { Action = () => { testCheckBox.Checked = wb.DocumentText.Contains("Get Started"); }, ActionType = ActionTypeEnumeration.UIThreadDependent} //... }; foreach (var action in actions) { string html = await ExecuteWebBrowserAutomationAction(cts.Token, action.Action, action.ActionType); // count HTML web page stats - just for fun int totalLength = 0; Int32.TryParse(totalHtmlLengthTextBox.Text, out totalLength); totalLength += !string.IsNullOrWhiteSpace(html) ? html.Length : 0; totalHtmlLengthTextBox.Text = totalLength.ToString(); } } catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); } } // asynchronous WebBroswer control Automation async Task ExecuteWebBrowserAutomationAction( CancellationToken ct, Action runWebBrowserAutomationAction, ActionTypeEnumeration actionType = ActionTypeEnumeration.UNDEFINED) { var onloadTcs = new TaskCompletionSource(); EventHandler onloadEventHandler = null; WebBrowserDocumentCompletedEventHandler documentCompletedHandler = delegate { // DocumentCompleted may be called several times for the same page, // if the page has frames if (onloadEventHandler != null) return; // so, observe DOM onload event to make sure the document is fully loaded onloadEventHandler = (s, e) => onloadTcs.TrySetResult(true); this.wb.Document.Window.AttachEventHandler("onload", onloadEventHandler); }; this.wb.DocumentCompleted += documentCompletedHandler; try { using (ct.Register(() => onloadTcs.TrySetCanceled(), useSynchronizationContext: true)) { runWebBrowserAutomationAction(); if (actionType == ActionTypeEnumeration.Navigation) { // wait for DOM onload event, throw if cancelled await onloadTcs.Task; } } } finally { this.wb.DocumentCompleted -= documentCompletedHandler; if (onloadEventHandler != null) this.wb.Document.Window.DetachEventHandler("onload", onloadEventHandler); } // the page has fully loaded by now // optional: let the page run its dynamic AJAX code, // we might add another timeout for this loop do { await Task.Delay(500, ct); } while (this.wb.IsBusy); // return the page's HTML content return this.wb.Document.GetElementsByTagName("html")[0].OuterHtml; }