用C#打造支持插件的多标签浏览器(篇一)扩展WebBrowser

本文详细介绍了如何通过继承MSHTML的高级接口,增强.NET Framework中的WebBrowser控件功能。重点讨论了实现拖拽功能、屏蔽脚本错误窗口以及自定义事件处理,提供了完整代码示例,帮助开发者解决实际问题。

自从 .NET Framework 2.0 中新增了 WebBrowser 控件, 要在WinForm里面嵌入一个浏览器就更方便了。

但就WebBrowser本身而已,封装过程中,有些底层的东西找不到了,比如IWebBrowser2中的Silent,这个属性的用处简单说就是屏蔽JS错误窗口。虽然可以通过Document.DomDocument获得Document的COM接口,如常用到的IHTMLDocument2。但是如果要实现想遨游那样的拖拽功能,默认就不支持了,而这个功能我非常喜欢。

幸运的是,我们可以继承一些有用的接口,扩展WebBrowser,实现一些我们想要的功能。

public partial class ExtendedWebBrowser : System.Windows.Forms.WebBrowser, 
        MsHtmHstInterop.IDocHostUIHandler, 
        MsHtmHstInterop.IDropTarget, 
        IEInterface.IWebBrowser2

除了继承WebBrowser以外,这里我继承了另外三个接口,IDocHostUIHandler,IDropTarget是MsHtmHstInterop里的, IEInterface.IWebBrowser2是在程序代码里的。顾名思义下,这三个接口主要还是为了显示拖放,当然,挖掘一下,会有更多强悍的功能。

如果你确实没有见过,这也不要紧,在这篇文章的结尾你会找到下载的地址,先简单说说他们是哪里的,什么用的。

IDocHostUIHandler和IDropTarget都是MSHTML的高级支持接口,可以帮助我们自由调整微软的WebBrowser用户界面,可以获得浏览器的COM接口之后,实现在 Web Browser 中显示个人的定制上下文菜单,文本和地址的拖放等。

参考文章1:在C#中使用MSHTML的高级支持接口
参考文章2:C# 继承.Net中提供的WebBrowser 2.0控件以实现IE的超级拖放

参考文章2具体实现了拖放功能,但是如果只是按那个方法,确实无法实现拖放功能,具体可以见那条非常有建设性的留言:
问题1
在类的构造函数中加上:
 ICustomDoc idoc = this.Document.DomDocument as ICustomDoc;
 不行,Document此时还是NULL,必须在OnDocumentCompleted后执行
问题2
 WebBrowser没有公开IWebBrowser2,不能调用RegisterAsDropTarget的属性,不过,AllowWebBrowserDrop属性也是一样的
问题3
 IDocHostUIHandler接口中GetDropTarget根本就没有被调用,我设置应该没有问题,IDocHostUIHandler.ShowContextMenu都调用了,就是GetDropTarget不调用!

那位楼主的回答是:
问题1:在 ICustomDoc idoc = this.Document.DomDocument as ICustomDoc;之前,需要先把webbrowser 定位到about:blank页面去,就行了。
问题2:实现IWebBrowser2再重写其属性,直接返回true,或是自己定义bool变量
问题3:我的程序中是调用了的,不知你的是什么原因。。。。


