MiNiWeb瀏覽器核心技術(shù)詳解
目錄
1、 簡介
2、 目標(biāo)、挑戰(zhàn)和解決方案
捕捉腳本錯(cuò)誤
過濾彈出窗口
添加多標(biāo)簽頁或多窗口瀏覽功能
當(dāng)一個(gè)窗口是由腳本關(guān)閉時(shí),需要確認(rèn)
3、 創(chuàng)建webbrowser擴(kuò)展控件
實(shí)現(xiàn) iwebbrowser2接口
實(shí)現(xiàn) dwebbrowserevents2接口
4、 使用該擴(kuò)展控件
捕捉腳本錯(cuò)誤
過濾彈出窗口并添加多標(biāo)簽頁或多窗口瀏覽功能
使用退出事件
1、簡介
在.net 2.0的system.windows.forms命名空間中新增了webbrowser控件,該控件本身是非常有用的,但是它沒有提供在某些情況下需要的事件。這篇文章描述了如何擴(kuò)展webbrowser控件并增加一些功能,例如:屏蔽彈出窗口、捕捉腳本錯(cuò)誤以及捕捉新窗口并將其顯示在多標(biāo)簽瀏覽窗口環(huán)境中。
在擴(kuò)展webbrowser控件時(shí),某些功能沒有寫入.net framework的幫助文件,不用理會(huì)“這個(gè)方法是用于支持.net基礎(chǔ)架構(gòu)的,不推薦直接用于您的代碼中”的提示信息,我們可以創(chuàng)建一個(gè)實(shí)現(xiàn)iwebbrowser2接口的對(duì)象,并使用瀏覽器對(duì)象的全部功能,此外,使用dwebbrowserevents2接口可以向控件中添加事件。
我們假設(shè)你已經(jīng)了解了iwebbrowser2接口和dwebbrowserevents2接口,對(duì)com的互操作和相關(guān)的接口知識(shí)也是需要了解的。
2、目標(biāo)、挑戰(zhàn)和解決方案
這個(gè)組件要實(shí)現(xiàn)的目標(biāo)是:
用簡潔的方式捕捉腳本錯(cuò)誤
過濾彈出窗口
加入多標(biāo)簽頁瀏覽或多窗口瀏覽功能
當(dāng)窗口被腳本關(guān)閉時(shí)需要確認(rèn)
這一節(jié)簡要講解實(shí)現(xiàn)這些目標(biāo)所碰到的問題和相關(guān)的解決方案,下一節(jié)中會(huì)給出更多的代碼細(xì)節(jié)。
捕捉腳本錯(cuò)誤
webbrowser控件有一個(gè)scripterrorsuppressed屬性,將這個(gè)屬性設(shè)置為true時(shí),該控件確實(shí)會(huì)比原來多做了一點(diǎn)事情,它不僅禁用了腳本出錯(cuò)的對(duì)話框,而且還禁用了登陸到需要用戶證書的安全站點(diǎn)時(shí)出現(xiàn)的登陸對(duì)話框。但是假如我們?nèi)匀恍枰@個(gè)功能,或者我們想獲得腳本出錯(cuò)的通知,或者我們想知道全部的腳本出錯(cuò)的細(xì)節(jié)時(shí)該怎么辦呢?
腳本錯(cuò)誤可以在htmlwindow.error事件中捕捉,這個(gè)事件會(huì)在腳本發(fā)生錯(cuò)誤時(shí)觸發(fā)并包含全部的錯(cuò)誤細(xì)節(jié)信息,但是難點(diǎn)在于htmlwindow是需要通過htmldocument對(duì)象才能訪問,而該對(duì)象并不是什么時(shí)候都有效,htmldocument對(duì)象只在navigated事件觸發(fā)時(shí)才有效,而假如用戶是按f5鍵刷新瀏覽器時(shí)呢,抱歉,navigated事件是不會(huì)觸發(fā)的。在經(jīng)過了很多的嘗試后,我發(fā)現(xiàn)唯一可行的方法是使用并不是默認(rèn)webbrowser控件一部分的downloadcomplete事件。
解決方案:
1. 實(shí)現(xiàn)dwebbrowserevents接口
2. 創(chuàng)建一個(gè)downloadcomplete事件
3. 當(dāng)downloadcomplete事件觸發(fā)時(shí),訂閱htmlwindow.error事件
4. 利用這個(gè)error事件來獲得腳本出錯(cuò)的具體信息
5. 設(shè)置handled屬性為true來阻止腳本出錯(cuò)
過濾彈出窗口
彈出窗口大部分情況都是不怎么受歡迎的或者是不適宜的,屏蔽這些彈出窗口需要一些額外的信息。當(dāng)用戶使用windows xp sp2或者windows 2003 sp1 或更高版本時(shí),newwindow3事件可以提供這些輔助信息,假如這個(gè)事件沒有觸發(fā),那么newwindows2事件會(huì)替代該事件。當(dāng)newwindow3事件觸發(fā)時(shí),你可以檢查以下內(nèi)容:
l 是否是用戶的操作才導(dǎo)致了新開窗口
l 用戶是否按住了覆蓋鍵(ctrl 鍵)
l 是否因?yàn)楫?dāng)一個(gè)窗口正在關(guān)閉才導(dǎo)致顯示彈出窗口
l 獲得將要打開窗口的url地址
l 更多...
使用newwindows3事件可以很明顯的實(shí)現(xiàn)這個(gè)目的,假如要使用這個(gè)事件,就必須實(shí)現(xiàn)dwebbrowserevents2接口。
解決方案:
1. 實(shí)現(xiàn)dwebbrowserevents2接口
2. 創(chuàng)建一個(gè)新的事件和一個(gè)新的事件參數(shù)類
3. 執(zhí)行這個(gè)事件并附帶適當(dāng)?shù)男畔?/span>
4. 當(dāng)這個(gè)事件觸發(fā)后,檢查這次的導(dǎo)航是否需要取消
添加多標(biāo)簽頁或多窗口瀏覽功能
多標(biāo)簽頁方式瀏覽在目前似乎變得越來越流行,例如在ie7中,這就是一個(gè)新增功能。實(shí)現(xiàn)多標(biāo)簽頁方式瀏覽的難點(diǎn)是,你需要在當(dāng)腳本或者超鏈接創(chuàng)建一個(gè)新窗口的時(shí)候去創(chuàng)建相應(yīng)的新的標(biāo)簽頁或子窗口,除此之外,還需要解析出多窗口或者多標(biāo)簽頁的窗口名稱。(例如:<a href=”http://somesite” target=”somewindowname”/>)要實(shí)現(xiàn)這一點(diǎn),一些自動(dòng)化對(duì)象(如:newwindowx事件中的ppdisp和iwebbrowser2接口中的application)就需要從新開窗口傳回到該事件中。而訪問application屬性需要獲得iwebbrowser2接口的引用。
解決方案:
1. 重載attachinterfaces和detachinterfaces接口
2. 保存iwebbrowser2接口對(duì)象的引用
3. 創(chuàng)建一個(gè)application屬性來暴露該接口中的application屬性
4. 實(shí)現(xiàn)dwebbrowserevent2接口
5. 監(jiān)聽newwindows2和/或newwindow3事件
6. 當(dāng)一個(gè)事件觸發(fā)時(shí),創(chuàng)建一個(gè)新的browser控件的實(shí)例
7. 將ppdisp事件參數(shù)指派給新實(shí)例的application屬性
當(dāng)一個(gè)窗口被腳本關(guān)閉時(shí)需要確認(rèn)
當(dāng)你在jscript中調(diào)用window.close()方法,webbrowser控件很可能出現(xiàn)假死。因?yàn)槟撤N原因,他不能用于導(dǎo)航頁面,也不能做其他任何事情。假如我們知道它什么時(shí)候發(fā)生可能會(huì)好一些。當(dāng)它發(fā)生時(shí)會(huì)觸發(fā)一系列的事件,但是這些事件沒有給我們需要的信息。重載wndproc方法并檢測父窗口是否通知該瀏覽器已經(jīng)被銷毀是唯一可行的解決方法(假如誰知道如何得到windowsclosing事件來實(shí)現(xiàn)這一點(diǎn)是更好的方法)
解決方案:
1. 重載wndproc方法
2. 檢查wm_parentnotify消息
3. 檢查wm_destriy參數(shù)
4. 假如檢測到了上述的內(nèi)容,則觸發(fā)一個(gè)新的事件(這個(gè)事件在例子中稱為quit)
3、創(chuàng)建webbrowser擴(kuò)展組件
從上一節(jié)中,我們可以發(fā)現(xiàn)上述的所有內(nèi)容都基本可以歸結(jié)為兩件事:
1. 實(shí)現(xiàn)一個(gè)iwebbrowser2類型的對(duì)象,從中獲得application屬性
2. 實(shí)現(xiàn)dwebbrowserevents2接口來觸發(fā)事件
實(shí)現(xiàn)iwebbrowser2接口
using System;
using System.Security;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Security.Permissions;
namespace MiniBrowser
{
///<summary>
/// An extended version of the <see cref="WebBrowser"/> control.
///</summary>
class ExtendedWebBrowser : System.Windows.Forms.WebBrowser
{
private UnsafeNativeMethods.IWebBrowser2 axIWebBrowser2;
///<summary>
/// This method supports the .NET Framework infrastructure and is not intended to be used directly from your code.
/// Called by the control when the underlying ActiveX control is created.
///</summary>
///<param name="nativeActiveXObject"></param>
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void AttachInterfaces(object nativeActiveXObject)
{
this.axIWebBrowser2 = (UnsafeNativeMethods.IWebBrowser2)nativeActiveXObject;
base.AttachInterfaces(nativeActiveXObject);
}
///<summary>
/// This method supports the .NET Framework infrastructure and is not intended to be used directly from your code.
/// Called by the control when the underlying ActiveX control is discarded.
///</summary>
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void DetachInterfaces()
{
this.axIWebBrowser2 = null;
base.DetachInterfaces();
}
///<summary>
/// Returns the automation object for the web browser
///</summary>
public object Application
{
get { return axIWebBrowser2.Application; }
}
System.Windows.Forms.AxHost.ConnectionPointCookie cookie;
WebBrowserExtendedEvents events;
///<summary>
/// This method will be called to give you a chance to create your own event sink
///</summary>
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void CreateSink()
{
// Make sure to call the base class or the normal events won't fire
base.CreateSink();
events = new WebBrowserExtendedEvents(this);
cookie = new AxHost.ConnectionPointCookie(this.ActiveXInstance, events, typeof(UnsafeNativeMethods.DWebBrowserEvents2));
}
///<summary>
/// Detaches the event sink
///</summary>
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void DetachSink()
{
if (null != cookie)
{
cookie.Disconnect();
cookie = null;
}
}
///<summary>
/// Fires when downloading of a document begins
///</summary>
public event EventHandler Downloading;
///<summary>
/// Raises the <see cref="Downloading"/> event
///</summary>
///<param name="e">Empty <see cref="EventArgs"/></param>
///<remarks>
/// You could start an animation or a notification that downloading is starting
///</remarks>
protected void OnDownloading(EventArgs e)
{
if (Downloading != null)
Downloading(this, e);
}
///<summary>
/// Fires when downloading is completed
///</summary>
///<remarks>
/// Here you could start monitoring for script errors.
///</remarks>
public event EventHandler DownloadComplete;
///<summary>
/// Raises the <see cref="DownloadComplete"/> event
///</summary>
///<param name="e">Empty <see cref="EventArgs"/></param>
protected virtual void OnDownloadComplete(EventArgs e)
{
if (DownloadComplete != null)
DownloadComplete(this, e);
}
……
}
}
webbrowser控件有兩個(gè)尚未公開的接口:attachinterfaces()和detachinterfaces()。這些方法用于獲得iwebbrowser2接口的引用。
下一步,我們可以添加application屬性。
///<summary>
/// Returns the automation object for the web browser
///</summary>
public object Application
{
get { return axIWebBrowser2.Application; }
}
這個(gè)屬性可以用來創(chuàng)建一個(gè)新窗口,并且當(dāng)創(chuàng)建新窗口事件觸發(fā)時(shí)將瀏覽器重定向到這個(gè)新窗口。
實(shí)現(xiàn)dwebbrowserevents2接口
在這個(gè)例子中實(shí)現(xiàn)了下列事件:
l newwindow2和newwindow3(用于屏蔽彈出窗口和創(chuàng)建新窗口)
l downloadbegin和downloadcomplete(用于捕捉腳本錯(cuò)誤)
l beforenavigate2(用于在導(dǎo)航到一個(gè)頁面前查看即將導(dǎo)航到的地址)
為了簡潔的實(shí)現(xiàn)dwebbrowserevents接口,最好的方法是在組件中建立一個(gè)私有的嵌入類。這樣,所有需要的事件都在一個(gè)地方并且輕易查找。當(dāng)我們實(shí)例化這個(gè)類的時(shí)候,我們可以給調(diào)用者提供一個(gè)引用,利用該引用可以調(diào)用方法來觸發(fā)我們需要的事件。
在組件的構(gòu)造過程中并沒有附帶這些事件,而是稍微晚一點(diǎn)。這里有兩個(gè)方法來實(shí)現(xiàn)它并且它們是可以重載的。它們是createsink()和detachsink()。當(dāng)我們將這些都添加完以后,我們的代碼會(huì)像下面這樣(注重有些代碼為了閱讀方便而刪掉了)
/// <summary>
/// An extended version of the <see cref="WebBrowser"/> control.
/// </summary>
public class extendedwebbrowser : system.windows.forms.webbrowser
{
// (MORE CODE HERE)
SYSTEM.WINDOWS.FORMS.AXHOST.CONNECTIONPOINTCOOKIE COOKIE;
WEBBROWSEREXTENDEDEVENTS EVENTS;
/// <SUMMARY>
/// THIS METHOD WILL BE CALLED TO GIVE
/// YOU A CHANCE TO CREATE YOUR OWN EVENT SINK
/// </SUMMARY>
[PERMISSIONSET(SECURITYACTION.LINKDEMAND, NAME = "FULLTRUST")]
PROTECTED OVERRIDE VOID CREATESINK()
{
// MAKE SURE TO CALL THE BASE CLASS OR THE NORMAL EVENTS WON'T FIRE
BASE.CREATESINK();
EVENTS = NEW WEBBROWSEREXTENDEDEVENTS(THIS);
COOKIE = NEW AXHOST.CONNECTIONPOINTCOOKIE(THIS.ACTIVEXINSTANCE,
EVENTS, TYPEOF(UNSAFENATIVEMETHODS.DWEBBROWSEREVENTS2));
}
/// <SUMMARY>
/// DETACHES THE EVENT SINK
/// </SUMMARY>
[PERMISSIONSET(SECURITYACTION.LINKDEMAND, NAME = "FULLTRUST")]
PROTECTED OVERRIDE VOID DETACHSINK()
{
IF (NULL != COOKIE)
{
COOKIE.DISCONNECT();
COOKIE = NULL;
}
}
/// <SUMMARY>
/// FIRES WHEN DOWNLOADING OF A DOCUMENT BEGINS
/// </SUMMARY>
PUBLIC EVENT EVENTHANDLER DOWNLOADING;
/// <SUMMARY>
/// RAISES THE <SEE CREF="DOWNLOADING"/> EVENT
/// </SUMMARY>
/// <PARAM NAME="E">EMPTY <SEE CREF="EVENTARGS"/></PARAM>
/// <REMARKS>
/// YOU COULD START AN ANIMATION
/// OR A NOTIFICATION THAT DOWNLOADING IS STARTING
/// </REMARKS>
PROTECTED VOID ONDOWNLOADING(EVENTARGS E)
{
IF (DOWNLOADING != NULL)
DOWNLOADING(THIS, E);
}
// (MORE CODE HERE)
THE IMPLEMENTATION OF DWEBBROWSEREVENTS2 FOR FIRING EXTRA EVENTS
}
4、使用這個(gè)組件
上一節(jié),我們創(chuàng)建了一個(gè)新的組件?,F(xiàn)在,我們來使用這些新的事件并盡可能多的挖掘?yàn)g覽器的功能。針對(duì)每一個(gè)目標(biāo),具體的解釋如下:
捕捉腳本錯(cuò)誤
在示例程序中,有一個(gè)工具窗口簡單的顯示了發(fā)生錯(cuò)誤的列表并附帶了錯(cuò)誤的具體內(nèi)容。一個(gè)單一實(shí)例類把握了腳本錯(cuò)誤的信息并且當(dāng)這個(gè)信息發(fā)生改變時(shí)通知所有訂閱者,為了捕捉這些腳本錯(cuò)誤,browsercontrol首先附加到downloadcomplete事件,其次它訂閱了htmlwindow.error事件。當(dāng)這個(gè)事件觸發(fā)時(shí),我們注冊(cè)這個(gè)腳本錯(cuò)誤并設(shè)置handled屬性為true。
public partial class browsercontrol : usercontrol
{
public browsercontrol()
{
initializecomponent();
_browser = new extendedwebbrowser();
_browser.dock = dockstyle.fill;
// here's the new downloadcomplete event
_browser.downloadcomplete +=
new eventhandler(_browser_downloadcomplete);
// some more code here
this.containerpanel.controls.add(_browser);
// some more code here
}
void _browser_downloadcomplete(object sender, eventargs e)
{
// check wheter the document is available (it should be)
if (this.webbrowser.document != null)
// subscribe to the error event
this.webbrowser.document.window.error +=
new htmlelementerroreventhandler(window_error);
}
void window_error(object sender, htmlelementerroreventargs e)
{
// we got a script error, record it
scripterrormanager.instance.registerscripterror(e.url,
e.description, e.linenumber);
// let the browser know we handled this error.
e.handled = true;
}
// some more code here
}
過濾彈出窗口,并且增加多標(biāo)簽頁或多窗口瀏覽功能
捕捉彈出窗口必須可以由用戶來進(jìn)行配置。為了示范的目的,我實(shí)現(xiàn)了四個(gè)級(jí)別,從不屏蔽任何窗口到屏蔽所有新窗口。下面的代碼是browsercontorl的一部分,用來展現(xiàn)如何實(shí)現(xiàn)這一點(diǎn)。當(dāng)一個(gè)新建窗口被答應(yīng)后,示例程序展現(xiàn)了如何讓新建窗口實(shí)現(xiàn)窗口名稱的解決方案。
void _browser_startnewwindow(object sender,
browserextendednavigatingeventargs e)
{
// here we do the pop-up blocker work
// note that in windows 2000 or lower this event will fire, but the
// event arguments will not contain any useful information
// for blocking pop-ups.
// there are 4 filter levels.
// none: allow all pop-ups
// low: allow pop-ups from secure sites
// medium: block most pop-ups
// high: block all pop-ups (use ctrl to override)
// we need the instance of the main form,
// because this holds the instance
// to the windowmanager.
mainform mf = getmainformfromcontrol(sender as control);
if (mf == null)
return;
// allow a popup when there is no information
// available or when the ctrl key is pressed
bool allowpopup = (e.navigationcontext == urlcontext.none)
|| ((e.navigationcontext &
urlcontext.overridekey) == urlcontext.overridekey);
if (!allowpopup)
{
// give none, low & medium still a chance.
switch (settingshelper.current.filterlevel)
{
case popupblockerfilterlevel.none:
allowpopup = true;
break;
case popupblockerfilterlevel.low:
// see if this is a secure site
if (this.webbrowser.encryptionlevel !=
webbrowserencryptionlevel.insecure)
allowpopup = true;
else
// not a secure site, handle this like the medium filter
goto case popupblockerfilterlevel.medium;
break;
case popupblockerfilterlevel.medium:
// this is the most dificult one.
// only when the user first inited
// and the new window is user inited
if ((e.navigationcontext & urlcontext.userfirstinited)
== urlcontext.userfirstinited &&
(e.navigationcontext & urlcontext.userinited)
== urlcontext.userinited)
allowpopup = true;
break;
}
}
if (allowpopup)
{
// check wheter it's a html dialog box.
// if so, allow the popup but do not open a new tab
if (!((e.navigationcontext &
urlcontext.htmldialog) == urlcontext.htmldialog))
{
extendedwebbrowser ewb = mf.windowmanager.new(false);
// the (in)famous application object
e.automationobject = ewb.application;
}
}
else
// here you could notify the user that the pop-up was blocked
e.cancel = true;
}
這個(gè)事件稱為startnewwindow的原因是編碼設(shè)計(jì)規(guī)則不答應(yīng)一個(gè)事件的名稱以“before”或者“after”開頭?!?/span>newwindowing”事件并沒有在這一范圍內(nèi)。
使用quit事件
當(dāng)quit事件觸發(fā)時(shí),我們只需要找到正確的窗口或者標(biāo)簽頁將其關(guān)閉,并且”dispose”這個(gè)示例即可。
作者:
RDIF
出處:
http://www.rzrgm.cn/huyong/
Email:
406590790@qq.com
QQ:
406590790
微信:
13005007127(同手機(jī)號(hào))
框架官網(wǎng):
http://www.guosisoft.com/
http://www.rdiframework.net/
框架其他博客:
http://blog.csdn.net/chinahuyong
http://www.rzrgm.cn/huyong
國思RDIF開發(fā)框架
,
給用戶和開發(fā)者最佳的.Net框架平臺(tái)方案,為企業(yè)快速構(gòu)建跨平臺(tái)、企業(yè)級(jí)的應(yīng)用提供強(qiáng)大支持。
關(guān)于作者:系統(tǒng)架構(gòu)師、信息系統(tǒng)項(xiàng)目管理師、DBA。專注于微軟平臺(tái)項(xiàng)目架構(gòu)、管理和企業(yè)解決方案,多年項(xiàng)目開發(fā)與管理經(jīng)驗(yàn),曾多次組織并開發(fā)多個(gè)大型項(xiàng)目,在面向?qū)ο?、面向服?wù)以及數(shù)據(jù)庫領(lǐng)域有一定的造詣?,F(xiàn)主要從事基于
RDIF
框架的技術(shù)開發(fā)、咨詢工作,主要服務(wù)于金融、醫(yī)療衛(wèi)生、鐵路、電信、物流、物聯(lián)網(wǎng)、制造、零售等行業(yè)。
如有問題或建議,請(qǐng)多多賜教!
本文版權(quán)歸作者和CNBLOGS博客共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,如有問題,可以通過微信、郵箱、QQ等聯(lián)系我,非常感謝。

浙公網(wǎng)安備 33010602011771號(hào)