1、主要用途:使用戶可以在窗體中導航網頁。
2、注意:WebBrowser 控件會占用大量資源。使用完該控件後一定要調用 Dispose 方法,以便確保及時釋放所有資源。必須在附加事件的同一線程上調用 Dispose 方法,該線程應始終是消息或用戶界面 (UI) 線程。
3、WebBrowser 使用下面的成員可以將控件導航到特定 URL、在導航歷史記錄列表中向後和向前移動,還可以加載當前用戶的主頁和搜索頁:
1.URL屬性:可讀、可寫,用於獲取或設置當前文檔的 URL。
WebBrowser 控件維護浏覽會話期間訪問的所有網頁的歷史記錄列表。設置Url屬性時,WebBrowser 控件導航到指定的 URL 並將該 URL 添加到歷史記錄列表的末尾。
WebBrowser 控件在本地硬盤的緩存中存儲最近訪問過的站點的網頁。每個頁面都可以指定一個到期日期,指示頁面在緩存中保留的時間。當控件定位到某頁時,如果該頁具有緩存的版本,則直接顯示緩存中的內容而不必重新下載該頁,從而節省了時間。使用 Refresh 方法強制 WebBrowser控件通過下載來重新加載當前頁,從而確保控件顯示最新版本。
注意:即使已請求了另一個文檔,該屬性也包含當前文檔的 URL。如果設置該屬性的值,然後立即再次檢索該值,要是 WebBrowser 控件尚未來得及加載新文檔,則檢索到的值可能與設置的值不同。
2.Navigate方法: 將指定位置的文檔加載到 WebBrowser 控件中。
3.GoBack方法:如果導航歷史記錄中的上一頁可用,則將 WebBrowser 控件導航到該頁。
如果導航成功,則返回true;如果導航歷史記錄中的上一頁不可用,則返回false。
WebBrowser 控件維護浏覽會話期間訪問的所有網頁的歷史記錄列表。可以使用GoForward方法實現一個“後退”按鈕。
使用 CanGoBack 屬性確定導航歷史記錄是否可用以及是否包含上一頁。處理 CanGoBackChanged 事件,在 CanGoBack 屬性值更改時接收通知。
4.GoForward方法:如果導航歷史記錄中的下一頁可用,則將 WebBrowser 控件導航到該頁。
如果導航成功,則返回true;如果導航歷史記錄中的下一頁不可用,則返回false。
WebBrowser 控件維護浏覽會話期間訪問的所有網頁的歷史記錄列表。可以使用 GoForward 方法實現一個“前進”按鈕.
使用 CanGoForward 屬性確定導航歷史記錄是否可用以及是否包含當前頁之後的頁。處理 CanGoForwardChanged 事件,在 CanGoForward 屬性值更改時接收通知
5.GoHome方法:將 WebBrowser 控件導航到當前用戶的主頁。
6.GoSearch方法:將 WebBrowser 控件導航到當前用戶的默認搜索頁。
默認搜索頁存儲在注冊表的 HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\Search Page 注冊表項下。
若要使用其他搜索頁而不是默認搜索頁,請調用 Navigate 方法或指定 Url 屬性。
7.Refresh方法:重新加載當前顯示在 WebBrowser 控件中的文檔。
8.Stop方法:取消所有掛起的導航並停止所有動態頁元素(如背景聲音和動畫)。
如果導航不成功,則顯示一頁指示出現的問題。使用這些成員中的任何一個進行導航都會導致在導航的不同階段發生 Navigating、Navigated 和DocumentCompleted 事件。
4、ObjectForScripting 屬性:獲取或設置一個對象,該對象可由顯示在 WebBrowser 控件中的網頁所包含的腳本代碼訪問。
使用該屬性啟用 WebBrowser 控件承載的網頁與包含 WebBrowser 控件的應用程序之間的通信。使用該屬性可以將動態 HTML (DHTML) 代碼與客戶端應用程序代碼集成在一起。為該屬性指定的對象可作為 window.external 對象(用於主機訪問的內置 DOM 對象)用於網頁腳本。
可以將此屬性設置為希望其公共屬性和方法可用於腳本代碼的任何 COM 可見的對象。可以通過使用 ComVisibleAttribute 對類進行標記使其成為 COM 可見的類。
若要從客戶端應用程序代碼調用網頁中定義的函數,請使用可從 Document 屬性檢索的 HtmlDocument 對象的 HtmlDocument.InvokeScript 方法。
5、AllowNavigation屬性:獲取或設置一個值,該值指示控件在加載其初始頁之後是否可以導航到其他頁。
6、AllowWebBrowserDrop屬性:獲取或設置一個值,該值指示 WebBrowser 控件是否導航到拖放到它上面的文檔。
7、WebBrowserShortcutsEnabled屬性:是否啟用WebBrowser自帶的快捷鍵。
8、ScriptErrorsSuppressed 屬性:獲取或設置一個值,該值指示出現腳本錯誤時,WebBrowser 控件是否顯示錯誤對話框。
9、IsWebBrowserContextMenuEnabled屬性:是否啟用右鍵菜單。
源:MSDN http://msdn.microsoft.com/zh-cn/library/system.windows.forms.webbrowser(v=vs.80).aspx
C#WinForm WebBrowser (二) 實用方法總結
實用方法1:獲取狀態欄信息
void webBrowser1_StatusTextChanged(object sender, EventArgs e) { label1.Text = webBrowser1.StatusText; }
實用方法2:頁面跳轉後改變地址欄地址
//在Navigated事件處理函數中改變地址欄地址是最恰當的: private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e) { textBox1.Text = webBrowser1.Url.ToString(); }
實用方法3:設置單選框
//建議使用執行單擊事件的方式來設置單選框,而不是修改屬性: webBrowser1.Document.GetElementById("RBT_A").InvokeMember("click");
實用方法4:設置聯動型下拉列表
//比較常見的聯動型多級下拉列表就是省/市縣選擇了,這種情況下直接設置選擇項的屬性不會觸發聯動,需要在最後執行觸發事件函數才能正常工作:
foreach (HtmlElement f in s.GetElementsByTagName("option")) { if (f.InnerText == "北京") { f.SetAttribute("selected", "selected"); } else { f.SetAttribute("selected", ""); } } s.RaiseEvent("onchange");
以上四種方法轉於:http://www.cnblogs.com/SkyD/archive/2009/04/23/1441696.html
實用方法一:在WinForm中相應Web事件 假設HTML源代碼如下:
<html> <body> <input type="button" id="btnClose" value="關閉" /> </body> </html>
HtmlDocument htmlDoc = webBrowser.Document; HtmlElement btnElement = htmlDoc.All["btnClose"]; if (btnElement != null) { btnElement.click += new HtmlElementEventHandler(HtmlBtnClose_Click); }
//很簡單吧?那麼稍稍高級一點的——我們都知道一個HTML元素可能有很多各種各樣的事件,而HtmlElement這個類只給出最常用、共通的幾個。那麼,如何響應其他事件呢?這也很簡單,只需要調用HtmlElement的AttachEventHandler就可以了:
btnElement.AttachEventHandler("onclick", new EventHandler(HtmlBtnClose_Click)); //這一句等價於上面的btnElement.click += new HtmlElementEventHandler(HtmlBtnClose_Click);
對於其他事件,把"onclick"換成該事件的名字就可以了。例如:
formElement.AttachEventHandler("onsubmit", new EventHandler(HtmlForm_Submit));
實用方法二:模擬表單自動填寫和提交
假設有一個最簡單的登錄頁面,輸入用戶名密碼,點“登錄”按鈕即可登錄。已知用戶名輸入框的id(或Name,下同)是username,密碼輸入框的id是password,“登錄”按鈕的id是submitbutton,那麼我們只需要在webBrowser的DocumentCompleted事件中使用下面的代碼即可:
HtmlElement btnSubmit = webBrowser.Document.All["submitbutton"]; HtmlElement tbUserid = webBrowser.Document.All["username"]; HtmlElement tbPasswd = webBrowser.Document.All["password"];
if (tbUserid == null || tbPasswd == null || btnSubmit == null) return;
tbUserid.SetAttribute("value", "smalldust"); tbPasswd.SetAttribute("value", "12345678");
btnSubmit.InvokeMember("click");
關於表單的提交,的確還有另一種方法就是獲取form元素而不是button,並用form元素的submit方法:
HtmlElement formLogin = webBrowser.Document.Forms["loginForm"]; //…… formLogin.InvokeMember("submit");
本文之所以沒有推薦這種方法,是因為現在的網頁,很多都在submit按鈕上添加onclick事件,以對提交的內容做最基本的驗證。如果直接使用form的submit方法,這些驗證代碼就得不到執行,有可能會引起錯誤。
實用方法三:調用腳本
首先是調用Web頁面的腳本中已經定義好的函數。假設HTML中有如下Javascript:
function DoAdd(a, b) { return a + b; }
那麼,我們要在WinForm調用它,只需如下代碼即可:
object oSum = webBrowser.Document.InvokeScript("DoAdd", new object[] { 1, 2 }); int sum = Convert.ToInt32(oSum);
其次,如果我們想執行一段Web頁面中原本沒有的腳本,該怎麼做呢?這次.Net的類沒有提供,看來還要依靠COM了。IHTMLWindow2可以將任意的字符串作為腳本代碼來執行。
string scriptline01 = @"function ShowPageInfo() {"; string scriptline02 = @" var numLinks = document.links.length; "; string scriptline03 = @" var numForms = document.forms.length; "; string scriptline04 = @" var numImages = document.images.length; "; string scriptline05 = @" var numScripts = document.scripts.length; "; string scriptline06 = @" alert('網頁的統計結果:\r\n鏈接數:' + numLinks + "; string scriptline07 = @" '\r\n表單數:' + numForms + "; string scriptline08 = @" '\r\n圖像數:' + numImages + "; string scriptline09 = @" '\r\n腳本數:' + numScripts);}"; string scriptline10 = @"ShowPageInfo();";
string strScript = scriptline01 + scriptline02 + scriptline03 + scriptline04 + scriptline05 + scriptline06 + scriptline07 + scriptline08 + scriptline09 + scriptline10;
IHTMLWindow2 win = (IHTMLWindow2)webBrowser.Document.Window.DomWindow; win.execScript(strScript, "Javascript");
以上三種實用方法轉於:http://www.cnblogs.com/smalldust/archive/2006/03/08/345561.html
最後:在腳本中調用WinForm裡的代碼,這個可能嗎? 呵呵,當然是可能的。 下面的代碼示例演示如何使用 ObjectForScripting 屬性。在該示例中,ObjectForScripting 屬性被設置為當前窗體。
view sourceprint?
using System;
using System.Windows.Forms;
using System.Security.Permissions;
[PermissionSet(SecurityAction.Demand, Name="FullTrust")]
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public class Form1 : Form
{
private WebBrowser webBrowser1 = new WebBrowser();
private Button button1 = new Button();
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
public Form1()
{
button1.Text = "call script code from client code";
button1.Dock = DockStyle.Top;
button1.Click += new EventHandler(button1_Click);
webBrowser1.Dock = DockStyle.Fill;
Controls.Add(webBrowser1);
Controls.Add(button1);
Load += new EventHandler(Form1_Load);
}
private void Form1_Load(object sender, EventArgs e)
{
webBrowser1.AllowWebBrowserDrop = false;
webBrowser1.IsWebBrowserContextMenuEnabled = false;
webBrowser1.WebBrowserShortcutsEnabled = false;
webBrowser1.ObjectForScripting = this;
// Uncomment the following line when you are finished debugging.
//webBrowser1.ScriptErrorsSuppressed = true;
webBrowser1.DocumentText =
"<html><head><script>" +
C# WinForm WebBrowser (三) 編輯模式
一、啟用編輯模式、 浏覽模式 及 自動換行
/// <summary> /// 編輯模式 /// </summary> public void EditMode() { if (this.webBrowser.Document != null) { mshtml.IHTMLDocument2 doc = this.webBrowser.Document.DomDocument as mshtml.IHTMLDocument2; if (doc != null) { doc.designMode = "on"; } } }
/// <summary> /// 啟用浏覽模式 /// </summary> public void BrowseMode() { if (this.webBrowser.Document != null) { mshtml.IHTMLDocument2 doc = this.webBrowser.Document.DomDocument as mshtml.IHTMLDocument2; if (doc != null) { doc.designMode = "off"; } } }
/// <summary> /// 設置自動換行 /// </summary> /// <param name="value"></param> public void SetAutoWrap(bool value) { mshtml.HTMLDocument doc = this.webBrowser.Document.DomDocument as mshtml.HTMLDocument; if (doc != null) { mshtml.HTMLBody body = doc.body as mshtml.HTMLBody; if (body != null) { body.noWrap = !value; } } }
在編輯模式下,可以使用:
this.webBrowser.Document.ExecCommand([string],[bool],[object]);
方法來操作WebBrowser中的HTML。 其中第一個字符串類型的參數為:要執行的命令的名稱 。 第二個布爾類型的參數為: 是否向用戶顯示命令特定的對話框或消息框。
第三個Object類型的參數為:要使用該命令分配的值。並非適用於所有命令。
常見的命令有:
private const string HTML_COMMAND_BOLD = "Bold"; //加粗 private const string HTML_COMMAND_UNDERLINE = "Underline"; //下劃線 private const string HTML_COMMAND_ITALIC = "Italic"; //斜體 private const string HTML_COMMAND_SUBSCRIPT = "Subscript"; //下標 private const string HTML_COMMAND_SUPERSCRIPT = "Superscript"; //上標 private const string HTML_COMMAND_STRIKE_THROUGH = "StrikeThrough"; //刪除線 private const string HTML_COMMAND_FONT_NAME = "FontName"; //字體 private const string HTML_COMMAND_FONT_SIZE = "FontSize"; //字號 private const string HTML_COMMAND_FORE_COLOR = "ForeColor"; //字體前景色 private const string HTML_COMMAND_BACK_COLOR = "BackColor"; //字體背景色 private const string HTML_COMMAND_INSERT_FORMAT_BLOCK = "FormatBlock"; //加粗 private const string HTML_COMMAND_REMOVE_FORMAT = "RemoveFormat"; //清楚樣式 private const string HTML_COMMAND_JUSTIFY_LEFT = "JustifyLeft"; //文本左對齊 private const string HTML_COMMAND_JUSTIFY_CENTER = "JustifyCenter"; //文本中間對齊 private const string HTML_COMMAND_JUSTIFY_RIGHT = "JustifyRight"; //文本右對齊 private const string HTML_COMMAND_JUSTIFY_FULL = "JustifyFull"; //文本兩端對齊 private const string HTML_COMMAND_INDENT = "Indent"; //增大縮進量 private const string HTML_COMMAND_OUTDENT = "Outdent"; //減小縮進量 private const string HTML_COMMAND_INSERT_LINE = "InsertHorizontalRule";//插入分割符 private const string HTML_COMMAND_INSERT_LIST = "Insert{0}List"; // replace with (Un)Ordered 插入項目符號或項目編號 private const string HTML_COMMAND_INSERT_IMAGE = "InsertImage"; //插入圖像 private const string HTML_COMMAND_INSERT_LINK = "CreateLink"; //插入鏈接 private const string HTML_COMMAND_REMOVE_LINK = "Unlink"; //移除鏈接 private const string HTML_COMMAND_TEXT_CUT = "Cut"; //剪切 private const string HTML_COMMAND_TEXT_COPY = "Copy"; //復制 private const string HTML_COMMAND_TEXT_PASTE = "Paste"; //粘貼 private const string HTML_COMMAND_TEXT_DELETE = "Delete"; //刪除 private const string HTML_COMMAND_TEXT_UNDO = "Undo"; //撤銷 private const string HTML_COMMAND_TEXT_REDO = "Redo"; //恢復 private const string HTML_COMMAND_TEXT_SELECT_ALL = "SelectAll"; //全選 private const string HTML_COMMAND_TEXT_UNSELECT = "Unselect"; //取消選擇 private const string HTML_COMMAND_TEXT_PRINT = "Print"; // 打印 private const string HTML_COMMAND_EDITMODE = "EditMode"; // 編輯模式 private const string HTML_COMMAND_BROWSEMODE = "BrowseMode"; // 浏覽模式 private const string HTML_COMMAND_OVERWRITE = "OverWrite"; //轉換插入、覆寫模式
// 更多的命令請參見:
http://msdn.microsoft.com/en-us/library/ms533049.aspx
———————————————————————————————————————————————————————————————— 由於本人在開發中經常要在程序中嵌入浏覽器,為了符合自己的需求經常要對浏覽器進行擴展和定制, 解決這些問題需在網上找資料和學習的過程,我想可能很多開發者或許會遇到同樣的問題,特寫此文,以供大家參考。 在MFC中使用浏覽器 在MFC中微軟為我們提供了CHtmlView、CDHtmlDialog類讓我們的程序很方便的嵌入浏覽器和進行浏覽器的二次開發,這比直 接使用WebBrowser控件要方便很多,所以本文中討論的浏覽器的問題都是針對CHtmlView來討論的。文中將提到一個類CLhpHtmlView, 它是CHtmlView的派生類,文中提及的擴展或定制都將在CLhpHtmlView類(或派生類)上實現。 怎樣擴展或定制浏覽器 浏覽器定義了一些擴展接口(如IDocHostUIHandler可以定制浏覽器界面有關的行為),以便開發者進行定制和擴展。浏覽 器會在需要的時候向他的控制站點查詢這些接口,在控制站點裡實現相應的接口就可以進行相應的擴展。在MFC7.01類 庫中,CHtmlView使用的控制站點是CHtmlControlSite的,在CHtmlControlSite類中 只實現了接口IDocHostUIHandler, 而要實現更多的擴展接口,必須用自定義的控制站類來取代CHtmlControlSite,在下文中提及的類CDocHostSite即為自定義 的控制站類。 關於接口的介紹請參考:
http://dev.csdn.net/develop/article/48/48483.shtm
如何使自定義的控制站點來替換默認的控制站點呢?在MFC7.0中只需重載CHtmlView的虛函數CreateControlSite即可:
BOOL CLhpHtmlView::CreateControlSite(COleControlContainer * pContainer,
COleControlSite ** ppSite, UINT /*nID*/, REFCLSID /*clsid*/)
{
*ppSite = new CDocHostSite(pContainer, this);// 創建自己的控制站點實例
return (*ppSite) ? TRUE : FALSE;
}
VC6.0要替換控制站要復雜的多,這裡就不討論了,如需要6.0版本的請給我發郵件到[email protected]。 定制鼠標右鍵彈出出菜單 要定制浏覽器的鼠標右鍵彈出菜單,必須在自定義的控制站點類中實現IDocHostUIHandler2接口,並且IE的 版本是5.5或以上。在接口IDocHostUIHandler2的ShowContextMenu方法中調用浏覽器類的OnShowContextMenu虛函數,我們 在浏覽器類的派生類重載此虛函數即可實現右鍵菜單的定制,參見代碼
HRESULT CDocHostSite::XDocHostUIHandler::ShowContextMenu(DWORD dwID,
POINT * ppt,
IUnknown * pcmdtReserved,
IDispatch * pdispReserved)
{
METHOD_PROLOGUE(CDocHostSite, DocHostUIHandler);
return pThis->m_pView->OnShowContextMenu( dwID, ppt, pcmdtReserved,pdispReserved );
}
HRESULT CLhpHtmlView::OnShowContextMenu(DWORD dwID,
LPPOINT ppt,
LPUNKNOWN pcmdtReserved,
LPDISPATCH pdispReserved)
{
HRESULT result = S_FALSE;
switch(m_ContextMenuMode)
{
case NoContextMenu:// 無菜單
result=S_OK;
break;
case DefaultMenu:// 默認菜單
break;
case TextSelectionOnly:// 僅文本選擇菜單
if(!(dwID == CONTEXT_MENU_TEXTSELECT || dwID == CONTEXT_MENU_CONTROL))
result=S_OK;
break;
case CustomMenu:// 自定義菜單
if(dwID!=CONTEXT_MENU_TEXTSELECT)
result=OnShowCustomContextMenu(ppt,pcmdtReserved,pdispReserved);
break;
}
return result;
}
在CLhpHtmlView中定義的枚舉類型CONTEXT_MENU_MODE舉出了定制右鍵彈出菜單的四種類型
enum CONTEXT_MENU_MODE// 上下文菜單
{
NoContextMenu,// 無菜單
DefaultMenu,// 默認菜單
TextSelectionOnly,// 僅文本選擇菜單
CustomMenu// 自定義菜單
};
通過CLhpHtmlView的函數SetContextMenuMode來設置右鍵菜單的類型。如果設定的右鍵彈出菜單是“自定義菜單”類型, 我們只要在CLhpHtmlView的派生類中重載OnShowCustomContextMenu虛函數即可,如下代碼 CDemoView是CLhpHtmlView的派生類
HRESULT CDemoView::OnShowCustomContextMenu(LPPOINT ppt, LPUNKNOWN pcmdtReserved,LPDISPATCH pdispReserved)
{
if ((ppt==NULL)||(pcmdtReserved==NULL)||(pcmdtReserved==NULL))
return S_OK;
HRESULT hr=0;
IOleWindow *oleWnd=NULL;
hr=pcmdtReserved->QueryInterface(IID_IOleWindow, (void**)&oleWnd);
if((hr != S_OK)||(oleWnd == NULL))
return S_OK;
HWND hwnd=NULL;
hr=oleWnd->GetWindow(&hwnd);
if((hr!=S_OK)||(hwnd==NULL))
{
oleWnd->Release();
return S_OK;
}
IHTMLElementPtrpElem=NULL;
hr = pdispReserved->QueryInterface(IID_IHTMLElement, (void**)&pElem);
if(hr != S_OK)
{
oleWnd->Release();
return S_OK;
}
IHTMLElementPtrpParentElem=NULL;
_bstr_ttagID;
BOOL go=TRUE;
pElem->get_id(&tagID.GetBSTR());
while(go && tagID.length()==0)
{
hr=pElem->get_parentElement(&pParentElem);
if(hr==S_OK && pParentElem!=NULL)
{
pElem->Release();
pElem=pParentElem;
pElem->get_id(&tagID.GetBSTR());
}
else
go=FALSE;
};
if(tagID.length()==0)
tagID="no id";
CMenu Menu,SubMenu;
Menu.CreatePopupMenu();
CString strTagID = ToStr(tagID);
if(strTagID == "red")
Menu.AppendMenu(MF_BYPOSITION, ID_RED, "您點擊的是紅色");
else if(strTagID == "green")
Menu.AppendMenu(MF_BYPOSITION, ID_GREEN, "您點擊的是綠色");
else if(strTagID == "blue")
Menu.AppendMenu(MF_BYPOSITION, ID_BLUE, "您點擊的是藍色");
else
Menu.AppendMenu(MF_BYPOSITION, ID_NONE, "你點了也白點,請在指定的地方點擊");
int MenuID=Menu.TrackPopupMenu(TPM_RETURNCMD|TPM_LEFTALIGN|TPM_RIGHTBUTTON,ppt->x, ppt->y, this);
switch(MenuID)
{
case ID_RED:
MessageBox("紅色");
break;
case ID_GREEN:
MessageBox("紅色");
break;
case ID_BLUE:
MessageBox("紅色");
break;
case ID_NONE:
MessageBox("haha");
break;
}
oleWnd->Release();
pElem->Release();
return S_OK;
}
實現腳本擴展(很重要的external接口) 在你嵌入了浏覽器的工程中,如果網頁的腳本中能調用C++代碼,那將是一件很惬意的事情,要實現這種交互,就必須實現腳本擴展。實現腳本擴展就是在程序中實現一個IDispatch接口,通過CHtmlView類的OnGetExternal虛函數返回此接口指針,這樣就可以在腳本中通過window.external.XXX(關鍵字window可以省略)來 引用接口暴露的方法或屬性(XXX為方法或屬性名)。在MFC中從CCmdTarget派生的類都可以實現自動化,而不必在MFC工程中引入繁雜的ATL。從CCmdTarget派生的類實現自動化接口的時候不要忘了在構造函數中調用EnableAutomation函數。 要使虛函數OnGetExternal發揮作用必須在 自定義的控制站點類中實現IDocHostUIHandler,在接口IDocHostUIHandler的GetExternal方法中調用浏覽器類的OnGetExternal虛函數,我們在浏覽器類的派生類重載OnGetExternal虛函數, 通過參數lppDispatch返回一個IDispatch指針,這樣腳本中引用window.external時就是引用的返回的接口,參見代碼
HRESULT CDocHostSite::XDocHostUIHandler::GetExternal(IDispatch ** ppDispatch)
{
METHOD_PROLOGUE(CDocHostSite, DocHostUIHandler);
return pThis->m_pView->OnGetExternal( ppDispatch );
}
CLhpHtmlView::CLhpHtmlView(BOOL isview)
{
......
EnableAutomation();// 允許自動化
}
HRESULT CLhpHtmlView::OnGetExternal(LPDISPATCH *lppDispatch)
{
*lppDispatch = GetIDispatch(TRUE);// 返回自身的IDispatch接口
return S_OK;
}
請注意上面代碼中,在OnGetExternal返回的是自身IDispatch接口, 這樣就不比為腳本擴展而另外寫一個從CCmdTarget派生的新類, CLhpHtmlView本身就是從CCmdTarget派生,直接在上面實現接口就是。 下用具體示例來說明怎樣實現腳本擴展 示例會在網頁上點擊一個按鈕而使整個窗口發生抖動 從CLhpHtmlView派生一個類CDemoView,在類中實現IDispatch, 並通過IDispatch暴露方法WobbleWnd
---------------------------------------------------------------------------
文件 DemoView.h
---------------------------------------------------------------------------
.......
class CDemoView : public CLhpHtmlView
{
......
DECLARE_DISPATCH_MAP() // 構建dispatch映射表以暴露方法或屬性
......
void WobbleWnd();// 抖動窗口
};
---------------------------------------------------------------------------
文件 DemoView.cpp
---------------------------------------------------------------------------
......
// 把成員函數映射到Dispatch映射表中,暴露方法給腳本
BEGIN_DISPATCH_MAP(CDemoView, CLhpHtmlView)
DISP_FUNCTION(CDemoView, "WobbleWnd", WobbleWnd, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()
......
void CDemoView::WobbleWnd()
{
// 在這裡實現抖動窗口
......
}
---------------------------------------------------------------------------
文件 Demo.htm
---------------------------------------------------------------------------
...... onclick="external.WobbleWnd()" ......
這裡我要介紹一下DISP_FUNCTION宏,它的作用是將一個函數映射到Dispatch映射表中,我們看
DISP_FUNCTION(CDemoView, "WobbleWnd", WobbleWnd, VT_EMPTY, VTS_NONE)
CDemoView是宿主類名, "WobbleWnd"是暴露給外面的名字(腳本調用時使用的名字), VT_EMPTY是返回值得類型為空,VTS_NONE說明此方法沒有參數,如果要映射的函數有返回值和參數該 如何映射,通過下面舉例來說明
DISP_FUNCTION(CCalendarView,"TestFunc",TestFunc,VT_BOOL,VTS_BSTR VTS_I4 VTS_I4)
BOOL TestFunc(LPCSTR param1, int param2, int param3)
{
.....
}
參數表VTS_BSTR VTS_I4 VTS_I4是用空格分隔,他們的類型映射請參考MSDN,這要提醒的是不要把VTS_BSTR與CString對應,而應與LPCSTR對應。 C++代碼中如何調用網頁腳本中的函數 IHTMLDocument2::scripts屬性表示HTML文檔中所有腳本對象。使用腳本對象的IDispatch接口的GetIDsOfNames方法可以得到腳本函數的 DispID,得到DispID後,使用IDispatch的Invoke函數可以調用對應的腳本函數。CLhpHtmlView提供了方便的調用JavaScript的函數,請參考CLhpHtmlView中有關鍵字“JScript”的代碼。 定制消息框的標題 我們在腳本中調用alert彈出消息框時,消息框的標題是微軟預定義的“Microsoft Internet Explorer”,如下圖: 在自定義的控制站點類中實現IDocHostShowUI接口,在接口的ShowMessage方法中調用浏覽器的OnShowMessage,我們重載 OnShowMessage虛函數即可定制消息框的標題,實現代碼如下:
// 窗口標題"Microsoft Internet Explorer"的資源標識
#define IDS_MESSAGE_BOX_TITLE 2213
HRESULT CLhpHtmlView::OnShowMessage(HWND hwnd,
LPOLESTR lpstrText,
LPOLESTR lpstrCaption,
DWORD dwType,
LPOLESTR lpstrHelpFile,
DWORD dwHelpContext,
LRESULT * plResult)
{
//載入Shdoclc.dll 和IE消息框標題字符串
HINSTANCE hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
if (hinstSHDOCLC == NULL)
return S_FALSE;
CString strBuf,strCaption(lpstrCaption);
strBuf.LoadString(hinstSHDOCLC, IDS_MESSAGE_BOX_TITLE);
// 比較IE消息框標題字符串和lpstrCaption
// 如果相同,用自定義標題替換
if(strBuf==lpstrCaption)
strCaption = m_DefaultMsgBoxTitle;
// 創建自己的消息框並且顯示
*plResult = MessageBox(CString(lpstrText), strCaption, dwType);
//卸載Shdoclc.dll並且返回
FreeLibrary(hinstSHDOCLC);
return S_OK;
}
從代碼中可以看到通過設定m_DefaultMsgBoxTitle的值來改變消息寬的標題,修改此值是同過SetDefaultMsgBoxTitle來實現
void CLhpHtmlView::SetDefaultMsgBoxTitle(CString strTitle)
{
m_DefaultMsgBoxTitle=strTitle;
}
怎樣定制、修改浏覽器向Web服務器發送的HTTP請求頭 在集成了WebBrowser控件的應用中,Web服務器有時可能希望客戶端(浏覽器)發送的HTTP請求中附帶一些額外的信息或自定義的 HTTP頭字段,這樣就必須在浏覽器中控制向Web服務器發送的HTTP請求。 下面是捕獲的一個普通的用浏覽器發送的HTTP請求頭:
GET /text7.htm HTTP/1.0
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, \
application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer: http://localhost
Accept-Language: en-us
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Poco 0.31; LHP Browser 1.01; \
.NET CLR 1.1.4322)
Host: localhost
Connection: Keep-Alive
CHtmlView的
void Navigate2(
LPCTSTR lpszURL,
DWORD dwFlags = 0,
LPCTSTR lpszTargetFrameName = NULL,
LPCTSTR lpszHeaders = NULL,
LPVOID lpvPostData = NULL,
DWORD dwPostDataLen = 0
);
函數參數lpszHeaders可以指定HTTP請求頭,示例如下:
Navigate2(_T("http://localhost"),NULL,NULL, "MyDefineField: TestValue");
我們捕獲的HTTP頭如下: 怎樣修改浏覽器標識 在HTTP請求頭中User-Agent字段表明了浏覽器的版本以及操作系統的版本等信息。WEB服務器經常需要知道用戶請求頁面時是來自IE還是來自自己的客戶端中的WebBrowser控件, 以便分開處理,而WebBrowser控件向WEB服務器發送的浏覽器標識(User-Agent字段)跟用IE發送的是一樣的,怎樣區分自己的浏覽器和IE呢? 微軟沒有提供現成的方法,要自己想法解決。 前面討論的定制HTTP請求頭就是為這一節准備的。 思路是這樣的: 在自己的浏覽器裡處理每一個U頁面請求,把請求頭User-Agent改成自己想要的。 在CHtmlView的OnBeforeNavigate2虛函數裡來修改HTTP請求是再好不過了,
#define WM_NVTO(WM_USER+1000)
class NvToParam
{
public:
CString URL;
DWORD Flags;
CString TargetFrameName;
CByteArray PostedData;
CString Headers;
};
void CDemoView::OnBeforeNavigate2(LPCTSTR lpszURL,
DWORD nFlags,
LPCTSTR lpszTargetFrameName,
CByteArray& baPostedData,
LPCTSTR lpszHeaders,
BOOL* pbCancel)
{
CString strHeaders(lpszHeaders);
if(strHeaders.Find("User-Agent:LHPBrowser 1.0") < 0)// 檢查頭裡有沒有自定義的User-Agent串
{
*pbCancel = TRUE;// 沒有,取消這次導航
if(!strHeaders.IsEmpty())
strHeaders += "\r\n";
strHeaders += "User-Agent:LHPBrowser 1.0";// 加上自定義的User-Agent串
NvToParam* pNvTo = new NvToParam;
pNvTo->URL = lpszURL;
pNvTo->Flags = nFlags;
pNvTo->TargetFrameName = lpszTargetFrameName;
baPostedData.Copy(pNvTo->PostedData);
pNvTo->Headers = strHeaders;
// 發送一個自定義的導航消息,並把參數發過去
PostMessage(WM_NVTO,(WPARAM)pNvTo);
return;
}
CHtmlView::OnBeforeNavigate2(lpszURL,
nFlags,
lpszTargetFrameName,
baPostedData,
lpszHeaders,
pbCancel);
}
LRESULT CDemoView::OnNvTo(WPARAM wParam, LPARAM lParam)
{
NvToParam* pNvTo = (NvToParam*)wParam;
Navigate2((LPCTSTR)pNvTo->URL,
pNvTo->Flags,
pNvTo->PostedData,
(LPCTSTR)pNvTo->TargetFrameName,
(LPCTSTR)pNvTo->Headers);
delete pNvTo;
return 1;
}
在OnBeforeNavigate2中如果發現沒有自定義的User-Agent串,就加上這個串,並取消本次導航,再Post一個消息(一定要POST,讓OnBeforeNavigate2跳出以後再進行導航 ),在消息中再次導航,再次導航時請求頭已經有了自己的標識,所以能正常的導航。 去掉討厭的異常警告 在程序中使用了CHtmlView以後,我們在調整窗口大小的時候經常會看到輸出窗口輸出的異常警告: ReusingBrowser.exe 中的 0x77e53887 處最可能的異常: Microsoft C++ exception: COleException @ 0x0012e348 。
Warning: constructing COleException, scode = DISP_E_MEMBERNOTFOUND($80020003).
這是由於CHtmlView在處理WM_SIZE消息時的一點小問題引起的,采用如下代碼處理WM_SIZE消息就不會有此警告了
void CLhpHtmlView::OnSize(UINT nType, int cx, int cy)
{
CFormView::OnSize(nType, cx, cy);
if (::IsWindow(m_wndBrowser.m_hWnd))
{
CRect rect;
GetClientRect(rect);
// 就這一句與CHtmlView的不同
::AdjustWindowRectEx(rect, GetStyle(), FALSE, WS_EX_CLIENTEDGE);
m_wndBrowser.SetWindowPos(NULL,
rect.left,
rect.top,
rect.Width(),
rect.Height(),
SWP_NOACTIVATE | SWP_NOZORDER);
}
}
怎樣處理浏覽器內的拖放 有時可能有這樣的需求,我們希望在資源管理器裡托一個文件到浏覽器而做出相應的處理,甚至是將文件拖到某一個網頁元素上來做出相應的處理,而浏覽器默認的處理拖放文件操作是將文件打開,但WebBrowser控件給了我們一個自己處理拖放的機會。 那就是在自定義的控制站點類中實現IDocHostUIHandler,在接口IDocHostUIHandler的GetDropTarget方法中調用 浏覽器類的OnGetDropTarget虛函數。要處理網頁內的拖放,必需在OnGetDropTarget函數中返回一個自己定義的IDropTarget接口指針, 所以我們自己寫一個類CMyOleDropTarget從COleDropTarget類派生,並且在實現IDropTarget接口,此類的代碼在這就不列出了,請下載演示 程序,參考文件MyOleDropTarget.h和MyOleDropTarget.cpp。我們看CLhpHtmlView中OnGetDropTarget的代碼
HRESULT CLhpHtmlView::OnGetDropTarget(LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget )
{
m_DropTarget.SetIEDropTarget(pDropTarget);
LPDROPTARGET pMyDropTarget;
pMyDropTarget = (LPDROPTARGET)m_DropTarget.GetInterface(&IID_IDropTarget);
if(pMyDropTarget)
{
*ppDropTarget = pMyDropTarget;
pMyDropTarget->AddRef();
return S_OK;
}
return S_FALSE;
}
m_DropTarget即為自定義的處理拖放的對象。這樣就能通過在從CLhpHtmlView派生的類中重載OnDragEnter、OnDragOver、 OnDrop、OnDragLeave虛函數來處理拖放了。在這裡順帶講一下視圖是怎樣處理拖放的。 要使視圖處理拖放,首先在視圖裡添加一個COleDropTarget(或派生類)成員變量,如CLhpHtmlView中的“CMyOleDropTarget m_DropTarget;”,再在 視圖創建時調用COleDropTarget對象的Register,即把視圖與COleDropTarget對象關聯起來,如CLhpHtmlView中的“m_DropTarget.Register(this);”,再對拖放 觸發的事件進行相應的處理, OnDragEnter 把某對象拖入到視圖時觸發,在此檢測拖入的對象是不是視圖想接受的對象,如是返回“DROPEFFECT_MOVE”表示接受此對象,如
if(pDataObject->IsDataAvailable(CF_HDROP))// 被拖對象是文件嗎?
return DROPEFFECT_MOVE;
OnDragOver 被拖對象在視圖上移動,同OnDragEnter一樣檢測拖入對象,如果要接受此對象返回“DROPEFFECT_MOVE”。 OnDrop 拖著被拖對象在視圖上放開鼠標,在這裡對拖入對象做出處理; OnDragLeave 拖著被拖對象離開視圖。 C++的代碼寫好了,但事情還沒完,還必須在網頁裡用腳本對拖放事件進行處理, 即頁面裡哪個元素要接受拖放對象哪個元素就要處理ondragenter、ondragover、ondrop,代碼其實很簡單,讓事件的返回值為false即可,這樣 C++的代碼才有機會處理拖放事件,代碼如下:
......
<td ondragenter="event.returnValue = false" ondragover="event.returnValue = false" \
ondrop="event.returnValue = false">
......
如果要使整個視圖都接受拖放,則在Body元素中處理此三個事件。 注意:別忘了讓工程對OLE的支持即在初始化應用程序時調用AfxOleInit()。 怎樣禁止網頁元素的選取 用網頁做界面時多數情況下是不希望網頁上的元素是能夠被鼠標選中的, 要使網頁元素不能被選中做法是:給浏覽器的“宿主信息標記”加上DOCHOSTUIFLAG_DIALOG標記。 “宿主信息標記”用N個標記位來控制浏覽器的許多性質,如:
· 禁用浏覽器的3D的邊緣;
· 禁止滾動條;
· 禁用腳本;
· 定義雙擊處理的方式;
· 禁用浏覽器的自動完成功能;
...... 更多詳情請參考MSDN的DOCHOSTUIFLAG幫助。 怎樣修改“宿主信息標記”? 在CDocHostSite中實現IDocHostUIHandler, 在GetHostInfo方法中調用浏覽器的OnGetHostInfo虛函數,在虛函數OnGetHostInfo中便可指定“宿主信息標記”,如:
HRESULT CLhpHtmlView::OnGetHostInfo(DOCHOSTUIINFO * pInfo)
{
pInfo->cbSize = sizeof(DOCHOSTUIINFO);
pInfo->dwFlags = DOCHOSTUIFLAG_DIALOG |
DOCHOSTUIFLAG_THEME |
DOCHOSTUIFLAG_NO3DBORDER |
DOCHOSTUIFLAG_SCROLL_NO;
pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
return S_OK;
}
用腳本也可實現: 在Head中加入腳本:
document.onselectstart=new Function(''return false'');
或者
<body onselectstart="return false">。
其它 在CLhpHtmlView中還提供了幾個函數, 修改網頁元素的內容:
BOOL PutElementHtml(CString ElemID,CString Html);
取表單元素的值:
BOOL GetElementValue(CString ElemID,CString& Value);
設置表單元素的值:
BOOL PutElementValue(CString ElemID,CString Value);
給表單元素設置焦點:
void ElementSetFocus(CString EleName);
轉載:http://www.vckbase.com/document/viewdoc/?id=1486
————————————————————————————————————————————————————————————————
自定義浏覽器
本教程提供了自定義浏覽器控件的行為和外觀的一些方法。你將看到高級的宿主接口,IDocHostUIHandler, IDocHostUIHandler2, IDocHostShowUI, 和ICustomDoc。本文也討論其他自定義方法,例如在宿主的IDispatch實現中處理DISPID_AMBIENT_DLCONTROL來進行下載控制;以及使用IHostDialogHelper。
本文分為如下章節
· 前提和需求
· 介紹
· 浏覽器自定義架構
· IDocHostUIHandler
· IDocHostUIHandler2
· GetOptionKeyPath 和 GetOverrideKeyPath的比較
· 控制導航
· IDocHostShowUI
· 控制下載和執行
· IHostDialogHelper
· 控制新的窗口
· 顯示證書對話框(New!)
· 信息欄(New!)
· 結論
前提和需求
為了理解和使用本教程,你需要
· 對C++和COM的深入了解
·
· 熟悉活動模板庫 (ATL)
·
· 安裝了Microsoft(R) Internet Explorer (IE)6 或更高版本
·
· 開發環境具有用於IE6或更高版本的頭文件和庫文件;特別是Mshtmhst.h.(譯者注:可以在http://www.microsoft.com/msdownload/platformsdk/sdkupdate/ 這裡下載最新的Internet Development SDK)
許多自定義特性是在IE5或者5.5版本就可以使用的,但是有幾個特性需要IE6。一些特性需要IE6的Windows XP SP2版本。使用某個特性之前,應該檢查參考文檔以獲得版本信息。
介紹
集成浏覽器控件是快速軟件開發的強有力的工具。通過成為浏覽器的宿主,你可以利用便於使用的Dynamic HTML (DHTML), HTML, 和Extensible Markup Language (XML)來顯示信息和開發一個用戶界面。但是,浏覽器控件的行為可能不確切符合你的需求。例如,默認的狀態允許用戶通過快捷菜單的查看源代碼選項查看一個顯示的頁面的源代碼,你可能需要禁用或者干脆去掉這個選項。你可能更進一步,需要用你自己的快捷菜單替換默認的快捷菜單。
在剛剛提到的自定義特性之外,高級宿主特性允許
· 在顯示的頁面上的按鈕和其他控件可以調用你的應用程序的內建方法,有效地擴展DHTML對象模型(DOM)。
· 改變拖放的行為
· 限制浏覽器的導航,例如,限制於指定的頁面/域,或者站點
· 捕獲用戶鍵入,並且在需要的時候處理。比如說,你可能需要捕獲CTRL+O來阻止用戶在新的IE中打開網頁而不是使用你的程序打開,
· 改變默認字體和顯示設置
· 控制下載內容,以及當下載完成之後浏覽器的處理。例如,你可能禁用視頻的播放,腳本的執行,點擊鏈接時打開新的窗口,或者Microsoft(R) ActiveX 控件的下載和執行。
· 限制查看源代碼
· 捕獲搜索
· 捕獲導航錯誤
· 替代/修改快捷菜單或者禁用,替代,自定義,或者添加快捷菜單項
· 為你的應用程序改變注冊表設定
· 控制和修改浏覽器控件顯示的消息框
· 控制新窗口的創建方式
在下列節中,我們將會看到多數,但是不是全部的這些可能性而且討論該如何實現他們。
浏覽器自定義架構
下面三個接口是浏覽器控件用戶界面的自定義的核心:IDocHostUIHandler ,IDocHostUIHandler2 和 IDocHostShowUI。當你修改浏覽器控件的時候 , 這些是你在你的應用程序中實現的接口。也有一些服務接口。 ICustomDoc 被MSHTML實現並且提供一個方法在某些情況下能夠自定義浏覽器控件。IHostDialogHelper提供一個方法打開可信的對話框,沒有像IE對話框那樣為他們(譯者注:在標題欄上)作標記。
除了使用這些接口,你還可以做其他兩件事。其一,你能通過在IDispatch實現中攔截環境特性的變化來控制下載;其次,你能通過在IDispatch實現中攔截DISPID_NEWWINDOW2來控制窗口的創建方式。
譯者注:MFC7中的DHTML類,例如CHtmlView和CDHtmlDialog實現了這些接口,但是對於使用其他的類庫的程序員,可能需要自己實現這些接口。
當一個容器提供對ActiveX 控件支持的時候 , 浏覽器控件自定義機制被設計為被自動化。當浏覽器控件被實例化的時候,如果可能的話,它嘗試找來自宿主的 IDocHostUIHandler , IDocHostUIHandler2 和 IDocHostShowUI 實現。浏覽器控件通過調用宿主的IOleClientSite接口的一個QueryInterface方法來查找。
譯者注:IE5.5有個Bug,沒有查詢IDocHostUIHandler2 接口的實現,這使得宿主程序不能覆蓋默認的參數。需要更多信息的話,參考微軟知識庫文章 Q272968 BUG:IDocHostUIHandler2 沒有在浏覽器控件中調用。
這一個結構為一個實現一個IOleClientSite接口的應用程序自動地工作,通過調用浏覽器的IOleObject::SetClientSite方法傳遞給浏覽器控件一個IOleClientSite接口。浏覽器控件的一個典型的實例化可能看起來像這樣:
例子
//為了明確起見,省略錯誤檢查
CComPtr<IOleObject> spOleObj;
//創建 WebBrowser--在類成員變量 m_spWebBrowser中保存指針
CoCreateInstance(CLSID_WebBrowser, NULL, CLSCTX_INPROC, IID_IWebBrowser2, (void**)&m_spWebBrowser);
// 查詢WebBrowser的IOleObject接口
m_spWebBrowser->QueryInterface(IID_IOleObject, (void**)&spOleObj);
//設置用戶站點
spOleObj->SetClientSite(this);
//本地激活浏覽器控件
RECT rcClient
GetClientRect(&rcClient);
spOleObj->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, this, 0, GetTopLevelWindow(), &rcClient);
//容器攔截浏覽器事件的注冊
AtlAdvise(m_spWebBrowser,GetUnknown(), DIID_DWebBrowserEvents2,&m_dwCookie);
//導航到啟動頁
m_spWebBrowser->Navigate(L"res://webhost.exe/startpage.htm", NULL, NULL, NULL, NULL);
然而,如果你的應用程序沒有一個IOleClientSite接口,你並沒失去全部希望。IE提供ICustomDoc接口,這樣你能自己傳遞你的IDocHostUIHandler接口給浏覽器。你不能使用IDocHostUIHandler2和 IDocHostShowUI接口而不提供一個浏覽器控件宿主的IOleClientSite接口。
譯者注:
MFC7中引入的類COleControlContainer和一大堆DHTML類曾經搞得我暈頭轉向,最後我不得不放棄了自己對IOleClientSite的實現,而通過ICustomDoc來顯式地設置IDocHostUIHandler接口。這樣必須在第一個頁面下載完成之後才能夠開始自定義浏覽器,因為暴露ICustomDoc接口的對象只有在第一個頁面下載完成之後才可用。一個ICustomDoc的示例可以在CSDN文檔中心找到,網址是http://www.csdn.net/develop/Read_Article.asp?Id=8813
當浏覽器控件獲得了對這些接口之中的任何一個的一個指針的時候,接口的方法在適當的時候在浏覽器控件的生命期中被調用。舉例來說, 當用戶右擊在浏覽器控件的客戶區的任何地點時,在IE顯示它的默認快捷菜單之前,你的IDocHostUIHandler::ShowContextMenu的實現將會被調用。這給你一個機會顯示你自己的快捷菜單而且取消IE的快捷菜單顯示。
譯者注:一些屏蔽快捷菜單的示例可以在CSDN文檔中心找到,網址是http://www.csdn.net/develop/article/18/18541.shtm
當初始化浏覽器控件的時候 ,記住幾個重點。你的應用程序應該使用 OleInitialize而不是CoInitialize啟動COM。OleInitialize啟用剪貼簿支持,拖放,對象連接與嵌入(OLE)和本地激活。當你的應用程序結束的時候使用OleUninitialize關閉COM庫。
ATL COM 向導使用 CoInitialize而不是OleInitialize打開COM庫。 如果你使用這一個向導建立一個可運行的程序,你需要將 CoInitialize 和 CoUninitialize 調用換成 OleInitialize 和 OleUninitialize。對於一個微軟基礎類 (MFC) 應用程序, 確定你的應用程序調用 AfxOleInit, 它在它的初始化程序中調用OleInitialize。
如果你不需要在你的應用程序中支持拖放,你可以調用IWebBrowser2::RegisterAsDropTarget,傳遞VARIANT_TRUE(譯者注:原文如此,按照接口的文檔,似乎應該傳遞VARIANT_FALSE), 避免任何在你的浏覽器控件實例上的拖放操作。
一個浏覽器控件宿主應用程序也需要IOleInPlaceSite的一個實現, 由於 IOleInPlaceSite派生自IOleWindow,應用程序將需要IOleWindow的一個實現。你需要這些實現使得你的應用程序具有一個窗口,顯示浏覽器控件,以及處理它的顯示設置。
這些接口和IOleClientSite的實現在許多情況可能是最小的或不存在的。IOleClientSite的所有方法都可以返回E_NOTIMPL。 一些IOleInPlaceSite和IOleWindow的方法需要一個實現來覆蓋返回值。可以在示例代碼中查看IOleInPlaceSite和IOleWindow的最小實現的樣例代碼。
既然我們已經完成了初始化的准備,讓我們看一看浏覽器控件自定義的每一個接口。
IDocHostUIHandler
IDocHostUIHandler自IE5以後已經是可用的。它提供15個方法。大體上,一些較重要的方法是IDocHostUIHandler::GetExternal, IDocHostUIHandler::GetHostInfo, IDocHostUIHandler::GetOptionKeyPath, IDocHostUIHandler::ShowContextMenu, 和 IDocHostUIHandler::TranslateAccelerator。當然,方法對你的重要性將會依賴於你的應用程序。
你使用IDocHostUIHandler::GetHostInfo告訴MSHTML有關你的應用程序的能力和需求。通過它你能控制很多東西, 舉例來說。
· 你能禁用浏覽器的3D的邊緣。
· 你能避免滾動條或改變他們的外觀。
· 你能禁用腳本。
· 你能定義雙擊處理的方式。
· 你能禁用浏覽器的自動完成功能。
IDocHostUIHandler::GetHostInfo有一個參數,被 MSHTML分配的DOCHOSTUIINFO 結構的一個指針。你的工作是要將在結構中填充你傳給MSHTML的信息。
DOCHOSTUIINFO結構有四個成員。第一個成員是 cbSize,是結構的大小。你應該自己像下面的示例代碼那樣設置。第二個成員是dwFlags,由來自DOCHOSTUIFLAG枚舉的數值位與組成。第三個成員是dwDoubleClick,來自DOCHOSTUIDBLCLK枚舉的一個數值。第四個成員是pchHostCss。你可以將pchHostCss設定為浏覽器控件顯示的頁面中應用的全局樣式表(CSS)規則的一個字符串的指針。DOCHOSTUIINFO 的最後一個成員是pchHostNs。你可以設置為你提供的分號分隔的命名空間列表字符串。在你正在浏覽器控件中顯示的頁上使用自定義標簽的時候使用這一個成員。這樣你能聲明一個全局的命名空間列表,而不需要在每個顯示的頁面上聲明他們。
確定使用CoTaskMemAlloc為pchHostCss或pchHostNS分配字符串。(譯者注:看起來調用者用CoTaskMemFree釋放這些字符串)。
例子
HRESULT GetHostInfo( DOCHOSTUIINFO* pInfo)
{
WCHAR* szCSS = L"BODY {background-color:#ffcccc}";
WCHAR* szNS = L"IE;MyTags;MyTags2='www.microsoft.com'";
#define CCHMAX 256
size_t cchLengthCSS,cchLengthszNS;
HRESULT hr=StringCchLengthW(szCSS, CCHMAX,&cchLengthCSS)
//TODO: 在這裡處理錯誤。
OLECHAR* pCSSBuffer=(OLECHAR*) CoTaskMemAlloc((cchLengthCSS+1)*sizeof(OLECHAR));
//TODO: 在這裡處理錯誤,確定內存成功地被分配。
hr=StringCchLengthW(szNS, CCHMAX,&cchLengthszNS)
//TODO: 在這裡處理錯誤。
OLECHAR* pNSBuffer=(OLECHAR*) CoTaskMemAlloc((cchLengthszNS+1)*sizeof(OLECHAR));
//TODO: 在這裡處理錯誤,確定內存成功地被分配。
hr=StringCchCopyW(pCSSBuffer , cchLengthCSS+1,szCSS)
//TODO: 在這裡處理錯誤。
hr=StringCchCopyW(pNSBuffer , cchLengthszNS+1,szNS)
//TODO: 在這裡處理錯誤。
pInfo-> cbSize= sizeof(DOCHOSTUIINFO)
pInfo-> dwFlags=DOCHOSTUIFLAG_NO3DBORDER|DOCHOSTUIFLAG_SCROLL_NO|DOCHOSTUIFLAG_ENABLE_FORMS_AUTOCOMPLETE;
pInfo-> dwDoubleClick= DOCHOSTUIDBLCLK_DEFAULT;
pInfo-> pchHostCss= pCSSBuffer;
pInfo-> pchHostNS= pNSBuffer;
return S_OK;
}
如果你沒有什麼需要告訴MSHTML的,你可以在這個方法中返回E_NOTIMPL 。
IDocHostUIHandler::ShowContextMenu
通過實現這一個方法, 你獲得在當一個用戶右擊時被浏覽器控件顯示的快捷菜單的控制。你能通過在這個方法中返回S_OK 阻止IE顯示它的默認快捷菜單。返回一些其他的數值 , 像S_FALSE或E_NOTIMPL,允許IE繼續執行它的默認快捷菜單行為。
如果你僅僅在這個方法中返回S_OK, 你能避免任何浏覽器控件的右擊行為。 這可能是你在許多場合中的全部需求,但是你能做到更多。通常,你使用這一個方法在返回 S_OK 之前產生並且顯示你自己的快捷菜單。如果你知道浏覽器控件顯示的菜單的資源,而且它如何選擇他們,你能也有效地自定義默認的浏覽器控件快捷菜單。讓我們看看它如何工作。
浏覽器控件由Shdoclc.dll獲得它的快捷菜單資源。這個知識和一些 #define給予你一個機會操縱浏覽器的菜單。讓我們舉例來說,假定你對默認菜單感到滿意,除了你想要除去查看源代碼項之外。下列代碼載入來自Shdoclc.dll的浏覽器控件快捷菜單資源,根據環境選擇正確的菜單,移除IDM_VIEWSOURCE命令對應的菜單項,然後顯示菜單。
例子
HRESULT CBrowserHost::ShowContextMenu(DWORD dwID,
POINT *ppt,
IUnknown *pcmdTarget,
IDispatch *pdispObject)
{
#define IDR_BROWSE_CONTEXT_MENU 24641
#define IDR_FORM_CONTEXT_MENU 24640
#define SHDVID_GETMIMECSETMENU 27
#define SHDVID_ADDMENUEXTENSIONS 53
HRESULT hr;
HINSTANCE hinstSHDOCLC;
HWND hwnd;
HMENU hMenu;
CComPtr<IOleCommandTarget> spCT;
CComPtr<IOleWindow> spWnd;
MENUITEMINFO mii={0};
CComVariant var, var1, var2;
hr = pcmdTarget->QueryInterface(IID_IOleCommandTarget, (void**)&spCT);
hr = pcmdTarget->QueryInterface(IID_IOleWindow, (void**)&spWnd);
hr = spWnd->GetWindow(&hwnd);
hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
if (hinstSHDOCLC == NULL)
{
// 載入模塊錯誤 -- 盡可能安全地失敗
return;
}
hMenu=LoadMenu(hinstSHDOCLC,
MAKEINTRESOURCE(IDR_BROWSE_CONTEXT_MENU));
hMenu=GetSubMenu(hMenu,dwID);
//獲得語言子菜單
hr = spCT->Exec(&CGID_ShellDocView, SHDVID_GETMIMECSETMENU, 0, NULL, &var);
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_SUBMENU;
mii.hSubMenu = (HMENU) var.byref;
//加入語言子菜單到編碼上下文菜單
SetMenuItemInfo(hMenu, IDM_LANGUAGE, FALSE, &mii);
//插入來自注冊表的快捷菜單擴展
V_VT(&var1) = VT_INT_PTR;
V_BYREF(&var1) = hMenu;
V_VT(&var2) = VT_I4;
V_I4(&var2) = dwID;
hr = spCT->Exec(&CGID_ShellDocView, SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);
//刪除查看源代碼
DeleteMenu(hMenu, IDM_VIEWSOURCE, MF_BYCOMMAND);
//顯示快捷菜單
int iSelection = ::TrackPopupMenu(hMenu,
TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
ppt->x,
ppt->y,
0,
hwnd,
(RECT*)NULL);
//發送選定的快捷菜單項目指令到外殼
LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);
FreeLibrary(hinstSHDOCLC);
return S_OK;
}
安全警告:不正確地使用LoadLibrary能載入錯誤的動態鏈接庫(DLL)來威脅你的應用程序的安全。關於該如何正確地用微軟Windows 的不同版本載入DLL的信息,參照LoadLibrary的文檔。
IDocHostUIHandler 提供一個讓你用在你自己的應用程序中實現的你自己的對象,方法和特性擴充IE文檔對象模型 (DOM)的方法。你的實現是提供給MSHTML一個IDispatch接口指針,指向你自定義的COM自動化對象,實現你自定義的對象、屬性和方法。這些對象,特性和方法之後可以在浏覽器控件顯示的任何頁面中通過文檔的外部對象訪問。
這一個方法的實現可以是非常簡單的, 假定你的IDispatch接口在實現IDocHostUIHandler的相同對象上。
HRESULT CBrowserHost::GetExternal(IDispatch **ppDispatch)
{
*ppDispatch = this;
return S_OK;
}
只要 MSHTML有對你的 IDispatch 的一個指針,MSHTML將會傳遞網頁上對任何外部對象的調用到你的應用程序的自動化方法:
<SCRIPT language="JScript">
function MyFunc(iSomeData)
{
external.MyCustomMethod("Some text", iSomeData);
}
</SCRIPT>
你也能使用這技術傳遞整個對象到一個網頁。為了實現它,在你的IDispatch實現中創建一個方法,傳遞回你的網頁可以用的對象。
<SCRIPT language="JScript">
function MyFunc(iSomeData)
{
var oCustCalendarObj;
external.GetCustomCalender(oCustCalenderObj);
oCustCalerdarObj.doStuffWithIt();
}
</SCRIPT>
可以看看示例代碼中使用 ATL的IDispatch自動化實現的一個例子 。
譯者注:IE也擴展了浏覽器的文檔對象模型,使得你在腳本中可以通過擴展對象的menuArguments屬性訪問當前窗口對象。
IDocHostUIHandler::GetOptionKeyPath
IDocHostUIHandler::GetOptionKeyPath是自定義浏覽器控件的一個非常有力的工具。 許多浏覽器控件顯示和行為設定被儲存在注冊表中HKEY_CURRENT_USER鍵的下面。IDocHostUIHandler::GetOptionKeyPath給你一個機會為你的浏覽器控件的特定實例覆蓋這些注冊表設定。它通過讓你提供一個替代的注冊表位置來實現,浏覽器控件將會在這裡讀取注冊表設置。
IDocHostUIHandler::GetOptionKeyPath的一個實現傳遞給你讓浏覽器控件讀取注冊表設置的位置的一個字符串。浏覽器控件將會找尋在HKEY_CURRENT_USER鍵下面的這一個鍵。
例子
HRESULT CBrowserHost::GetOptionKeyPath(LPOLESTR *pchKey,
DWORD dwReserved)
{
HRESULT hr;
#define CCHMAX 256
size_t cchLength;
if (pchKey)
{
WCHAR* szMyKey = L"Software\MyCompany\MyApp";
hr = StringCchLengthW(szMyKey, CCHMAX, &cchLength);
//TODO: 在這裡處理錯誤。
*pchKey = (LPOLESTR)CoTaskMemAlloc((cchLength + 1) * sizeof(WCHAR));
if (*pchKey)
hr = StringCchCopyW(*pchKey, cchLength + 1, szKey);
//TODO: 在這裡處理錯誤。
hr = (*pchKey) ? S_OK : E_OUTOFMEMORY;
}
else
hr = E_INVALIDARG;
return hr;
}
和IDocHostUIHandler::GetHostInfo一樣,確保為你的字符串使用 CoTaskMemAlloc分配內存。
告訴浏覽器控件該在哪裡找尋你的注冊表設置實際上是第一步——就程序運行來說是第二步。 你的程序必須在被IDocHostUIHandler::GetOptionKeyPath告訴的位置設置一個注冊表鍵,這樣浏覽器控件才可以去讀取。有多種方法來完成這個步驟。一個方法是當應用程序被安裝的時候執行一個注冊表腳本。另外的一個方法是當應用程序啟動的時候,用代碼來完成。這裡是改變默認值字體,大小和顏色的一個設定。
例子
HRESULT SetSomeKeys()
{
HKEY hKey = NULL;
HKEY hKey2 = NULL;
HKEY hKey3 = NULL;
DWORD dwDisposition = NULL;
LONG lResult = NULL;
#define CBMAX 256
size_t cbLength;
RegCreateKeyEx(HKEY_CURRENT_USER, _T("Software\MyCompany\MyApp"),
NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE,
NULL, &hKey, &dwDisposition);
RegCreateKeyEx(hKey, _T("Main"), NULL, NULL, REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE, NULL, &hKey2, &dwDisposition);
RegSetValueEx(hKey2, _T("Use_DlgBox_Colors"), NULL, REG_SZ,
(CONST BYTE*)_T("no"), sizeof(_T("no")));
RegCloseKey(hKey2);
RegCreateKeyEx(hKey, _T("Settings"), NULL, NULL, REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE, NULL, &hKey2, &dwDisposition);
RegSetValueEx(hKey2, _T("Anchor Color"), NULL, REG_SZ,
(CONST BYTE*)_T("0,255,255"), sizeof(_T("0,255,255")));
RegSetValueEx(hKey2, _T("Text Color"), NULL, REG_SZ,
(CONST BYTE*)_T("255,0,255"), sizeof(_T("255,0,255")));
RegCloseKey(hKey2);
RegCreateKeyEx(hKey, _T("International\Scripts"), NULL, NULL,
REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL,
&hKey2, &dwDisposition);
BYTE bDefaultScript = 0x3;
RegSetValueEx(hKey2, _T("Default_Script"), NULL, REG_BINARY,
&bDefaultScript, sizeof(bDefaultScript));
RegCreateKeyEx(hKey2, _T("3"), NULL, NULL, REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE, NULL, &hKey3, &dwDisposition);
BYTE bSize = 0x4; // Value from 0 - 4. 2 is medium.
TCHAR* szFontName = _T("Comic Sans MS");
TCHAR* szFixedFontName = _T("Courier");
HRESULT hr = StringCbLength(szFontName, CBMAX, &cbLength);
//TODO: 在這裡處理錯誤。
RegSetValueEx(hKey3, _T("IEPropFontName"), NULL, REG_SZ,
(CONST BYTE*)szFontName, cbLength + sizeof(TCHAR));
hr = StringCbLength(szFixedFontName, CBMAX, &cbLength);
//TODO: 在這裡處理錯誤。
RegSetValueEx(hKey3, _T("IEFixedFontName"), NULL, REG_SZ,
(CONST BYTE*)szFixedFontName, cbLength + sizeof(TCHAR));
RegSetValueEx(hKey3, _T("IEFontSize"), NULL, REG_BINARY, &bSize, sizeof(bSize));
RegCloseKey(hKey3);
RegCloseKey(hKey2);
RegCloseKey(hKey);
return S_OK;
}
IDocHostUIHandler2
IDocHostUIHandler2 只有一個方法,IDocHostUIHandler2::GetOverrideKeyPath。它運行非常類似於IDocHostUIHandler::GetOptionKeyPath的一個功能。它指出你修改自默認注冊表設置的集成浏覽器使用的注冊表設置的位置。IDocHostUIHandler2::GetOverrideKeyPath 的一個實現看起來會很類似於IDocHostUIHandler::GetOptionKeyPath的一個實現。
GetOptionKeyPath 和 GetOverrideKeyPath 的比較
你或許沒看到IDocHostUIHandler::GetOptionKeyPath和IDocHostUIHandler2::GetOverrideKeyPath之間的任何不同。在他們之間的不同是微妙的, 但是重要的。如果你實現 IDocHostUIHandler::GetOptionKeyPath,你的浏覽器控件實例將會忽略任何IE的用戶設定。這些設定被儲存在注冊表的HKEY_CURRENT_USER/Software/Microsoft/Internet Explorer下面。如果你實現IDocHostUIHandler2::GetOverrideKeyPath,你的浏覽器控件實例將會合並任何的用戶設定—字體設定,菜單擴展,諸如此類——到它的顯示和行為中。
舉例說明在IDocHostUIHandler::GetOptionKeyPath和IDocHostUIHandler2::GetOverrideKeyPath之間的不同,讓我們重新看看IDocHostUIHandler::ShowContextMenu那段的示例代碼。記住這一行:
spCT->Exec(&CGID_ShellDocView, SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);
如果你已經實現IDocHostUIHandler::GetOptionKeyPath,因為菜單擴展信息被儲存在當前用戶的注冊表信息中,所以這一行不會加入任何自定義項目到快捷菜單。如果你已經實現IDocHostUIHandler2::GetOverrideKeyPath, 這一個行會添加在HKEY_CURRENT_USER/Software/Microsoft/Internet Explorer/MenuExt面定義的任何目前用戶定義的菜單擴展, 除非你明確地在你的自定義注冊信息位置提供一個空的或替代的MenuExt鍵。
控制導航
你可能想知道在IDocHostUIHandler那一節為什麼不提到 IDocHostUIHandler::TranslateUrl,作為在你希望控制頁面導航時實現的方法。原因是這一個方法不是控制導航的最通用的技術。 除非你直接地集成MSHTML,這一個方法將沒有控制導航的效果。作為替代,通過實現IDispatch::Invoke,處理DISPID_BEFORENAVIGATE2,你可以控制導航。例如,下列代碼避免導航到一個特別的網址,如果使用者嘗試這麼做,會顯示 "沒有允許導航" 錯誤頁。
例子
case DISPID_BEFORENAVIGATE2:
{
CComBSTR url = ((*pDispParams).rgvarg)[5].pvarVal->bstrVal;
if (url == "http://www.adatum.com" || url == "http://www.adatum.com/")
{
CComPtr<IWebBrowser2> spBrowser;
CComPtr<IDispatch> spDisp = ((*pDispParams).rgvarg)[6].pdispVal;
spDisp->QueryInterface(IID_IWebBrowser2, (void**)&spBrowser);
spBrowser->Stop();
CComBSTR newURL = "L"res://webhost.exe/nonavigate.htm";
spBrowser->Navigate(newURL, NULL, NULL, NULL, NULL);
((*pDispParams).rgvarg)[0].boolVal = TRUE;
}
break;
}
IDocHostShowUI
這個接口給你對浏覽器控件顯示的信息對話框和幫助的控制。它工作機理和IDocHostUIHandler和IDocHostUIHandler2一樣,你實現它,這樣在浏覽器控件顯示它自己的任何的信息或幫助之前 ,能調用你的IDocHostShowUI的方法。這給你一個機會阻止浏覽器控件顯示任何東西,而且使你能夠改為顯示你自己的自定義信息或幫助。 IDocHostShowUI有兩個方法,IDocHostShowUI::ShowMessage和IDocHostShowUI::ShowHelp。
返回 S_OK禁用浏覽器控件的信息對話框。任何其他的返回數值,像S_FALSE或E_NOTIMPL,允許浏覽器控件顯示它的信息對話框。
你通過這個方法能做的一件好的事情是為你的應用程序自定義信息框標題,替代 "Microsoft Internet Explorer" 。你能通過比較lpstrCaption和儲存在Shdoclc.dll中的IE使用的字符串資源來完成它。它的ID是IDS_MESSAGE_BOX_TITLE,數值是2213。下列示例代碼演示你可能需要做的工作。
例子
HRESULT CBrowserHost::ShowMessage(HWND hwnd,
LPOLESTR lpstrText,
LPOLESTR lpstrCaption,
DWORD dwType,
LPOLESTR lpstrHelpFile,
DWORD dwHelpContext,
LRESULT *plResult)
{
USES_CONVERSION;
TCHAR pBuffer[50];
// 窗口標題"Microsoft Internet Explorer"的資源標識
#define IDS_MESSAGE_BOX_TITLE 2213
//載入Shdoclc.dll 和IE消息框標題字符串
HINSTANCE hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
if (hinstSHDOCLC == NULL)
{
// 載入模塊錯誤 -- 盡可能安全地失敗
return;
}
LoadString(hinstSHDOCLC, IDS_MESSAGE_BOX_TITLE, pBuffer, 50);
// 比較IE消息框標題字符串和lpstrCaption
// 如果相同,用自定義標題替換
if (_tcscmp(OLE2T(lpstrCaption), pBuffer) == 0)
lpstrCaption = L"Custom Caption";
// 創建自己的消息框並且顯示
*plResult = MessageBox(OLE2T(lpstrText), OLE2T(lpstrCaption), dwType);
//卸載Shdoclc.dll並且返回
FreeLibrary(hinstSHDOCLC);
return S_OK;
}
安全警告:不正確地使用LoadLibrary能載入錯誤的動態鏈接庫(DLL)來威脅你的應用程序的安全。關於該如何正確地用微軟Windows的不同版本載入DLL的信息,參照 LoadLibrary的文檔。
這一個方法在當IE需要顯示幫助時被調用,舉例來說當 F1 鍵被按下時,而且工作方式和IDocHostShowUI::ShowMessage類似。返回S_OK覆蓋IE的幫助,或另外的HRESULT值讓IE執行自己的幫助。
控制下載和執行
浏覽器控件給你它的下載,顯示設置和執行的控制權。 為了要得到這些控制,你實現你的宿主的IDispatch接口,使得它處理DISPID_AMBIENT_DLCONTROL。當浏覽器控件被實例化的時候,它將會以這一個ID調用你的IDispatch::Invoke。將pvarResult設置為下列的標識的一個位與的組合,指明你的配置。
· DLCTL_DLIMAGES , DLCTL_VIDEOS 和 DLCTL_BGSOUNDS: 如果這些標識被設定,圖像,視頻和背景音樂將會被從服務器下載並且顯示或播放,否則將不被下載和顯示。
· DLCTL_NO_SCRIPTS 和 DLCTL_NO_JAVA: 腳本和Java小程序將不被運行。
· DLCTL_NO_DLACTIVEXCTLS 和 DLCTL_NO_RUNACTIVEXCTLS: ActiveX 控件將不被下載或者運行。
· DLCTL_DOWNLOADONLY: 網頁只將會被下載,不顯示。
· DLCTL_NO_FRAMEDOWNLOAD:浏覽器控件將會下載並且解析框架集頁面,但是不會下載和解析框架集中單獨的框架。
· DLCTL_RESYNCHRONIZE 和 DLCTL_PRAGMA_NO_CACHE: 這些標志導致Internet緩沖的刷新。通過 DLCTL_RESYNCHRONIZE,服務器將會被請求更新狀態。如果服務器指出緩存信息是最新的,將會使用 緩存文件。通過DLCTL_PRAGMA_NO_CACHE,不管文件的更新狀態如何,文件都會被從服務器重新下載。
· DLCTL_NO_BEHAVIORS: 行為不被下載並且在文件中被禁用。
· DLCTL_NO_METACHARSET_HTML: 忽略在META元素中指明的字符集。
· DLCTL_URL_ENCODING_DISABLE_UTF8 和 DLCTL_URL_ENCODING_ENABLE_UTF8: 這些標志的功能類似於IDocHostUIHandler::GetHostInfo中使用的DOCHOSTUIFLAG_URL_ENCODING_DISABLE_UTF8和DOCHOSTUIFLAG_URL_ENCODING_ENABLE_UTF8標志。不同是只有在浏覽器控件被初始化的時候,DOCHOSTUIFLAG標志才會被檢查。這裡的環境特性變化的下載標志在每當浏覽器控件需要運行一個下載時被檢查。
· DLCTL_NO_CLIENTPULL: 不運行客戶端重定位頁面操作(譯者注:例如<meta http-equiv="refresh" content="30"> 的默認行為)。
· DLCTL_SILENT: 在下載期間沒有用戶界面顯示。
· DLCTL_FORCEOFFLINE: 浏覽器控件總是在脫機模式中操作。
· DLCTL_OFFLINEIFNOTCONNECTED 和 DLCTL_OFFLINE: 這些標志是相同的。如果不連接到英特網,浏覽器控件將會在脫機模式中操作。
DISPID_AMBIENT_DLCONTROL和標志的數值是在mshtmdid.h被定義的。
最初,當對IDispatch::Invoke調用開始的時候, pvarResult參數指向的VARIANT是VT_EMPTY類型。 你必須為任何有效的設定設置它為VT_I4類型。你可以在VARIANT的lVal成員中存儲標志數值。
大部份標志數值有否定的效果,也就是說,他們避免行為正常地發生。舉例來說,如果你不自定義浏覽器控件行為,那麼通常腳本會被執行。 但是如果你設定DLCTL_NOSCRIPTS 標志,腳本將不會在控制的那個實例中運行。然而,三個標志— DLCTL_DLIMAGES , DLCTL_VIDEOS 和 DLCTL_BGSOUNDS的作用正好相反。你必須全部設置標志,使得浏覽器控件以它的默認行為執行關於圖像,視頻和聲音的處理。
下列示例代碼使得一個浏覽器控件實例下載並且顯示圖像和視頻,但是不處理背景音樂,因為DLCTL_BGSOUNDS沒有被明確地設定。浏覽器控件顯示的頁上的腳本運行被禁用。
STDMETHODIMP CAtlBrCon::Invoke(DISPID dispidMember, REFIID riid,
LCID lcid, WORD wFlags,
DISPPARAMS* pDispParams,
VARIANT* pvarResult,
EXCEPINFO* pExcepInfo,
UINT* puArgErr)
{
switch (dispidMember)
{
case DISPID_AMBIENT_DLCONTROL:
pvarResult->vt = VT_I4;
pvarResult->lVal = DLCTL_DLIMAGES | DLCTL_VIDEOS | DLCTL_NO_SCRIPTS;
break;
default:
return DISP_E_MEMBERNOTFOUND;
}
return S_OK;
}
IHostDialogHelper
IHostDialogHelper是一個你能根據你的愛好創建對話框的接口。這一個接口有一個方法,IHostDialogHelper::ShowHTMLDialog。這一個方法提供如同功能ShowHTMLDialog一般的服務,但是使用起來稍微比較容易一點。
為了要使用IHostDialogHelper,你從頭產生對話框輔助對象。在這裡是你使用CoCreateInstance的方式創建它。接口和ID在 mshtmhst.h 中被定義。
例子
IHostDialogHelper* pHDH;
IMoniker* pUrlMoniker;
BSTR bstrOptions = SysAllocString(L"dialogHeight:30;dialogWidth:40");
BSTR bstrPath = SysAllocString(L"c:\dialog.htm");
CreateURLMoniker(NULL, bstrPath, &pUrlMoniker);
// 創建對話框輔助對象
CoCreateInstance(CLSID_HostDialogHelper,
NULL,
CLSCTX_INPROC,
IID_IHostDialogHelper,
(void**)&pHDH);
//調用ShowHTMLDialog 創建對話框
pHDH->ShowHTMLDialog(NULL,
pUrlMoniker,
NULL,
bstrOptions,
NULL,
NULL);
//釋放資源
SysFreeString(bstrPath);
SysFreeString(bstrOptions);
pUrlMoniker->Release();
pHDH->Release();
譯者注:如果要使用對話框來獲得用戶輸入,你可能需要傳遞兩個參數到ShowHTMLDialog。關於ShowHTMLDialog參數的說明,參見Platform SDK文檔。ShowHTMLDialog和ShowHTMLDialogEx 似乎一直是MSHTML.DLL導出的兩個函數,微軟把它封裝為接口,可能是在為未來的兼容性作准備。
控制新的窗口
控制浏覽器控件的一個重要的方法是控制導航。你在前面已經看見如何在IDispatch::Invoke中攔截DISPID_BEFORENAVIGATE2來實現控制你的浏覽器控件的導航位置。另外一個導航的重要的方面是要控制導航發生方式, 尤其是打開新的窗口的時候。讓我們舉例來說, 使用者右擊一個鏈接,選擇 "在新窗囗中打開" 或某一頁包含像這樣的腳本:
window.open("www.msn.com")
默認地,浏覽器控件對這行代碼的處理是通過打開IE的一個新的實例來顯示網頁。這可能正好是你的應用程序需要的,但是也可能不是。也許你需要在當前的浏覽器控件實例中打開所有鏈接,或者你將在你控制下的浏覽器控件的一個新的實例——具有你的用戶界面和你的商標——打開鏈接。
你可以在你的IDispatch實現中攔截一個事件——DWebBrowserEvents2::NewWindow2——來控制它。你的控制需要連接到DWebBrowserEvents2的連接點來攔截這一個事件。
你連接到了DWebBrowserEvents2之後,實現你的IDispatch::Invoke以處理 DISPID_NEWWINDOW2。在為DISPID_NEWWINDOW2的IDispatch::Invoke函數調用中,數組pDispParams包含兩個參數。第一個,序號是零, 是一個布爾類型的數值,告訴浏覽器控件是否取消新的窗囗。默認它是假值,而且將會打開一個新的窗囗。如果你要完全取消新窗囗的創建, 設定標志到真值。
序號為一的參數是一個IDispatch接口的指針。你可以將這一個參數設定為你已經創建的浏覽器控件的IDispatch。當你傳回這樣一個IDispatch之後,MSHTML將會使用你給出的控件打開鏈接。
譯者注:MFC中的DHTML類和類向導默認支持這個事件。需要更多信息的話,參見MSJ1998年7月份的文章Keeping an Eye on Your Browser by Monitoring Internet Explorer 4.0 Events,以及 微軟知識庫文章 Q184876 HOWTO: Use the WebBrowser Control NewWindow2 Event
顯示一個正數對話框
IE6或者更高版本中,你可以在用戶浏覽一個合法的安全超文本傳輸協議(HTTPS)站點的時候顯示證書對話框。這和用戶點擊IE中的鎖圖標效果相同。你可以通過 DWebBrowserEvents2::SetSecureLockIcon事件來顯示你自己的圖標。
#define SHDVID_SSLSTATUS 33
IOleCommandTarget *pct;
if (SUCCEEDED(pWebBrowser2->QueryInterface(IID_IOleCommandTarget, (void **) &pct)))
{
pct->Exec(&CGID_ShellDocView, SHDVID_SSLSTATUS, 0, NULL, NULL);
pct->Release();
}
信息欄
Windows XP SP2 中的Internet Explorer 6 引入了一個新的安全用戶界面元素,稱為信息欄。在特定操作被阻止的時候,信息欄給用戶顯示一個用戶界面元素。特別的,它會在以下操作被阻止的時候顯示。
· 彈出窗口初始化(參見 彈出窗口殺手)
·
· 文件下載 (see 文件下載的限制)
·
· 安裝ActiveX 控件(see ActiveX 的限制)
·
· ActiveX控件安全提示的原因是用戶安全設置或者是控件未標記為腳本安全的。
·
· 文件的擴展名和多用途因特網郵件擴展類型(MIME)不符的(參見 MIME 處理)
·
· 網絡協議鎖死的內容(參見 協議)
·
信息欄是Windows XP SP2 中的Internet Explorer 6引入的安全特性之一。和其他安全特性控制一樣,可以通過一個注冊表鍵來控制:(FEATURE_SECURITYBAND). 默認情況下IE(iexplorer.exe) 和Windows 資源管理器(explorer.exe) 在這個安全特性控制下。下面顯示注冊表鍵和啟用過程:
HKEY_LOCAL_MACHINE (or HKEY_CURRENT_USER)
SOFTWARE
Microsoft
Internet Explorer
Main
FeatureControl
FEATURE_SECURITYBAND
iexplorer.exe= 0x00000001
explorer.exe= 0x00000001
process name.exe=0x00000001
這個FEATURE_SECURITYBAND 安全特性控制影響IE是否顯示信息欄,信息欄在一個操作被阻止的時候提示用戶。它不控制操作的阻止過程。
一個集成浏覽器控件的程序可以通過將其進程添加到這個注冊表鍵來啟用信息欄。這可以通過調用CoInternetSetFeatureEnabled函數來在運行時執行。如果一個應用程序並未在這個安全特性控制下,那麼浏覽器控件的行為和Internet Explorer 6 SP1b中的一樣.
沒有方法通過腳本來訪問這個特性。
在FEATURE_SECURITYBAND及相關安全特性控制下的應用程序可以使用信息欄應用程序編程接口(API)來在一個URL 操作被禁止時自定義顯示的用戶界面。為信息欄引入了很多新的OLECMDID命令。頭三個是屬於CGID_DocHostCommandHandler組。宿主應用程序應該在它們的IDocHostUIHandler實現的同一個對象中實現IOleCommandTarget ,以接受來自浏覽器控件的IOleCommandTarget::Exec調用。
· OLECMDID_PAGEACTIONBLOCKED
·
· OLECMDID_PAGEACTIONUIQUERY
·
· OLECMDID_FOCUSVIEWCONTROLS
·
宿主應用程序可以使用下面兩個新的OLECMDID 命令來執行浏覽器控件的IOleCommandTarget::Exec調用。
· OLECMDID_FOCUSVIEWCONTROLSQUERY
·
· OLECMDID_SHOWPAGEACTIONMENU
·
這個示例使用IWebBrowser2::ExecWB 來執行OLECMDID_SHOWPAGEACTIONMENU 命令。
POINT pt = { 0 };
GetCursorPos(&pt);
CComVariant varHwnd((LONG)hwnd);
CComVariant varX(pt.x);
CComVariant varY(pt.y);
SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, 3);
LONG lIndex = 0;
SafeArrayPutElement(psa, &lIndex, &varHwnd);
lIndex++;
SafeArrayPutElement(psa, &lIndex, &varX);
lIndex++;
SafeArrayPutElement(psa, &lIndex, &varY);
CComVariant varArgIn;
V_VT(&varArgIn) = VT_ARRAY | VT_I4;
V_ARRAY(&varArgIn) = psa;
pBrowser->ExecWB(OLECMDID_SHOWPAGEACTIONMENU, (OLECMDEXECOPT)dwPageActionFlags, &varArgIn, NULL);
另外,應用程序可以實現IInternetSecurityManager來重載默認的安全區域設置,參見創建一個自定義URL安全管理器以獲得更多信息.
結論
你現在有許多技術,可以根據你的處理來自定義浏覽器控件。這個文章決不是沒有遺漏的,但是希望你現在可以自行發現超越本文的技術。檢查IE注冊表設置中那些你可以用IDocHostUIHandler::GetOptionKeyPath或IDocHostUIHandler2::GetOverrideKeyPath修改的信息。記住許多注冊表設置相互依賴。你可能必須做一些實驗來發現注冊表設置可以多麼的有效地自定義;如果需要控制浏覽器控件的拖放行為,你也可以去看看IDocHostUIHandler::GetDropTarget。
C# WinForm WebBrowser (五) 討厭的問題
WebBrowse 編輯模式 中幾個討厭的問題:
1、當設置DocumentText屬性值時會一直彈出一個“可惡的保存對話框”
現我已知的較好的策略有: 1)在設置兩個DocumentText屬性值之間使用webBrowser1.Document.OpenNew(true)方法,但這個方法會引發一些問題。詳細內容見下。 2)在設置DocumentText屬性之前將編輯模式改為浏覽模式,設置完後再將浏覽模式改為編輯模式。
2、監控Html內容的改變。
監控 WebBrowser 控件內容的改變
3、WebBrowse的Mouse事件。
WebBrowse本身沒有Mouse的相關事件,但是我們可以借助WebBrowse中的Body元素來模擬一些簡單的相關事件。
//在WinForm中注冊Web事件 //假設HTML源代碼如下: <html> <body> <input type="button" id="btnClose" value="關閉" /> </body> </html> // WinForm中注冊Web事件 HtmlDocument htmlDoc = webBrowser.Document; HtmlElement btnElement = htmlDoc.All["btnClose"]; if (btnElement != null) { btnElement.click += new HtmlElementEventHandler(HtmlBtnClose_Click); }
WebBrowse中更多的實用方法參見:C#WinForm WebBrowser (二) 實用方法總結
4、webBrowser1.Document.OpenNew(true)的Bug
相關鏈接: 1)追蹤導致 WebBrowser控件 編輯模式下 Ctrl + Z 失效的原因 【求大俠佐證,這算不算是微軟的Bug呢?】
追蹤導致 WebBrowser控件 編輯模式下 Ctrl + Z 失效的原因 【求大俠佐證,這算不算是微軟的Bug呢?】
導致Ctrl + Z失效的原因由以下2點連鎖引發而導致:
1、為了解決 WebBrowser 控件導航時彈出“保存對話框”,使用了 this.webBrowser.Document.OpenNew(true); // 防止 彈出保存對話框, 該方法指示新的文本改變將會在新窗口中打開。
2、 由原因1導致 webBrowser 控件的編輯模式失效, 表面上看上去還是可以編輯的,但實際上新窗口內部已經不支持編輯了。
注:這裡涉及到了WebBrowser控件的特殊性,它是由三層控件嵌套而成的,外面的兩層是大概負責容器、 及 響應用戶操作的, 而最內層的則是承載HTML標記,並通過渲染引擎展示HTML內容。用黑盒測試的方法推斷,當使用webBrowser.Document.OpenNew(true); 方法時,最內層控件應該是一個新的實例, 表面上看上去還是可以編輯的,但實際上內部的新窗口已經不支持編輯了,進而導致了Ctrl + Z的失效!
測試代碼如下:
public partial class FrmTest : Form { // 界面上有一個WebBrowser 和 4個Button private string strUrl = "http://www.cnblogs.com/08shiyan";
public FrmTest() { InitializeComponent(); }
/// <summary> /// 編輯模式 /// </summary> public void EditMode() { if (this.webBrowser1.Document != null) { mshtml.IHTMLDocument2 doc = this.webBrowser1.Document.DomDocument as mshtml.IHTMLDocument2; doc.designMode = "on"; } }
/// <summary> /// 啟用浏覽模式 /// </summary> public void BrowseMode() { if (this.webBrowser1.Document != null) { mshtml.IHTMLDocument2 doc = this.webBrowser1.Document.DomDocument as mshtml.IHTMLDocument2; doc.designMode = "off"; } }
// 請確保該按鈕是應用程序啟動後第一次被點擊 private void button1_Click(object sender, EventArgs e) { this.webBrowser1.DocumentText = string.Empty; this.webBrowser1.Document.Write(string.Format("<BODY>{0}我的誓言博客2</BODY>", this.strUrl)); this.EditMode();
this.webBrowser1.Document.OpenNew(true); this.webBrowser1.Document.Write(string.Format("<BODY>{0}我的誓言博客2</BODY>", this.strUrl));
// 注意此時Ctrl + Z 撤銷操作將會失效 }
// 請確保該按鈕是應用程序啟動後第一次被點擊 private void button2_Click(object sender, EventArgs e) { this.webBrowser1.DocumentText = string.Empty; this.webBrowser1.Document.Write(string.Format("<BODY>{0}我的誓言博客2</BODY>", this.strUrl)); this.EditMode();
this.webBrowser1.Document.OpenNew(true); this.webBrowser1.Document.Write(string.Format("<BODY>{0}我的誓言博客2</BODY>", this.strUrl));
this.EditMode(); // 與button1的差別是再次啟用編輯模式 // 注意此時Ctrl + Z 撤銷操作也會失效 }
// 請確保該按鈕是應用程序啟動後第一次被點擊 private void button3_Click(object sender, EventArgs e) { this.webBrowser1.DocumentText = string.Empty; this.webBrowser1.Document.Write(string.Format("<BODY>{0}我的誓言博客2</BODY>", this.strUrl)); this.EditMode();
this.webBrowser1.Document.OpenNew(true); this.webBrowser1.Document.Write(string.Format("<BODY>{0}我的誓言博客2</BODY>", this.strUrl));
this.BrowseMode(); // 與button2 的區別是 先關閉編輯模式,再啟用編輯模式 this.EditMode();
// 此時 Ctrl + Z 可以使用 }
// 重啟應用程序 private void button4_Click(object sender, EventArgs e) { Application.Restart(); }
}
根據以上得出結論: 在“編輯模式”下: this.webBrowser.Document.OpenNew(true); 方法會打開一個新的“內部窗口”,而新窗口中“編輯模式”出現問題,導致Ctrl + Z ,Ctrl +Y操作失效。 此時 需要先關閉 “編輯模式” 然後再打開“編輯模式” Ctrl + Z, Ctrl +Y 才能正常使用。
求佐證。。。
原創 轉載請標明出處: http://www.cnblogs.com/08shiyan
a1 = 0x01; //0000 0001
a2 = 0x00; //0000 0000
a3 = 0x03; //0000 0011
a4 = 0x02; //0000 0010
b1 = a1 ^ a2; //0000 0001
b2 = a1 ^ a3; //0000 0010
b3 = a1 ^ a4; //0000 0011
^異或運算符,位值相同為0,不同為1,見上示例.
//
簡單實際問題舉例:
======\=======\=======
======a=======b=======
上面是2條電路,2個開關分別為a和b,打開狀態:\[1],關閉狀態:/[0].
若同時打開或者關閉,兩條電路均不通.
若a打開[1],b關閉[0],電路1通電
======\=======/=======
若a關閉[0],b打開[1],電路2通電
======/=======\=======
綜上,電路在a,b狀態相同時不通[0],在a,b不同時通電[1].
a1 = 0x01; //0000 0001
a2 = 0x00; //0000 0000
a3 = 0x03; //0000 0011
a4 = 0x02; //0000 0010
b1 = a1 ^ a2; //0000 0001
b2 = a1 ^ a3; //0000 0010
b3 = a1 ^ a4; //0000 0011
^異或運算符,位值相同為0,不同為1,見上示例.
//
簡單實際問題舉例:
======\=======\=======
======a=======b=======
上面是2條電路,2個開關分別為a和b,打開狀態:\[1],關閉狀態:/[0].
若同時打開或者關閉,兩條電路均不通.
若a打開[1],b關閉[0],電路1通電
======\=======/=======
若a關閉[0],b打開[1],電路2通電
======/=======\=======
綜上,電路在a,b狀態相同時不通[0],在a,b不同時通電[1].