先鼓掌下,简明扼要,行之有效! 关于第一条我就不累赘了,第三条也不多说,因为如果操作正确了,确实是可以用的,来说说第二条,然后补充一个第四条(这条很关键o!)

 [ComImport, SuppressUnmanagedCodeSecurity, TypeLibType(TypeLibTypeFlags.FOleAutomation |
   (TypeLibTypeFlags.FDual | TypeLibTypeFlags.FHidden)),
   Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E")]
        public interface IWebBrowser2
        {
            /*[DispId(100)]
            void GoBack();
            [DispId(0x65)]
            void GoForward();
            [DispId(0x66)]
            void GoHome();
            [DispId(0x67)]
            void GoSearch();
            [DispId(0x68)]
            void Navigate([In] string Url, [In] ref object flags, [In] ref object targetFrameName, [In] ref object postData, [In] ref object headers);
            [DispId(-550)]
            void Refresh();
            [DispId(0x69)]
            void Refresh2([In] ref object level);
            [DispId(0x6a)]
            void Stop();
            [DispId(200)]
            object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
            [DispId(0xc9)]
            object Parent { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
            [DispId(0xca)]
            object Container { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
            [DispId(0xcb)]
            object Document { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
            [DispId(0xcc)]
            bool TopLevelContainer { get; }
            [DispId(0xcd)]
            string Type { get; }
            [DispId(0xce)]
            int Left { get; set; }
            [DispId(0xcf)]
            int Top { get; set; }
            [DispId(0xd0)]
            int Width { get; set; }
            [DispId(0xd1)]
            int Height { get; set; }
            [DispId(210)]
            string LocationName { get; }
            [DispId(0xd3)]
            string LocationURL { get; }
            [DispId(0xd4)]
            bool Busy { get; }
            [DispId(300)]
            void Quit();
            [DispId(0x12d)]
            void ClientToWindow(out int pcx, out int pcy);
            [DispId(0x12e)]
            void PutProperty([In] string property, [In] object vtValue);
            [DispId(0x12f)]
            object GetProperty([In] string property);
            [DispId(0)]
            string Name { get; }
            [DispId(-515)]
            int HWND { get; }
            [DispId(400)]
            string FullName { get; }
            [DispId(0x191)]
            string Path { get; }
            [DispId(0x192)]
            bool Visible { get; set; }
            [DispId(0x193)]
            bool StatusBar { get; set; }
            [DispId(0x194)]
            string StatusText { get; set; }
            [DispId(0x195)]
            int ToolBar { get; set; }
            [DispId(0x196)]
            bool MenuBar { get; set; }
            [DispId(0x197)]
            bool FullScreen { get; set; }
            [DispId(500)]
            void Navigate2([In] ref object URL, [In] ref object flags, [In] ref object targetFrameName, [In] ref object postData, [In] ref object headers);
            [DispId(0x1f5)]
            NativeMethods.OLECMDF QueryStatusWB([In] NativeMethods.OLECMDID cmdID);
            [DispId(0x1f6)]
            void ExecWB([In] NativeMethods.OLECMDID cmdID, [In] NativeMethods.OLECMDEXECOPT cmdexecopt, ref object pvaIn, IntPtr pvaOut);
            [DispId(0x1f7)]
            void ShowBrowserBar([In] ref object pvaClsid, [In] ref object pvarShow, [In] ref object pvarSize);
            [DispId(-525)]
            WebBrowserReadyState ReadyState { get; }
            [DispId(550)]
            bool Offline { get; set; }*/
            [DispId(0x227)]
            bool Silent { get; set; }
            [DispId(0x228)]
            bool RegisterAsBrowser { get; set; }
            [DispId(0x229)]
            bool RegisterAsDropTarget { get; set; }
            /*[DispId(0x22a)]
            bool TheaterMode { get; set; }
            [DispId(0x22b)]
            bool AddressBar { get; set; }
            [DispId(0x22c)]
            bool Resizable { get; set; }*/
        }
        #endregion


Silent:当IE浏览器遇到脚本错误时浏览器,左下角会出现一个黄色图标,点击可以查看脚本错误的详细信息,并不会有弹出的错误信息框。当我们使用WebBrowser控件时有错误信息框弹出,这样程序显的很不友好,而且会让一些自动执行的程序暂停。 屏蔽的方法很简单 WebBrowser1.Silent = true。

RegisterAsBrowser:设置为true,将导致新生成的WebBrowser控件参与到窗口命名的冲突问题上。例如,如果一个窗口的名字在脚本的另外一处用到,那么该控件被派上用场,而不是再产生一个新的窗口,因为控件在打开一个新的窗口之前先检查一下所有已存在的窗口名称以避免命名冲突。

RegisterAsDropTarget:为ture的时候,就允许用户拖放HTML文档到当前Web浏览器

个人觉得这应该是比较完整的 IWebBrowser2 接口声明了,我们只用到拖放那一部分,所以注释掉了多余部分,有兴趣的可以去试试那些注释掉的功能。可以参考:WebBrowser秘笈之属性、方法、事件

接下来说说第四条,也就是上面没有提到的一条,困扰了我好几个小时:由于继承了 IDocHostUIHandler 接口,里面有很多未实现的方法,很多只要把那句默认的throw去掉就行了,可测试过程中,我发现两个致命的问题,1:网页上无论怎么按键都没有反应,2:鼠标右键,没有菜单弹出来。经过各种搜索,终于找到了原因,是返回值的问题,而解决的办法很奇特,就是通过 throw COMException 来返回值,这个的副作用就是调试过程中有时候很麻烦。下面来段代码:

        private const int WM_KEYDOWN = 0x100;
        private const int WM_KEYUP = 0x101;
        private const int WM_SYSKEYDOWN = 0x104;
        private const int WM_SYSKEYUP = 0x105;
        private const byte VK_TAB = 0x09;
        [DllImport("User32.dll")]
        public static extern short GetAsyncKeyState(int vKey);
        void MsHtmHstInterop.IDocHostUIHandler.TranslateAccelerator(
            ref MsHtmHstInterop.tagMSG lpMsg, ref Guid pguidCmdGroup, uint nCmdID)
        {
            const int WM_KEYDOWN = 0x0100;
            const int VK_CONTROL = 0x11;
            if (lpMsg.message != WM_KEYDOWN)
                // don't disable
                throw new COMException("", 1); // returns HRESULT = S_FALSE
            lpMsg.wParam &= 0xFF; // get the virtual keycode
            if (lpMsg.wParam == 'N')
                if (GetAsyncKeyState(VK_CONTROL) < 0)
                    // disable the Ctrl-N accelerator
                    throw new COMException("", 0); // returns HRESULT = S_OK
            // allow everything else
            throw new COMException("", 1); // returns HRESULT = S_FALSE
        }

        void MsHtmHstInterop.IDocHostUIHandler.ShowContextMenu(
            uint dwID, ref MsHtmHstInterop.tagPOINT ppt, object pcmdtReserved, object pdispReserved)
        {
            // tell MsHtml to show its default context menu
            const int Error = 1;
            throw new COMException("", Error); // returns HRESULT = S_FALSE;
        }

这两个方法里面填上这些内容就搞定了!


下面来贴一下关键代码,包括声明和构造函数等

        public event Events.DragEnterEventHander ExDragEnter;
        private Events.DragEnterEventArgs dragEnterEventArgs;
        public event Events.DragOverEventHander ExDragOver;
        private Events.DragOverEventArgs dragOverEventArgs;
        public event Events.DragLeaveEventHander ExDragLeave;
        public event Events.DragDropEventHander ExDragDrop;
        private Events.DragDropEventArgs dragDropEventArgs;
        private bool registerAsDropTarget = true;
        private bool registerAsBrowser = true;
        private bool slient = true;
        MsHtmHstInterop.IDropTarget pDropTarget;//保存原有的拖放对象
        private bool isDragToInputBox = false;//判断是否是在向输入框中拖放
        Image favicon = null;

        public ExtendedWebBrowser()
        {
            InitializeComponent();

            this.AllowWebBrowserDrop = true;
            Navigate("about:blank");
            MsHtmHstInterop.ICustomDoc idoc = this.Document.DomDocument as MsHtmHstInterop.ICustomDoc;
            idoc.SetUIHandler(this as MsHtmHstInterop.IDocHostUIHandler);
            dragEnterEventArgs = new Events.DragEnterEventArgs();
            dragOverEventArgs = new Events.DragOverEventArgs();
            dragDropEventArgs = new Events.DragDropEventArgs();

            this.ScriptErrorsSuppressed = true; // 忽略脚本错误
            this.Silent = true;                 // COM中忽略脚本错误的属性
        }


创建于ActiveX的关联

        AxHost.ConnectionPointCookie cookie;
        WebBrowserExtendedEvents events;

        //This method will be called to give you a chance to create your own event sink
        protected override void CreateSink()
        {
            //MAKE SURE TO CALL THE BASE or the normal events won't fire
            base.CreateSink();
            events = new WebBrowserExtendedEvents(this);
            cookie = new AxHost.ConnectionPointCookie(this.ActiveXInstance, events, typeof(LingSky.ControlLibrary.IEInterface.DWebBrowserEvents2));
        }

        protected override void DetachSink()
        {
            if (null != cookie)
            {
                cookie.Disconnect();
                cookie = null;
            }
            base.DetachSink();
        }


控制新开页面及关闭

        //This new event will fire when the page is navigating
        public event EventHandler<WebBrowserExtendedNavigatingEventArgs> ExtendedBeforeNavigate;

        protected void OnExtendedNavigating(string url, string frame, out bool cancel)
        {
            EventHandler<WebBrowserExtendedNavigatingEventArgs> h = ExtendedBeforeNavigate;
            WebBrowserExtendedNavigatingEventArgs args = new WebBrowserExtendedNavigatingEventArgs(url, frame);
            if (null != h)
            {
                h(this, args);
            }
            //Pass the cancellation chosen back out to the events
            cancel = args.Cancel;
        }

        public event EventHandler<WebBrowserExtendedNewWindowEventArgs> ExtendedBeforeNewWindow;

        protected void OnExtendedNewWindow(string url, out bool cancel)
        {
            EventHandler<WebBrowserExtendedNewWindowEventArgs> h = ExtendedBeforeNewWindow;
            WebBrowserExtendedNewWindowEventArgs args = new WebBrowserExtendedNewWindowEventArgs(url);
            if (null != h)
            {
                h(this, args);
            }
            //Pass the cancellation chosen back out to the events
            cancel = args.Cancel;
        }

        public event EventHandler<Events.WebBrowserExtendedWindowClosingEventArgs> ExtendedWindowClosing;

        protected void OnExtendWindowClosing(out bool cancel)
        {
            EventHandler<Events.WebBrowserExtendedWindowClosingEventArgs> h = ExtendedWindowClosing;
            Events.WebBrowserExtendedWindowClosingEventArgs args = new Events.WebBrowserExtendedWindowClosingEventArgs();
            if (null != h)
            {
                h(this, args);
            }
            //Pass the cancellation chosen back out to the events
            cancel = args.Cancel;
        }


捕获WebBrowser事件

        //This class will capture events from the WebBrowser
        class WebBrowserExtendedEvents : System.Runtime.InteropServices.StandardOleMarshalObject, LingSky.ControlLibrary.IEInterface.DWebBrowserEvents2
        {
            ExtendedWebBrowser _Browser;
            private bool m_bPop;
            public WebBrowserExtendedEvents(ExtendedWebBrowser browser)
            { _Browser = browser; }

            //Implement whichever events you wish
            public void BeforeNavigate2(object pDisp, ref object URL, ref object flags, ref object targetFrameName, ref object postData, ref object headers, ref bool cancel)
            {
                //_Browser.OnBeforeNavigate((string)URL, (string)targetFrameName, out cancel);
                _Browser.OnExtendedNavigating((string)URL, (string)targetFrameName, out cancel);
            }

            public void NewWindow3(ref object ppDisp, ref bool Cancel, uint dwFlags, string bstrUrlContext, string bstrUrl)
            {
                _Browser.OnExtendedNewWindow(bstrUrl, out Cancel);
            }

            #region DWebBrowserEvents2 成员

            public void StatusTextChange(string Text)
            {
                //throw new NotImplementedException();
            }

            public void ProgressChange(int Progress, int ProgressMax)
            {
                //throw new NotImplementedException();
            }

            public void CommandStateChange(int Command, bool Enable)
            {
                //throw new NotImplementedException();
            }

            public void DownloadBegin()
            {
                m_bPop = false;
            }

            public void DownloadComplete()
            {
                m_bPop = true;
            }

            public void TitleChange(string Text)
            {
                //throw new NotImplementedException();
            }

            public void PropertyChange(string szProperty)
            {
                //throw new NotImplementedException();
            }

            public void NewWindow2(ref object ppDisp, ref bool Cancel)
            {
                Cancel = m_bPop;
            }

            public void NavigateComplete2(object pDisp, ref object URL)
            {
              
            }

            public void DocumentComplete(object pDisp, ref object URL)
            {
                //throw new NotImplementedException();
            }

            public void OnQuit()
            {
                //throw new NotImplementedException();
            }

            public void OnVisible(bool Visible)
            {
                //throw new NotImplementedException();
            }

            public void OnToolBar(bool ToolBar)
            {
                //throw new NotImplementedException();
            }

            public void OnMenuBar(bool MenuBar)
            {
                //throw new NotImplementedException();
            }

            public void OnStatusBar(bool StatusBar)
            {
                //throw new NotImplementedException();
            }

            public void OnFullScreen(bool FullScreen)
            {
                //throw new NotImplementedException();
            }

            public void OnTheaterMode(bool TheaterMode)
            {
                //throw new NotImplementedException();
            }

            public void WindowSetResizable(bool Resizable)
            {
                //throw new NotImplementedException();
            }

            public void WindowSetLeft(int Left)
            {
                //throw new NotImplementedException();
            }

            public void WindowSetTop(int Top)
            {
                //throw new NotImplementedException();
            }

            public void WindowSetWidth(int Width)
            {
                //throw new NotImplementedException();
            }

            public void WindowSetHeight(int Height)
            {
                //throw new NotImplementedException();
            }

            public void WindowClosing(bool IsChildWindow, ref bool Cancel)
            {
                _Browser.OnExtendWindowClosing(out Cancel);
            }

            public void ClientToHostWindow(ref int CX, ref int CY)
            {
                //throw new NotImplementedException();
            }

            public void SetSecureLockIcon(int SecureLockIcon)
            {
                //throw new NotImplementedException();
            }

            public void FileDownload(ref bool Cancel)
            {
                //throw new NotImplementedException();
            }

            public void NavigateError(object pDisp, ref object URL, ref object Frame, ref object StatusCode, ref bool Cancel)
            {
                //throw new NotImplementedException();
            }

            public void PrintTemplateInstantiation(object pDisp)
            {
                //throw new NotImplementedException();
            }

            public void PrintTemplateTeardown(object pDisp)
            {
                //throw new NotImplementedException();
            }

            public void UpdatePageStatus(object pDisp, ref object nPage, ref object fDone)
            {
                //throw new NotImplementedException();
            }

            public void PrivacyImpactedStateChange(bool bImpacted)
            {
                //throw new NotImplementedException();
            }

            #endregion
        }


IDropTarget 成员(实现拖放)

        #region IDropTarget 成员

        public new void DragEnter(MsHtmHstInterop.IDataObject pDataObj, uint grfKeyState, MsHtmHstInterop._POINTL pt, ref uint pdwEffect)
        {
            pDropTarget.DragEnter(pDataObj, grfKeyState, pt, ref pdwEffect);//调用默认方法
            //获取拖动的参数
            if (ExDragEnter != null)
            {
                DataObject dobj = null;
                if (pDataObj != null)
                {
                    dobj = new DataObject(pDataObj);//拖动数据
                    Point p = new Point(pt.x, pt.y);//鼠标在容器上的位置
                    dragEnterEventArgs.SetParameters(dobj, grfKeyState, p, pdwEffect);
                    ExDragEnter(this, dragEnterEventArgs);//此处为自定义事件
                    if (dragEnterEventArgs.handled)//自定义事件的返回值
                    {
                        pdwEffect = dragEnterEventArgs.pdwEffect;
                    }
                }
            }
        }

        public new void DragLeave()
        {
            pDropTarget.DragLeave();//调用默认的方法
            if (ExDragLeave != null)
            {
                ExDragLeave(this);//自定义事件
            }
        }

        public new void DragOver(uint grfKeyState, MsHtmHstInterop._POINTL pt, ref uint pdwEffect)
        {
            uint temp = pdwEffect;//保存原有的拖放效果,当调用默认的
            //操作时此参数会改变,并根据此来判
            //断是否向输入框中拖放

            pDropTarget.DragOver(grfKeyState, pt, ref pdwEffect);//调用默认方法        
            if (pdwEffect > 0)//如果值变为了零,那么是向输入框中拖放了
            {
                isDragToInputBox = true;//标记设为true
                temp = 0;
            }
            else
            {
                isDragToInputBox = false;//否则标记设为false
                pdwEffect = temp;
            }
            if (ExDragOver != null)//自定义事件处理
            {
                Point p = new Point(pt.x, pt.y);
                dragOverEventArgs.SetParameters(grfKeyState, p, temp);
                if (dragOverEventArgs.handled)
                {
                    pdwEffect = dragOverEventArgs.pdwEffect;
                }
            }
        }

        public void Drop(MsHtmHstInterop.IDataObject pDataObj, uint grfKeyState, MsHtmHstInterop._POINTL pt, ref uint pdwEffect)
        {
            if (ExDragDrop != null)//自定义事件处理
            {
                if (pDataObj != null && !isDragToInputBox)//如果没有向输入框中拖放则触发此事件
                //否则按默认处理
                {
                    DataObject dobj = new DataObject(pDataObj);
                    Point p = new Point(pt.x, pt.y);
                    dragDropEventArgs.SetParameters(dobj, grfKeyState, p, pdwEffect);
                    ExDragDrop(this, dragDropEventArgs);
                    if (dragDropEventArgs.handled)
                    {
                        pdwEffect = dragDropEventArgs.pdwEffect;
                    }
                }
            }
            pDropTarget.Drop(pDataObj, grfKeyState, pt, ref pdwEffect);//调用默认方法
        }

        #endregion


另外,补充一个截获IWebBrowser2接口的方法,这个方法在我的程序里是没有用到的,主要用于弹出新窗口的控制,因为我的是多窗口,不弹出新窗口,但有兴趣的可以试试,毕竟IWebBrowser2可以现实很多功能,有关的参考文章也附上: WebBrowser弹出窗口之(一)

        #region 截获 IWebBrowser2 接口

        private SHDocVw.IWebBrowser2 axIWebBrowser2=null;
        // 该方法由系统自动调用,从该函数的调用中截获到IWebBrowser2接口 
        [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
        protected override void AttachInterfaces(object nativeActiveXObject)
        {
            this.axIWebBrowser2 = (SHDocVw.IWebBrowser2)nativeActiveXObject;
            base.AttachInterfaces(nativeActiveXObject);
        }

        [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
        protected override void DetachInterfaces()
        {
            this.axIWebBrowser2 = null;
            base.DetachInterfaces();
        }

        // 返回WebBrowser的自动化对象    
        public object ApplicationObject
        {
            get { return axIWebBrowser2.Application; }
        }
        #endregion



最后还有一个我用来获取favicon的方法,比较土,如果有更好的方法要告诉我哦!

        public string GetFaviconUrl()
        {
            if (this.Document.Url.AbsolutePath.Equals("blank")) return null;
            try
            {
                foreach (HtmlElement eOne in this.Document.GetElementsByTagName("link"))
                {
                    if (eOne.GetAttribute("rel").ToLower().Equals("shortcut icon"))
                    {
                        return eOne.GetAttribute("href");
                    }
                }

            }
            catch { }
            //return null;
            if (this.Document.Url.ToString().Length > 8 + this.Document.Url.Host.Length)
            {
                return this.Document.Url.ToString().Remove(8 + this.Document.Url.Host.Length) + "favicon.ico";
            }
            else
            {
                return this.Document.Url.ToString() + "favicon.ico";
            }
        }

关于浏览器的扩展就写到这里了,谢谢关注!








评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值