得到

2014-01-15 42 views
2

這已經在這裏和在其他網站和其工作awnsered很多次都沒有的DoEvents WebBrowser控件readyState的,但我想的想法其他方式:使用導航或交後得到

得到的readyState =完成,因爲它的所有缺點而不使用DoEvents。

我還會注意到,使用DocumentComplete事件不會在這裏幫助,因爲我不會只在一個頁面上導航,而是像這樣一個接一個地導航。

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 

它是今天通過使用DoEvents工作的方式。我想知道是否有人有一個正確的方法來等待瀏覽器方法的異步調用,然後繼續執行其餘的邏輯。只是爲了它。

在此先感謝。

+1

你** **必須使用DocumentCompleted事件。所有你需要做的就是跟蹤*完成了什麼*。該事件已經告訴你,你會得到e.Url屬性。如果您需要了解更多信息,請使用跟蹤狀態的變量。一個簡單的整數或枚舉將會很好。 –

回答

0

這是一個「快速&髒」的解決方案。這不是100%萬無一失,但它不會阻塞UI線程,它應該是令人滿意的原型WebBrowser控件自動化程序:

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

這裏是一個比較testButton_Click方法的通用版本:

private async void testButton_Click(object sender, EventArgs e) 
    { 
     var actions = new List<Action>() 
      { 
       () => 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))); 
    } 

[更新]

我已經適應了我的「快速&髒」的借用和sligthly重構@Noseratio's NavigateAsync method from this topic樣品。 新的代碼版本將在UI線程上下文中自動執行/執行,不僅僅是導航操作,還包括Javascript/AJAX調用 - 任何「lamdas」/一個自動化步驟任務實現方法。

所有代碼評論/評論都非常受歡迎。特別是從@Noseratio。我們要共同努力,讓這個世界變得更美好;)

public enum ActionTypeEnumeration 
    { 
     Navigation = 1, 
     Javascript = 2, 
     UIThreadDependent = 3, 
     UNDEFINED = 99 
    } 

    public class ActionDescriptor 
    { 
     public Action Action { get; set; } 
     public ActionTypeEnumeration ActionType { get; set; } 
    } 

    /// <summary> 
    /// Executes a set of WebBrowser control's Automation actions 
    /// </summary> 
    /// <remarks> 
    /// Test form shoudl ahve the following controls: 
    /// webBrowser1 - WebBrowser, 
    /// testbutton - Button, 
    /// testCheckBox - CheckBox, 
    /// totalHtmlLengthTextBox - TextBox 
    /// </remarks> 
    private async void testButton_Click(object sender, EventArgs e) 
    { 
     try 
     { 
      var cts = new CancellationTokenSource(60000); 

      var actions = new List<ActionDescriptor>() 
      { 
       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<string> ExecuteWebBrowserAutomationAction(
          CancellationToken ct, 
          Action runWebBrowserAutomationAction, 
          ActionTypeEnumeration actionType = ActionTypeEnumeration.UNDEFINED) 
    { 
     var onloadTcs = new TaskCompletionSource<bool>(); 
     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; 
    } 
+1

沒有冒犯,但這是一個糟糕的設計。它僅使用後臺線程通過Control.Invoke操作UI線程上的WebBrowser對象。 **這個任務不需要額外的線程。**和'Thread.Sleep(300)'循環......這裏有'DocumentCompleted'事件。 – Noseratio

+0

@Noseratio,謝謝,我知道:)所以我已經注意到它是**原型** WebBrowser控件的自動化程序**的「快速&髒」**解決方案。我確實使用了'DocumentCompleted'來進行真實生活中的項目。顯然'var actions ...''「控制結構」可以被推廣,我的'private void stepWeb(Action task)'可以被重構爲使用'DocumentCompleted'和其他'技巧'來處理不僅'WebBrowser'控制導航,而且Javascript/AJAX操作/調用。至於*額外的線程* - 這個「快速和骯髒」的解決方案將掛起UI線程沒有*額外的線程*,不是嗎? – ShamilS

+0

@Noseratio,我剛剛在這裏發佈了一個新的代碼版本,通過借用和調整你的代碼示例的一部分,這個主題是否可以? – ShamilS

2

下面是一個基本的WinForms應用程序代碼,說明如何等待DocumentCompleted事件異步,使用async/await。它一個接一個地導航到多個頁面。一切都發生在主UI線程上。

而不是調用​​,它可能是模擬一個表單按鈕點擊,以觸發POST風格的導航。

webBrowser.IsBusy異步循環邏輯是可選的,其目的是爲頁面的動態AJAX代碼(可能發生在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<string> NavigateAsync(CancellationToken ct, Action startNavigation) 
     { 
      var onloadTcs = new TaskCompletionSource<bool>(); 
      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; 
     } 
    } 
} 

如果你正在尋找做從控制檯應用程序類似的東西,這裏是an example of that

1

解決方法很簡單:

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

//移動到您的子序列碼...


骯髒的和快速的。我是一個VBA的傢伙,這種邏輯一直致力於永遠,只是把我天,沒有發現對C#但我想通了這一點自己。

以下是我的完整功能,其目標是從網頁獲取信息的一個片段:

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