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将会传递网页上对任何外部对象的调用到你的应用程序的自动化方法:
你也能使用这技术传递整个对象到一个网页。为了实现它,在你的IDispatch实现中创建一个方法,传递回你的网页可以用的对象。
可以看看示例代码中使用 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 spBrowser; CComPtr 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: 不运行客户端重定位页面操作(译者注:例如 的默认行为)。 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。