英文原文:http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/hosting/wbcustomization.asp
譯文出處:http://dev.csdn.net/article/19/19627.shtm
自定義浏覽器
本教程提供了自定義浏覽器控件的行為和外觀的一些方法。你将看到進階的宿主接口,IDocHostUIHandler, IDocHostUIHandler2, IDocHostShowUI, 和ICustomDoc。本文也讨論其他自定義方法,例如在宿主的IDispatch實作中處理DISPID_AMBIENT_DLCONTROL來進行下載下傳控制;以及使用IHostDialogHelper。
本文分為如下章節
- 前提和需求
- 介紹
- 浏覽器自定義架構
- IDocHostUIHandler
- IDocHostUIHandler2
- GetOptionKeyPath 和 GetOverrideKeyPath的比較
- 控制導航
- IDocHostShowUI
- 控制下載下傳和執行
- IHostDialogHelper
- 控制新的視窗
- 結論
前提和需求
為了了解和使用本教程,你需要
- 對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。使用某個特性之前,應該檢查參考文檔以獲得版本資訊。
介紹
內建浏覽器控件是快速軟體開發的強有力的工具。通過成為浏覽器的宿主,你可以利用便于使用的Dynamic HTML (DHTML), HTML, 和Extensible Markup Language (XML)來顯示資訊和開發一個使用者界面。但是,浏覽器控件的行為可能不确切符合你的需求。例如,預設的狀态允許使用者通過快捷菜單的檢視源代碼選項檢視一個顯示的頁面的源代碼,你可能需要禁用或者幹脆去掉這個選項。你可能更進一步,需要用你自己的快捷菜單替換預設的快捷菜單。
在剛剛提到的自定義特性之外,進階宿主特性允許
- 在顯示的頁面上的按鈕和其他控件可以調用你的應用程式的内建方法,有效地擴充DHTML對象模型(DOM)。
- 改變拖放的行為
- 限制浏覽器的導航,例如,限制于指定的頁面/域,或者站點
- 捕獲使用者鍵入,并且在需要的時候處理。比如說,你可能需要捕獲CTRL+O來阻止使用者在新的IE中打開網頁而不是使用你的程式打開,
- 改變預設字型和顯示設定
- 控制下載下傳内容,以及當下載下傳完成之後浏覽器的處理。例如,你可能禁用視訊的播放,腳本的執行,點選連結時打開新的視窗,或者Microsoft(R) ActiveX 控件的下載下傳和執行。
- 限制檢視源代碼
- 捕獲搜尋
- 捕獲導航錯誤
- 替代/修改快捷菜單或者禁用,替代,自定義,或者添加快捷菜單項
- 為你的應用程式改變系統資料庫設定
- 控制和修改浏覽器控件顯示的消息框
- 控制新視窗的建立方式
在下列節中,我們将會看到多數,但是不是全部的這些可能性而且讨論該如何實作他們。
浏覽器自定義架構
介紹 IDocHostUIHandler , IDocHostUIHander2 , IDocHostShowUI 和 ICustomDoc
下面三個接口是浏覽器控件使用者界面的自定義的核心: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
你使用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::GetExternal: 擴充文檔對象模型
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。
IDocHostShowUI::ShowMessage
傳回 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的文檔。
IDocHostShowUI::ShowHelp
這一個方法在當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
結論
你現在有許多技術,可以根據你的處理來自定義浏覽器控件。這個文章決不是沒有遺漏的,但是希望你現在可以自行發現超越本文的技術。檢查IE系統資料庫設定中那些你可以用IDocHostUIHandler::GetOptionKeyPath或IDocHostUIHandler2::GetOverrideKeyPath修改的資訊。記住許多系統資料庫設定互相依賴。你可能必須做一些實驗來發現系統資料庫設定可以多麼的有效地自定義;如果需要控制浏覽器控件的拖放行為,你也可以去看看IDocHostUIHandler::GetDropTarget。