是否可以确定WebBrowser是否正在导航?

时间:2010-11-08 19:44:22

标签: c# .net browser webbrowser-control

我正在尝试为我的程序找到一种方法来了解WebBrowser何时导航以及何时不导航。这是因为程序将通过将在文档中注入的JavaScript与加载的文档进行交互。我没有任何其他方式知道何时开始导航而不是处理Navigating事件,因为不是我的程序,而是通过与文档交互导航的用户。但是,当DocumentCompleted发生时并不一定意味着它已完成导航。我一直在谷歌上搜索,发现了两个伪解决方案:

  1. DocumentCompleted事件中检查WebBrowser的ReadyState属性。这样做的问题是,如果不是文档而是加载文档中的框架,即使主文档没有完成,ReadyState也将是Completed

  2. 为防止出现这种情况,他们建议您查看传递给DocumentCompleted的{​​{3}}参数是否与WebBrowser的{​​{3}}相匹配。这样我就知道文档中的其他框架没有调用DocumentCompleted

  3. 2的问题在于,正如我所说,我必须知道页面导航的唯一方法是处理Navigating(或Navigated)事件。例如,如果我在Google地图中并点击搜索,则会调用Navigating,但只是一个框架正在导航;不是整个页面(在特定的Google案例中,我可以使用TargetFrameName的{​​{1}}属性来检查它是否是正在导航的帧,但帧并不总是有名称。那么在此之后,WebBrowserNavigatingEventArgs将会被调用,但不会与DocumentCompleted的{​​{1}}属性相同Url,因为它只是一个导航的帧,所以我的程序将会永远导航它。

    添加对WebBrowser的通话并减去对Url的通话也不会有效。它们并不总是一样的。我已经好几个月没有找到这个问题的解决方案;我一直在使用解决方案1和2,并希望它们适用于大多数情况。我的计划是使用计时器,以防某些网页出现错误或其他问题,但我认为Google地图没有任何错误。我仍然可以使用它,但唯一更丑陋的解决方案是烧毁我的电脑。

    编辑:到目前为止,这是我最接近解决方案的地方:

    Navigating

    DocumentCompleted继承partial class SafeWebBrowser { private class SafeNavigationManager : INotifyPropertyChanged { private SafeWebBrowser Parent; private bool _IsSafeNavigating = false; private int AccumulatedNavigations = 0; private bool NavigatingCalled = false; public event PropertyChangedEventHandler PropertyChanged; public bool IsSafeNavigating { get { return _IsSafeNavigating; } private set { SetIsSafeNavigating(value); } } public SafeNavigationManager(SafeWebBrowser parent) { Parent = parent; } private void SetIsSafeNavigating(bool value) { if (_IsSafeNavigating != value) { _IsSafeNavigating = value; OnPropertyChanged(new PropertyChangedEventArgs("IsSafeNavigating")); } } private void UpdateIsSafeNavigating() { IsSafeNavigating = (AccumulatedNavigations != 0) || (NavigatingCalled == true); } private bool IsMainFrameCompleted(WebBrowserDocumentCompletedEventArgs e) { return Parent.ReadyState == WebBrowserReadyState.Complete && e.Url == Parent.Url; } protected void OnPropertyChanged(PropertyChangedEventArgs e) { if (PropertyChanged != null) PropertyChanged(this, e); } public void OnNavigating(WebBrowserNavigatingEventArgs e) { if (!e.Cancel) NavigatingCalled = true; UpdateIsSafeNavigating(); } public void OnNavigated(WebBrowserNavigatedEventArgs e) { NavigatingCalled = false; AccumulatedNavigations++; UpdateIsSafeNavigating(); } public void OnDocumentCompleted(WebBrowserDocumentCompletedEventArgs e) { NavigatingCalled = false; AccumulatedNavigations--; if (AccumulatedNavigations < 0) AccumulatedNavigations = 0; if (IsMainFrameCompleted(e)) AccumulatedNavigations = 0; UpdateIsSafeNavigating(); } } } 。方法SafeWebBrowserWebBrowserOnNavigating在相应的OnNavigated重写方法上调用。属性OnDocumentCompleted会让我知道它是否正在导航。

3 个答案:

答案 0 :(得分:4)

等到文档加载是一个难题,但是你想要不断检查.ReadyState和.Busy(不要忘记)。我会给你一些你需要的一般信息,然后我会在最后回答你的具体问题。

BTW,NC = NavigateComplete和DC = DocumentComplete。

此外,如果您正在等待的页面有帧,您需要获取它们并检查它们的.busy和.readystate,如果帧是嵌套的,则嵌套帧.readystate和.busy为好吧,所以你需要编写一个递归地检索那些引用的函数。

现在,无论它有多少帧,第一次触发的NC事件始终是顶级文档,最后一次触发的DC事件也始终是顶级(父级)文档。

所以你应该检查它是第一次调用还是pDisp Is WebBrowser1.object(字面意思是你在if语句中键入的内容)然后你知道它的顶级文档的NC,然后你等待这个相同的对象要出现在DC事件中,请将pDisp保存到全局变量,并等待直到运行DC并且DC的pDisp等于您在第一次NC事件期间保存的全局pDisp(例如,您的pDisp)全局保存在第一个触发的NC事件中)。因此,一旦您知道pDisp在DC中返回,您就知道整个文档已完成加载。

