Как использовать элемент управления WebBrowser Событие DocumentCompleted в С#?

Прежде чем начинать писать этот вопрос, я пытался решить следующие

// 1. navigate to page
// 2. wait until page is downloaded
// 3. read and write some data from/to iframe 
// 4. submit (post) form

Проблема заключалась в том, что если iframe существует на веб-странице, событие DocumentCompleted будет запускаться более одного раза (после завершения каждого документа). Очень вероятно, что программа попыталась бы прочитать данные из DOM, которые не были завершены и, естественно, потерпели неудачу.

Но неожиданно при написании этого вопроса "Что, если" монстр вдохновил меня, и я исправил проблему, которую я пытался решить. Поскольку я не смог выполнить Google, я подумал, что было бы неплохо опубликовать его здесь.

    private int iframe_counter = 1; // needs to be 1, to pass DCF test
    public bool isLazyMan = default(bool);

    /// <summary>
    /// LOCK to stop inspecting DOM before DCF
    /// </summary>
    public void waitPolice() {
        while (isLazyMan) Application.DoEvents();
    }

    private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e) {
        if(!e.TargetFrameName.Equals(""))
            iframe_counter --;
        isLazyMan = true;
    }

    private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
        if (!((WebBrowser)sender).Document.Url.Equals(e.Url))
            iframe_counter++;
        if (((WebBrowser)sender).Document.Window.Frames.Count <= iframe_counter) {//DCF test
            DocumentCompletedFully((WebBrowser)sender,e);
            isLazyMan = false; 
        }
    }

    private void DocumentCompletedFully(WebBrowser sender, WebBrowserDocumentCompletedEventArgs e){
        //code here
    }

На данный момент, по крайней мере, мой 5-метровый хак, похоже, работает нормально.

Возможно, я действительно терпеть неудачу при запросе Google или MSDN, но я не могу найти: "Как использовать управление веб-браузером Событие DocumentCompleted в С#?"

Примечание: Узнав много о webcontrol, я обнаружил, что это материал FuNKY.

Даже если вы обнаружите, что документ завершен, в большинстве случаев он не останется таким навсегда. Обновление страницы может быть выполнено несколькими способами: обновление фреймов, ajax, например, запрос или передача на стороне сервера (вам нужно иметь некоторый элемент управления, который поддерживает асинхронную связь и имеет html или JavaScript-интерфейс). Кроме того, некоторые фреймы никогда не будут загружаться, поэтому не стоит долго ждать их.

В итоге я использовал:

if (e.Url != wb.Url)

Ответы

Ответ 1

Возможно, вам понадобятся также вызовы AJAX.

Подумайте об этом:

private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    string url = e.Url.ToString();
    if (!(url.StartsWith("http://") || url.StartsWith("https://")))
    {
            // in AJAX
    }

    if (e.Url.AbsolutePath != this.webBrowser.Url.AbsolutePath)
    {
            // IFRAME 
    }
    else
    {
            // REAL DOCUMENT COMPLETE
    }
}

Ответ 2

Мне еще предстоит найти рабочее решение для этой проблемы в Интернете. Надеюсь, это сделает его на первое место и спасет всех в течение нескольких месяцев, которые я потратил, пытаясь его решить, и связанных с ним крайностей. За последние годы я боролся за эту проблему, так как Microsoft изменила реализацию/надежность isBusy и document.readystate. С IE8 мне пришлось прибегнуть к следующему решению. Это похоже на вопрос/ответ от Маргуса за некоторыми исключениями. Мой код будет обрабатывать вложенные фреймы, запросы javascript/ajax и мета-перенаправления. Я упростил код для ясности, но я также использую функцию тайм-аута (не входит) в reset веб-страницу, если 5 минут domAccess все равно равен false.

private void m_WebBrowser_BeforeNavigate(object pDisp, ref object URL, ref object Flags, ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel)
{
    //Javascript Events Trigger a Before Navigate Twice, but the first event 
    //will contain javascript: in the URL so we can ignore it.
    if (!URL.ToString().ToUpper().StartsWith("JAVASCRIPT:"))
    {
        //indicate the dom is not available
        this.domAccess = false;
        this.activeRequests.Add(URL);
    }
}

