强制所有网页链接在同一页面打开或者在TabControl中弹出新窗口
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,看看是具体代码
我们继续深挖
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() { }
百度翻译了一下
哈,找到了我们该如何触发事件的地方了。这意思就是事件发生时,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)); //创建给定接口类型的连接点。 //将调用实现该接口的托管代码接收器。
到现在也说了很多 我们来理一下思路
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; }
到这里大致过程就明了。
深层的跳转,新开窗口都这里。
我们现在只有能重写以上这两个就可以了。
最后 我们再来整理一下全部的思路
理解了大致的思路,我们就编写代码了。
思路就是
手写编写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) { }