【问题标题】:Is is possible to know for sure if a WebBrowser is navigating or not?是否可以确定 WebBrowser 是否正在导航?
【发布时间】:2010-11-08 19:44:22
【问题描述】:

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

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

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

2 的问题在于,正如我所说,我必须知道页面何时导航的唯一方法是处理 Navigating(或 Navigated)事件。因此,例如,如果我在 Google 地图中并单击搜索,Navigating 将被调用,但只是一个框架正在导航;不是整个页面(在特定的 Google 案例中,我可以使用 WebBrowserNavigatingEventArgs 的 TargetFrameName 属性来检查它是否是正在导航的框架,但框架并不总是有名称)。所以在那之后,DocumentCompleted 将被调用,但与 Url 与我的 WebBrowsers 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。方法OnNavigating、OnNavigated 和OnDocumentCompleted 在相应的WebBrowser 覆盖方法上调用。 IsSafeNavigating 的属性会让我知道它是否正在导航。

【问题讨论】:

    标签: c# .net browser webbrowser-control


    【解决方案1】:

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

    顺便说一句,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 对象被发送到第二次使用新 URL 发生的第二个 NC 事件,但仍然使用完全相同的 pDisp 对象完成。

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

    我知道这需要花费很多时间,但我花了好几年的时间来处理这个可怕的控件来解决这些问题,如果你需要,我还有其他代码和方法,但是我提到的一些东西这里是关于 WB Navigation 真正准备好的,之前还没有在网上发布过,所以我真的希望你觉得它们有用,让我知道你是怎么做的。此外,如果您想/需要澄清其中的一些内容,请告诉我,不幸的是,如果您想 100% 确定网页已完成加载,以上内容并不是全部,干杯。

    PS:另外,忘了提一下,依靠 URL 进行任何类型的计数都是不准确的,也是一个非常糟糕的主意,因为多个框架可以具有相同的 URL - 例如,www.microsoft.com 网站就是这样做的,您在地址栏中看到大约 3 个框架调用 MS 的主站点。不要将 URL 用于任何计数方法。

    【讨论】:

      【解决方案2】:

      首先我将文档转换为 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 是超时值。 我希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-16
        • 1970-01-01
        • 2012-04-11
        • 1970-01-01
        • 2012-12-24
        • 1970-01-01
        • 1970-01-01
        • 2022-12-05
        相关资源
        最近更新 更多