private void m_WebBrowser_DocumentComplete(object pDisp, ref object URL) 
{

    this.activeRequests.RemoveAt(0);

    //if pDisp Matches the main activex instance then we are done.
    if (pDisp.Equals((SHDocVw.WebBrowser)m_WebBrowser.ActiveXInstance)) 
    {
        //Top Window has finished rendering 
        //Since it will always render last, clear the active requests.
        //This solves Meta Redirects causing out of sync request counts
        this.activeRequests.Clear();
    }
    else if (m_WebBrowser.Document != null)
    {
        //Some iframe completed dom render
    }

    //Record the final complete URL for reference
    if (this.activeRequests.Count == 0)
    {
        //Finished downloading page - dom access ready
        this.domAccess = true;
    }
}

Ответ 3

В отличие от Thorsten мне не нужно было использовать ShDocVw, но для меня было важно добавить цикл проверки ReadyState и использовать Application.DoEvents(), пока он не готов. Вот мой код:

        this.webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(WebBrowser_DocumentCompleted);
        foreach (var item in this.urlList) // This is a Dictionary<string, string>
        {
            this.webBrowser.Navigate(item.Value);
            while (this.webBrowser1.ReadyState != WebBrowserReadyState.Complete)
            {
                Application.DoEvents();
            }
        }

И я использовал решение Yuki для проверки результатов WebBrowser_DocumentCompleted, хотя с последней заменой if/else на комментарий пользователя:

     private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        string url = e.Url.ToString();
        var browser = (WebBrowser)sender;

        if (!(url.StartsWith("http://") || url.StartsWith("https://")))     
        {             
            // in AJAX     
        }
        if (e.Url.AbsolutePath != this.webBrowser.Url.AbsolutePath)     
        {
            // IFRAME           
        }     
        else     
        {             
            // REAL DOCUMENT COMPLETE
            // Put my code here
        }
    }

Работал как шарм:)

Ответ 4

Мне нужно было сделать что-то подобное. То, что я делаю, это использовать ShDocVw напрямую (добавив ссылку на все необходимые сборки interop для моего проекта). Затем я не добавляю элемент управления WebBrowser в свою форму, но элемент управления AXShDocVw.AxWebBrowser.

Для навигации и ожидания я использую следующий метод:

private void GotoUrlAndWait(AxWebBrowser wb, string url)
{
    object dummy = null;
    wb.Navigate(url, ref dummy, ref dummy, ref dummy, ref dummy);

    // Wait for the control the be initialized and ready.
    while (wb.ReadyState != SHDocVw.tagREADYSTATE.READYSTATE_COMPLETE)
        Application.DoEvents();
}

Ответ 5

Просто подумал о том, чтобы бросить пару или пару здесь, о небольшом улучшении, которое работает в сочетании с кодом FeiBao. Идея заключается в том, чтобы ввести ориентировочную (javascript) переменную на веб-странице и использовать ее для определения того, какой из последующих событий DocumentComplete является реальной сделкой. Я сомневаюсь, что это пуленепробиваемый, но он работал более надежно в целом, чем тот подход, в котором его нет. Любые комментарии приветствуются. Вот шаблонный код:

 void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        string url = e.Url.ToString();
        var browser = (WebBrowser)sender;

        if (!(url.StartsWith("http://") || url.StartsWith("https://")))
        {
            // in AJAX     
        }
        if (e.Url.AbsolutePath != this.webBrowser.Url.AbsolutePath)
        {
            // IFRAME           
        }
        else if (browser.Document != null && (bool)browser.Document.InvokeScript("eval", new object[] { @"typeof window.YourLandMarkJavascriptVariableHere === 'undefined'" }))
        {
            ((IHTMLWindow2)browser.Document.Window.DomWindow).execScript("var window.YourLandMarkJavascriptVariableHere = true;");

            // REAL DOCUMENT COMPLETE
            // Put my code here
        }
    }