VC中自定义IE浏览器
http://www.5xsoft.com/Article.aspx?id=155
本教程提供了自定义浏览器控件的行为和外观的一些方法。你将看到高级的宿主接口, IDocHostUIHandler, IDocHostUIHandler2, IDocHostShowUI, 和ICustomDoc。本文也讨论其他自定义方法,例如在宿主的IDispatch实现中处理DISPID_AMBIENT_DLCONTROL来进行 下载控制;以及使用IHostDialogHelper。
本文分为如下章节
为了理解和使用本教程,你需要
许多自定义特性是在IE5或者5.5版本就可以使用的,但是有几个特性需要IE6。使用某个特性之前,应该检查参考文档以获得版本信息。
集成浏览器控件是快速软件开发的强有力的工具。通过成为浏览器的宿主,你可以利用便于使用的Dynamic HTML (DHTML), HTML, 和Extensible Markup Language (XML)来显示信息和开发一个用户界面。但是,浏览器控件的行为可能不确切符合你的需求。例如,默认的状态允许用户通过快捷菜单的查看源代码选项查看一 个显示的页面的源代码,你可能需要禁用或者干脆去掉这个选项。你可能更进一步,需要用你自己的快捷菜单替换默认的快捷菜单。
在刚刚提到的自定义特性之外,高级宿主特性允许
在下列节中,我们将会看到多数,但是不是全部的这些可能性而且讨论该如何实现他们。
下面三个接口是浏览器控件用户界面的自定义的核心: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自IE5以后已经是可用的。它提供15个方法。大体上,一些较重要的方法是 IDocHostUIHandler::GetExternal, IDocHostUIHandler::GetHostInfo, IDocHostUIHandler::GetOptionKeyPath, IDocHostUIHandler::ShowContextMenu, 和 IDocHostUIHandler::TranslateAccelerator。当然,方法对你的重要性将会依赖于你的应用程序。
你使用IDocHostUIHandler::GetHostInfo告诉MSHTML有关你的应用程序的能力和需求。通过它你能控制很多东西, 举例来说:
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 。
这一个方法在当IE需要显示帮助时被调用,举例来说当 F1 键被按下时,而且工作方式和IDocHostShowUI::ShowMessage类似。返回S_OK覆盖IE的帮助,或另外的HRESULT值让IE执行自己的帮助。
浏览器控件给你它的下载,显示设置和执行的控制权。 为了要得到这些控制,你实现你的宿主的IDispatch接口,使得它处理DISPID_AMBIENT_DLCONTROL。当浏览器控件被实例化的时候,它将会以这一个ID调用你的IDispatch::Invoke。将pvarResult设置为下列的标识的一个位与的组合,指明你的配置。
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::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。
[VC/C语言] VC中自定义IE浏览器(1) [VC/C语言] VC中自定义IE浏览器(2) [VC/C语言] 一个即时通信软件QTALK [VC/C语言] 仿QQ悬挂窗口的实现 [VC/C语言] 内存映射修改大文件 [VC/C语言] VC++下编译出极小的程序 [VC/C语言] WINDOWS键盘事件的挂钩监控原理及其应用技术 [VC/C语言] 用C语言实现Ping程序功能 [VC/C语言] VC下显示JPEG、GIF图像的简便方法 [VC/C语言] Win32环境下动态链接库(DLL)编程原理