确定一个 WebBrowser 是否正在进行导航,是否有可能确定吗?

7
我正在尝试找到一种方法,使我的程序能够知道WebBrowser何时正在导航以及何时没有。这是因为程序将通过在文档中注入的JavaScript与加载的文档进行交互。我没有其他方法可以知道它何时开始导航,除了处理Navigating事件,因为不是我的程序而是用户通过与文档交互来导航。但是,当DocumentCompleted发生时,并不意味着它已经完成导航。我已经搜索了很多并找到了两个伪解决方案:
  1. DocumentCompleted 事件中检查 WebBrowser 的 ReadyState 属性。问题在于,如果加载的不是文档而是文档中的一个框架,则即使主文档未完成,ReadyState 也将是 Completed

  2. 为了防止这种情况,建议检查传递给 DocumentCompletedUrl 参数是否与 WebBrowserUrl 匹配。这样我就知道 DocumentCompleted 不是由文档中的其他框架调用的。

2的问题在于,正如我所说的,我知道页面何时导航的唯一方法是处理“Navigating”(或“Navigated”)事件。因此,例如,如果我在Google Maps中单击搜索,“Navigating”将被调用,但只有一个框架正在导航;而不是整个页面(在特定的Google情况下,我可以使用“WebBrowserNavigatingEventArgs”的“TargetFrameName”属性来检查是否导航的是一个框架,但并非所有框架都有名称)。因此,在此之后,“DocumentCompleted”将被调用,但其“Url”与我的“WebBrowser”的“Url”属性不同,因为只有一个框架进行了导航,因此我的程序会认为它仍在导航,永远无法结束。
将对 Navigating 的调用次数加起来,减去对 DocumentCompleted 的调用次数不起作用。它们并不总是相同的。我已经几个月没有找到解决这个问题的方法了;我一直在使用解决方案1和2,并希望它们适用于大多数情况。我的计划是在某些网页出现错误或其他情况时使用定时器,但我认为 Google 地图没有任何错误。我仍然可以使用它,但唯一更丑陋的解决方案就是烧毁我的电脑。 编辑: 到目前为止,这是我接近解决方案的最近进展:
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();
        }
    }
}

SafeWebBrowser 继承 WebBrowser。方法 OnNavigatingOnNavigatedOnDocumentCompleted 分别在相应的 WebBrowser 重写方法中被调用。属性 IsSafeNavigating 可以让我知道它是否正在导航。

2个回答

4

等待文档加载完成是一个很困难的问题,但你需要不断检查.ReadyState和.Busy属性(别忘了这个)。我将给你一些你需要的一般信息,然后在最后回答你的具体问题。

顺便说一下,NC = NavigateComplete,DC = DocumentComplete。

此外,如果你要等待的页面有框架,你需要获取对它们的引用并检查它们的.busy和.readystate属性,如果这些框架是嵌套的,那么嵌套的框架的.readystate和.busy属性也要这样做,所以你需要编写一个递归检索这些引用的函数。

现在,无论它有多少个框架,第一个触发的NC事件总是顶级文档,最后触发的DC事件也总是顶级(父)文档。

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

这将改进你现有的方法,但为了使其更加可靠,你还需要进行框架检查,因为即使你完成了上述所有工作,它也只有90%的好处,但不是100%的可靠性,需要做更多的工作来解决这个问题。

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

现在,由于您已经统计了所有唯一的NC pDisp对象的数量,因此您可以(逐个)在每个DC事件发生时将它们删除,方法是使用For循环包装的典型If pDisp Is pDispArray(i) Then比较(这是VB中的语法),对于每一个被取下的对象,您的数组计数将越来越接近0。这是正确的做法,但仅凭这些还不够,因为在计数达到0之后可能会发生另一个NC/DC对。此外,在NavigateError事件中必须记得执行与DC事件相同的For循环pDisp检查,因为当导航错误发生时,会触发NavigateError事件而不是DC事件。
我知道这听起来很多,但我花了多年时间才弄清楚如何处理这个可怕的控件,如果您需要,我有其他代码和方法,但是与WB Navigation相关的一些内容以前没有在网上发布过,所以我真的希望您能发现它们有用,并让我知道您的进展情况。此外,如果您需要对某些内容进行澄清,请告诉我,不幸的是,以上内容并不是全部,如果您想要100%确定网页加载完成,还需要其他步骤,谢谢。
PS:还有一点,依赖URL进行任何计数方法都是不准确和非常糟糕的想法,因为几个框架可以具有相同的URL - 例如,www.microsoft.com网站就是这样做的,有大约3个框架调用MS的主站点,您可以在地址栏中看到。不要使用URL进行任何计数方法。

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是超时值。 希望这能帮到您。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接