强制所有网页链接在同一页面打开或者在TabControl中弹出新窗口

时间:2022-12-20 12:06:17

IEwebbrowser中老生常谈的话题。

一般的解决都是通过

    // webBrowser.Navigating += WebBrowser_Navigating; 注册转跳前事件
    private  void WebBrowser_Navigating(object sender, System.Windows.Forms.WebBrowserNavigatingEventArgs e)
        {
            webBrowser.Navigate("新的网页地址");
        }

但是并不是特别的好用,比如网页中设置是弹出窗口来跳转网页

下面我会将为什么不好使用,已经正确的用法

 

好在是C# 4.72开源了。不用反编译了。 有些东西也好解释了开源地址

搜索Webbrowser查看源代码,你会发现。很多功能都是由一个叫做AxWebbrowser的类是实现的。很明显,webbrowser大部分都是Com控件的包装。

找到Navigate,看看是具体代码

 

 强制所有网页链接在同一页面打开或者在TabControl中弹出新窗口

 

 我们继续深挖

  private void PerformNavigateHelper(string urlString, bool newWindow, string targetFrameName, byte[] postData, string headers)
        {
            object objUrlString = (object)urlString;
            object objFlags = (object) (newWindow ? 1 : 0);
            object objTargetFrameName = (object)targetFrameName;
            object objPostData = (object)postData;
            object objHeaders = (object)headers;
            PerformNavigate2(ref objUrlString, ref objFlags, ref objTargetFrameName, ref objPostData, ref objHeaders);
        }
 
        private void PerformNavigate2(ref object URL, ref object flags, ref object targetFrameName, ref object postData, ref object headers) 
        {
            try {
                this.AxIWebBrowser2.Navigate2(ref URL, ref flags, ref targetFrameName, ref postData, ref headers);
            }
            catch (COMException ce) {
                if ((uint)unchecked(ce.ErrorCode) != (uint)unchecked(0x800704c7)) {
                    // "the operation was canceled by the user" - navigation failed
                    // ignore this error, IE has already alerted the user. 
                    throw;
                }
            }
        }

跳转都是用一个方法。

最终实现的是一个叫做PerformNaviagate2内的AxIWebbrowser2所实现的

继续深挖

最后发现在一个名为UnsafeNativeMethods的类中

这个类是用来做什么呢?

是实现win32API和COM的。(说句心里话写桌面软件,微软心里面还是C++是亲儿子。多少懂一些C++没有错。)

  [DispId(500)] void Navigate2([In] ref object URL, [In] ref object flags,
                            [In] ref object targetFrameName, [In] ref object postData,
                            [In] ref object headers);

嗯,看起来似乎就是普通的导航连接啊。

到这里就是很明显了,Navigate就是负责普通的导航,如果是遇到弹出窗口等 基本不好用的

那我们该如何正确的处理呢?

准确的说,我们是想在网页弹出新窗口或者跳转新网页的时候,将其强制的定位到一个网页,让其不弹出新的窗口。

所以我们重新回到了Webbrowser 了

我们发现Webbrowser继承了WebbroweserBase

我们来看看父类中的函数

果不其然发现了重点

    /// <include file='doc\WebBrowserBase.uex' path='docs/doc[@for="WebBrowserBase.CreateSink"]/*' />
        /// <devdoc>
        ///     <para>
        /// This will be called when we are ready to start listening to events.
        /// Inheritors can override this method to hook their own connection points.
        ///     </para>
        /// </devdoc>
        protected virtual void CreateSink() {
        }
 

百度翻译了一下

强制所有网页链接在同一页面打开或者在TabControl中弹出新窗口

哈,找到了我们该如何触发事件的地方了。这意思就是事件发生时,Webbrowser会做的一些事情。

很明显子类肯定要重写这个方法。重新到Webbrowser寻找这个方法

