zoukankan      html  css  js  c++  java
  • 【WPF】监听WPF的WebBrowser控件弹出新窗口的事件

    WPF中自带一个WebBrowser控件,当我们使用它打开一个网页,例如百度,然后点击它其中的链接时,如果这个链接是会弹出一个新窗口的,那么它会生生的弹出一个IE窗口来,而不是在内部跳到该链接。

    如果使用Winform的WebBrowser控件,我们可以监听它的NewWindow事件,在这个事件中做一些处理,例如,在新建一个Tab来打开,或者控制它在当前WebBrowser中跳转。很不幸的是,WPF的WebBrowser没有这个事件。

    说到底,Winform的WB或者是WPF的WB都是在调用IE的一个控件,因此,Winform能加上的,我们WPF一定也有办法加上。如此,那我们就请出神器Reflector,研究一把。

    首先,我们打开Winform的WebBrowser,找到触发NewWindow事件的代码:

        protected virtual void OnNewWindow(CancelEventArgs e)
        {
            
    if (this.NewWindow != null)
            {
                
    this.NewWindow(this, e);
            }
        }

    它是在OnNewWindow方法中触发的。那么,是谁调用了这个OnNewWindow呢?接着搜索,最后在一个叫WebBrowserEvent的类里面发现这么一段:

    public void NewWindow2(ref object ppDisp, ref bool cancel)
    {
         CancelEventArgs e 
    = new CancelEventArgs();
         
    this.parent.OnNewWindow(e);
         cancel 
    = e.Cancel;
    }

    我们接着搜NewWindow2,却发现没有地方显式地调用它了。既然从方法入手没找到,那我们就来研究一下定义这个方法的WebBrowserEvent,看看是谁在使用它。
    仔细搜索一遍,最后发现在WebBrowser的CreateSink方法中有这么一段:

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

    注意这句话:

    this.cookie = new AxHost.ConnectionPointCookie(activeXInstance, this.webBrowserEvent, typeof(UnsafeNativeMethods.DWebBrowserEvents2));

    很显然,这句话是关键。AxHost.ConnectionPointCookie类的作用是:“将一个ActiveX 控件连接到处理该控件的事件的客户端”。

    上面的调用中有一个很奇怪的类型:DWebBrowserEvents2,熟悉COM的童鞋应该马上能想到,这其实是一个COM类型的定义。

    代码
    [ComImport, TypeLibType(TypeLibTypeFlags.FHidden), InterfaceType(ComInterfaceType.InterfaceIsIDispatch), Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D")]
    public interface DWebBrowserEvents2
    {
         ......
    }

    实际上,我们再去看WebBrowserEvent的定义,它恰恰是实现了这个接口的。

    [ClassInterface(ClassInterfaceType.None)]
    private class WebBrowserEvent : StandardOleMarshalObject, UnsafeNativeMethods.DWebBrowserEvents2
    {
        ......
    }

    因此,上面这句话不难理解,就是定义一个实现了特定COM接口的类型,让浏览器控件的事件能够转发到这个类型实例去处理。因此,NewWindow2其实是浏览器控件去调用的。

    Winform的WebBrowser我们搞清楚了,下面我们来看WPF的。其实,打开WPF的WebBrowser代码之后,我们会发现它跟Winform的WebBrowser机制是一样的。一个似曾相识的CreateSink方法映入眼中:

    代码
    [SecurityTreatAsSafe, SecurityCritical]
    internal override void CreateSink()
    {
        
    this._cookie = new ConnectionPointCookie(this._axIWebBrowser2, this._hostingAdaptor.CreateEventSink(), typeof(UnsafeNativeMethods.DWebBrowserEvents2));
    }

    这儿也有一个ConnectionPointCookie,但是它的访问权限是internal的:(
    第二个参数,_hostingAdapter.CreateEventSink返回的是什么呢:

    代码
    [SecurityCritical]
    internal virtual object CreateEventSink()
    {
        
    return new WebBrowserEvent(this._webBrowser);
    }

    [ClassInterface(ClassInterfaceType.None)]
    internal class WebBrowserEvent : InternalDispatchObject<UnsafeNativeMethods.DWebBrowserEvents2>, UnsafeNativeMethods.DWebBrowserEvents2
    {
        ......
    }

    仍然是一个WebBrowserEvent!悲剧的是,这个WPF的WebBrowserEvent,并没有触发NewWindowEvent:

    public void NewWindow2(ref object ppDisp, ref bool cancel)
    {
    }

    现在知道为什么WPF的WB控件没有NewWindow事件了吧?微软的童鞋压根儿就没写!

    既然微软的童鞋不写,那我们就自己折腾一把,反正原理已经搞清楚了。

    首先,我们也得定义一个DWebBrowserEvents2接口,这个我们直接通过Reflector复制一份就好了。代码就不贴上来了。

    接着,我们再仿造一个WebBrowserEvent,关键是要触发NewWindow事件:

    代码
    public partial class WebBrowserHelper
        {
            
    private class WebBrowserEvent : StandardOleMarshalObject, DWebBrowserEvents2
            {
                
    private WebBrowserHelper _helperInstance = null;

                
    public WebBrowserEvent(WebBrowserHelper helperInstance)
                {
                    _helperInstance = helperInstance;
                }
                ......
                
                
    public void NewWindow2(ref object pDisp, ref bool cancel)
                {
                    _helperInstance.OnNewWindow(ref cancel);
                }
                ......
            }
        }

    最后,我们需要仿造Framework中的代码,也来CreateSink一把(我承认,用了反射来取WebBrowser内部的东东,谁让这些类型都是internal的呢):

    代码
    private void Attach()
    {
        var axIWebBrowser2 = _webBrowser.ReflectGetProperty("AxIWebBrowser2");
        var webBrowserEvent = new WebBrowserEvent(this);
        var cookieType = typeof(WebBrowser).Assembly.GetType("MS.Internal.Controls.ConnectionPointCookie");
        _cookie = Activator.CreateInstance(
            cookieType,
            ReflectionService.BindingFlags,
            
    null,
            
    new[] { axIWebBrowser2, webBrowserEvent, typeof(DWebBrowserEvents2) },
            CultureInfo.CurrentUICulture);
    }


    最后的使用:

    var webBrowserHelper = new WebBrowserHelper(webBrowser);
    ......
    webBrowserHelper.NewWindow 
    += WebBrowserOnNewWindow;


    【效果图】

    初始网页:


    点击一个链接,默认情况下,将是弹出一个IE窗口,现在是在新的Tab中打开:

    【示例代码】

    (新建按钮点击后,请输入完整的网址,例如:http://www.sina.com)

    /Files/RMay/WpfWebBrowser.zip



  • 相关阅读:
    Mac——Mac使用MicrosoftRemoteDesktopforMac同一局域网内远程桌面Windows
    Linux—Unix、Linux、macOS操作系统的关系是什么?
    Mac/Windows——怎么启动进入或退出jupyter notebook?如何网页查看后台目录和网页编辑脚本文件
    金蝶EAS——我的EAS报销流程怎么能让另一个人看到呢?即如何设置流程传阅功能?设置“代理报销”
    【C#】.Net 腾讯云一句话识别 【实例】
    ucharts图表tooltip自定义换行
    《山丘》李宗盛
    指定特定ip ip段 用户使用ssh访问linux系统
    (论文笔记)Learning Deep Structured Semantic Models for Web Search using Clickthrough Data
    (论文笔记)Deep Neural Network for YouTube Recommendation
  • 原文地址:https://www.cnblogs.com/RMay/p/1745388.html
Copyright © 2011-2022 走看看