首页 > 解决方案 > System.Windows.Forms.WebBrowser 等到页面完全加载

问题描述

我一直在尝试使用wait和的许多不同解决方案async。似乎没有任何效果。我无法找到实际上完全等待页面完全加载的解决方案。所有代码都在等待一段时间,但直到页面加载完毕并且我在下一个过程中遇到错误。

如何将示例代码设置为等待模式,直到Document.GetElementById("quickFind_text_0")在页面上找到元素?

这是我的代码:

    private void button7_Click(object sender, EventArgs e)
    {

        webBrowser1.Navigate("https://company.crm4.dynamics.com/main.aspx?app=d365default&pagetype=entitylist&etn=opportunity");

        webBrowser1.Document.GetElementById("shell-container").Document.GetElementById("quickFind_text_0").SetAttribute("value", "Airbus");

        webBrowser1.Document.GetElementById("shell-container").Document.GetElementById("quickFind_text_0").InnerText = "Airbus";

        //Thread.Sleep(2000);

        HtmlElement fbLink = webBrowser1.Document.GetElementById("shell-container").Document.GetElementById("mainContent").Document.GetElementById("quickFind_button_0"); ;
        fbLink.InvokeMember("click");
    }

PS我必须这样做“两次”否则它不起作用:

    webBrowser1.Document.GetElementById("shell-container").Document.GetElementById("quickFind_text_0").SetAttribute("value", "Airbus");

    webBrowser1.Document.GetElementById("shell-container").Document.GetElementById("quickFind_text_0").InnerText = "Airbus";

在 VBA 中这有效:

    While .Busy
        DoEvents

    Wend
    While .ReadyState <> 4
        DoEvents
    Wend

是否可以在 C# 中做同样的事情?


编辑:

我的完整代码如下。由于某种原因异步/等待不起作用。

System.NullReferenceException HResult=0x80004003 Message=对象引用未设置为对象的实例。Source=v.0.0.01
StackTrace:位于 C:\Users\PC\source\repos\v.0.0.01\v.0.0 中的 v._0._0._01.Browser.<button7_Click>d__7.MoveNext()。 01\Browser.cs:第 69 行

这是我的代码:

using System;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace v.0._0._01
{

    public static class WebBrowserExtensions
    {
        public static Task<Uri> DocumentCompletedAsync(this WebBrowser wb)
        {
            var tcs = new TaskCompletionSource<Uri>();
            WebBrowserDocumentCompletedEventHandler handler = null;
            handler = (_, e) =>
            {
                wb.DocumentCompleted -= handler;
                tcs.TrySetResult(e.Url);
            };
            wb.DocumentCompleted += handler;
            return tcs.Task;
        }
    }

    public partial class Browser : Form
    {

        public Browser()
        {
            InitializeComponent();
        }

        private async void button7_Click(object sender, EventArgs e)
        {

            webBrowser1.Navigate("https://company.crm4.dynamics.com/main.aspx?app=d365default&pagetype=entitylist&etn=opportunity");
            await webBrowser1.DocumentCompletedAsync(); // async magic
            HtmlElement fbLink = webBrowser1.Document.GetElementById("shell-container").Document.GetElementById("mainContent").Document.GetElementById("quickFind_button_0"); ;
            fbLink.InvokeMember("click");

        }

    }

}

现在我也注意到了,quickFind_text_0并且quickFind_button_0总是以相同的单词开头,但数字正在变化,例如quickFind_text_1andquickFind_button_1quickFind_text_2and quickFind_button_2。但是,通过手动单击,一切都适用于quickFind_text_0quickFind_button_0

标签: c#web-scrapingdynamics-crmwebbrowser-control

解决方案


这是一个易于等待DocumentCompleted事件的扩展方法:

public static class WebBrowserExtensions
{
    public static Task<Uri> DocumentCompletedAsync(this WebBrowser wb)
    {
        var tcs = new TaskCompletionSource<Uri>();
        WebBrowserDocumentCompletedEventHandler handler = null;
        handler = (_, e) =>
        {
            wb.DocumentCompleted -= handler;
            tcs.TrySetResult(e.Url);
        };
        wb.DocumentCompleted += handler;
        return tcs.Task;
    }
}

它可以这样使用:

private async void button1_Click(object sender, EventArgs e)
{

    webBrowser1.Navigate("https://company.crm4.dynamics.com/main.aspx");
    await webBrowser1.DocumentCompletedAsync(); // async magic
    HtmlElement fbLink = webBrowser1.Document.GetElementById("quickFind_button_0");
    fbLink.InvokeMember("click");
}

之后的await行将在页面完成加载后运行。


更新:这是等待特定元素出现在页面中的另一种扩展方法:

public static async Task<HtmlElement> WaitForElementAsync(this WebBrowser wb,
    string elementId, int timeout = 30000, int interval = 500)
{
    var stopwatch = Stopwatch.StartNew();
    while (true)
    {
        try
        {
            var element = wb.Document.GetElementById(elementId);
            if (element != null) return element;
        }
        catch { }
        if (stopwatch.ElapsedMilliseconds > timeout) throw new TimeoutException();
        await Task.Delay(interval);
    }
}

例如,可以在调用使用 XMLHttpRequest 修改页面的单击事件后使用它:

someButton.InvokeMember("click");
var mainContentElement = await webBrowser1.WaitForElementAsync("mainContent", 5000);

推荐阅读