这将改善你的当前方法,但是,为了使它更加简单,你需要进行帧检查,因为即使你做了以上所有,它超过90%好但不是100%傻瓜证明,需要为此做更多的事情。

为了以有意义的方式成功进行NC / DC计数(有可能,请相信我),您需要将每个NC的pDisp保存在数组或集合中,当且仅当它不存在时在该数组/集合中。完成这项工作的关键是检查重复的NC pDisp,如果存在则不添加它。因为发生的事情是,NC使用特定URL触发,然后发生服务器端重定向或URL更改,当发生这种情况时,NC再次被触发,但它发生在用于旧URL的相同pDisp对象上。因此,相同的pDisp对象将被发送到第二个NC事件,该事件现在第二次出现一个新的URL,但仍然完全使用完全相同的pDisp对象。

现在,因为你有一个所有唯一的NC pDisp对象的计数,你可以(逐个)删除它们,因为每个DC事件发生,通过执行典型的If pDisp Is pDispArray(i) Then(这是在VB中)比较包裹一个For循环,并且对于每个取消,你的阵列数将接近0.这是准确的方法,但是,仅此一项是不够的,因为在你的计数达到0之后可能发生另一个NC / DC对此外,您必须记住在DC事件中执行与NavigateError事件完全相同的For Loop pDisp检查,因为当发生导航错误时,会触发DC事件的INSTEAD的NavigateError事件。

我知道这需要做很多事情,但是我花了多年的时间来处理这个可怕的控制来解决这些问题,我还有其他的代码&amp;方法,如果你需要,但我在这里提到的与WB Navigation真正准备好的一些东西,之前没有在线发布,所以我真的希望你发现它们很有用,让我知道你怎么做。此外,如果你想要/需要澄清一些这方面让我知道,不幸的是,如果你想100%确定网页已经完成加载,欢呼,上述并不是一切。

PS:另外,忘记提及,依赖URL来进行任何类型的计数是不准确的,这是一个非常糟糕的主意,因为几个帧可以有相同的URL - 例如,www.microsoft.com网站这样做,你可以在地址栏中看到3个左右的MS主站点。不要将URL用于任何计数方法。

答案 1 :(得分:1)

不,没有适用于所有网站的方法。原因:Javascript可能会突然触发导航(想想AJAX ......)并且没有办法预测是否或何时发生这种情况。除非你当然为特定的网站开发。

我建议问一个不同的问题:如果您想要做某事时进行导航会怎样?一旦你知道你可以发现错误。

答案 2 :(得分:0)

首先我将文档转换为XML,然后使用我的魔术方法:

    nodeXML = HtmlToXml.ConvertToXmlDocument((IHTMLDocument2)htmlDoc.DomDocument);
    if (ExitWait(false))
        return false;

转换:

public static XmlNode ConvertToXmlDocument(IHTMLDocument2 doc2)
{
    XmlDocument xmlDoc = new XmlDocument();
    IHTMLDOMNode htmlNodeHTML = null;
    XmlNode xmlNodeHTML = null;

    try
    {
        htmlNodeHTML = (IHTMLDOMNode)((IHTMLDocument3)doc2).documentElement;
        xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", ""/*((IHTMLDocument2)htmlDoc.DomDocument).charset*/, ""));
        xmlNodeHTML = xmlDoc.CreateElement("html"); // create root node
        xmlDoc.AppendChild(xmlNodeHTML);
        CopyNodes(xmlDoc, xmlNodeHTML, htmlNodeHTML);
    }
    catch (Exception err)
    {
        Utils.WriteLog(err, "Html2Xml.ConvertToXmlDocument");
    }

魔法:

private bool ExitWait(bool bDelay)
{
    if (m_bStopped)
        return true;
    if (bDelay)
    {
        DateTime now = DateTime.Now;
        DateTime later = DateTime.Now;
        TimeSpan difT = (later - now);
        while (difT.TotalMilliseconds < MainDef.IE_PARSER_DELAY)
        {
            Application.DoEvents();
            System.Threading.Thread.Sleep(10);
            later = DateTime.Now;
            difT = later - now;
            if (m_bStopped)
                return true;
        }
    }
    return m_bStopped;
}

默认情况下,m_bStopped为false,IE_PARSER_DELAY是超时值。 我希望这会有所帮助。