protected override void CreateSink() {
            object ax = this.activeXInstance;
            if (ax != null) {
                webBrowserEvent = new WebBrowserEvent(this);
                webBrowserEvent.AllowNavigation = AllowNavigation;
                this.cookie = new AxHost.ConnectionPointCookie(ax, webBrowserEvent,
                        typeof(UnsafeNativeMethods.DWebBrowserEvents2));
            }
        }

来看一下啊所有的参数都是些什么

 this.activeXInstance;
 //这是Webbrowser的父类一个参数,意思是获取基础 ActiveX WebBrowser 控件。
 
  webBrowserEvent = new WebBrowserEvent(this);    
webBrowserEvent.AllowNavigation = AllowNavigation; //这两个都是一个实例,只不过在设置参数。 //WebBrowserEvent是什么? //他是实现了 //StandardOleMarshalObject,UnsafeNativeMethods.DWebBrowserEvents2的类 this.cookie = new AxHost.ConnectionPointCookie(ax, webBrowserEvent, typeof(UnsafeNativeMethods.DWebBrowserEvents2)); //创建给定接口类型的连接点。 //将调用实现该接口的托管代码接收器。

 

到现在也说了很多 我们来理一下思路

 

强制所有网页链接在同一页面打开或者在TabControl中弹出新窗口

Navigate可以排除掉了。不是我们想要的。

那是什么地方呢?

我们来看看这个WebBrowserEvent类都是实现了方法

 public void BeforeNavigate2(object pDisp, ref object urlObject, ref object flags, ref object targetFrameName, ref object postData, ref object headers, ref bool cancel) {
                Debug.Assert(parent != null, "Parent should have been set");
                //Note: we want to allow navigation if we haven't already navigated.
                if (AllowNavigation || !haveNavigated)
                {
                    Debug.Assert(urlObject == null || urlObject is string, "invalid url type");
                    Debug.Assert(targetFrameName == null || targetFrameName is string, "invalid targetFrameName type");
                    Debug.Assert(headers == null || headers is string, "invalid headers type");
                    //
                    // Due to a bug in the interop code where the variant.bstr value gets set
                    // to -1 on return back to native code, if the original value was null, we
                    // have to set targetFrameName and headers to "".
                    if (targetFrameName == null) {
                        targetFrameName = "";
                    }
                    if (headers == null) {
                        headers = "";
                    }
 
                    string urlString = urlObject == null ? "" : (string)urlObject;
                    WebBrowserNavigatingEventArgs e = new WebBrowserNavigatingEventArgs(
                        new Uri(urlString), targetFrameName == null ? "" : (string)targetFrameName);
                    this.parent.OnNavigating(e);
                    cancel = e.Cancel;
                }
                else 
                {
                    cancel = true;
                }
            }
 public void NewWindow2(ref object ppDisp, ref bool cancel) {
                CancelEventArgs e = new CancelEventArgs();
                this.parent.OnNewWindow(e);
                cancel = e.Cancel;
            }

到这里大致过程就明了。

深层的跳转,新开窗口都这里。

我们现在只有能重写以上这两个就可以了。

最后 我们再来整理一下全部的思路

强制所有网页链接在同一页面打开或者在TabControl中弹出新窗口

理解了大致的思路,我们就编写代码了。

思路就是

手写编写DWwebbrowserEvent2的接口,编写两个方法。

手写WebbrowserEvent类,实现DW接口。还需要继承StandardOleMarshalObject类

剩下就重写CreateSinK方法了。这个只需要继承Webbrowser就好了。

为了重写定位或者跳转网页,很明显我们还需要一个类来实现webbrowser的url。

