自定义浏览器
本教程提供了自定义浏览器控件的行为和外观的一些方法。你将看到高级的宿主接口,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'";
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)
{
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;
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 ;
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" 的资源标识
// 载入 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。