而且还需要实现CancelEventArgs类来设置是否取消事件。

 public class WebBrowserUrl : CancelEventArgs
    {
        public string Url { get; }

        public string Frame { get; }

        public WebBrowserUrl(String url, String frame) : base()
        {
            this.Url = url;
            this.Frame = frame;
        }

    }
    public class NewWebBrwser : System.Windows.Forms.WebBrowser
    {
        System.Windows.Forms.AxHost.ConnectionPointCookie cookie;
        NewWebBrowserEvent events;

        public event EventHandler BeforeNavigate;

        public event EventHandler BeforeNewWindow;

        protected override void CreateSink()
        {
            base.CreateSink();//还是需要源
            events = new NewWebBrowserEvent(this);
            cookie = new AxHost.ConnectionPointCookie(this.ActiveXInstance, events, typeof(DWebBrowserEvents2));
        }
        protected override void DetachSink()
        {
            if (null != cookie)
            {
                cookie.Disconnect();
                cookie = null;
            }
            base.DetachSink();
        }
     public void OnBeforeNavigate(string url, string frame, out bool cancel)
        {
          
            WebBrowserUrl webBrowserUrl = new WebBrowserUrl(url, frame);
            BeforeNavigate?.Invoke(this, webBrowserUrl);
            cancel = webBrowserUrl.Cancel;
        }
      public void OnBeforeNewWindow(string url, out bool cancel)
        {
           
             WebBrowserUrl webBrowserUrl = new WebBrowserUrl(url, null);
             BeforeNewWindow?.Invoke(this, webBrowserUrl);
             cancel = webBrowserUrl.Cancel;

        }
       

    }
    public class NewWebBrowserEvent : System.Runtime.InteropServices.StandardOleMarshalObject, DWebBrowserEvents2
    {
        private NewWebBrwser webBrowser;

        public NewWebBrowserEvent(NewWebBrwser newWebBrowser) => webBrowser = newWebBrowser;

        public void BeforeNavigate2(object pDisp, ref object urlObject, ref object flags, ref object targetFrameName, ref object postData, ref object headers, ref bool cancel) => webBrowser.OnBeforeNavigate((string)urlObject, (string)targetFrameName, out cancel);

        //当高于IE6时使用
        public void NewWindow3(object pDisp, ref bool cancel, ref object flags, ref object URLContext, ref object URL) => webBrowser.OnBeforeNewWindow((string)URL, out cancel);
    }

    //下面这些特性都是古老的COM要用的
    [System.Runtime.InteropServices.ComImport(), System.Runtime.InteropServices.Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D"),
        System.Runtime.InteropServices.InterfaceTypeAttribute(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIDispatch),
        System.Runtime.InteropServices.TypeLibType(System.Runtime.InteropServices.TypeLibTypeFlags.FHidden)]
    public interface DWebBrowserEvents2
    {
        [System.Runtime.InteropServices.DispId(250)]
        void BeforeNavigate2(object pDisp, ref object urlObject, ref object flags, ref object targetFrameName, ref object postData, ref object headers, ref bool cancel);


        //当高于IE6时使用 //本来应该还有一个NewWindow2 太古老 根本用不上了
        [System.Runtime.InteropServices.DispId(273)]
        void NewWindow3([System.Runtime.InteropServices.In,System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.IDispatch)]
                object pDisp,
                      [System.Runtime.InteropServices.In, System.Runtime.InteropServices.Out]
                ref bool cancel,
                      [System.Runtime.InteropServices.In]
                ref object flags,
                      [System.Runtime.InteropServices.In]
                ref object URLContext,
                      [System.Runtime.InteropServices.In]
                ref object URL);
    }

使用方式

       NewWebBrwser Brwser = new NewWebBrwser();
        public Form1()
        {
            InitializeComponent();

            Brwser.Url = new Uri("http://www.baidu.com");
            Brwser.BeforeNewWindow += Brwser_BeforeNewWindow;
            Brwser.BeforeNavigate += Brwser_BeforeNavigate;
            this.Controls.Add(Brwser);
        }


        private void Brwser_BeforeNewWindow(object sender, EventArgs e)
        {
            WebBrowserUrl newWeb = e as WebBrowserUrl;

            Brwser.Navigate(newWeb.Url);

            newWeb.Cancel = true;//取消转跳事件
        }

        private void Brwser_BeforeNavigate(object sender, EventArgs e)
        {
            

        }