ATL 实现定制的 IE 浏览器栏工具栏和桌面工具栏.docx
《ATL 实现定制的 IE 浏览器栏工具栏和桌面工具栏.docx》由会员分享,可在线阅读,更多相关《ATL 实现定制的 IE 浏览器栏工具栏和桌面工具栏.docx(19页珍藏版)》请在冰豆网上搜索。
ATL实现定制的IE浏览器栏工具栏和桌面工具栏
ATL实现定制的IE浏览器栏、工具栏和桌面工具栏
作者:
杨老师
下载源代码
关键字:
Band,DeskBand,ExplorerBand,ToolBand,浏览器栏,工具栏,桌面工具栏
一、引言
最近,由于工作的要求,我需要在IE上做一些开发工作。
于是在MSDN上翻阅了一些资料,根据MSDN上的说明我用ATL胜利完成了“资本家老板”分配的任务。
(并且在白天睡觉的过程中梦到了老板给我加工资啦......)
现在,我把MSDN上的原文资料,经过翻译整理并把一个ATL的实现奉贤给VCKBASE上的朋友们。
∙概念
∙原理
基本band对象
必须实现的COM接口
IPersistStream
IObjectWithSite
IDeskBand、IDockingWindow、IOleWindow
选择实现的COM接口
Band对象注册
∙ATL实现
二、概念
在翻译的过程中,有两个词汇非常不好理解。
第一个词是Band对象,词典中翻译为“镶边、裙子边、带子、乐队......”我的英文水平有限,实在不知道应该翻译为什么词汇更合适。
于是我毅然决然地决定:
在如下的论述中,依然使用band这个词!
(什么?
没听明白?
我的意思就是说,我不翻译这个词了)但到底Band对象应该如何理解那?
请看图一:
图一
图一中画红圈的地方,分别称作“垂直的浏览器栏”、“水平的浏览器栏”、“工具栏”和“桌面工具栏”。
这些“栏”,都可以在IE的“查看”菜单中或鼠标右键的上下文快捷方式菜单中显示或隐藏起来。
这些界面窗口的实现,其实就是实现一种COM接口对象,而这个对象叫band。
这个概念实在是只能意会而无法言传的,我总不能在文章中把它翻译为“总是靠在IE主窗口边上的对象”吧?
^_^
另外,还有一个词叫site。
这个很好翻译,叫“站点”!
。
呵呵,我敢打包票,如果你要能理解这个翻译在计算机类文章中的含义,那就只能恭喜你了,你的智慧太高了。
(都是学计算机软件的人,做人的差距咋就这么大呢?
)在本篇文章中,site可以这样理解:
IE的主框架四周,就好比是“汽车站”,那些band对象,就好比是“汽车”。
band汽车总是可以停靠在“汽车站”上。
所以,site就是“站点”,它也是COM接口的对象(IObjectWithSite、IInputObjectSite)。
三、原理
3.1 基本band对象
Band对象,从Shell4.71(IE5.0)开始提供支持。
Band是一个COM对象,必须放在一个容器中去使用,当然使用它们就好象使用普通窗口是一样的。
IE就是一个容器,桌面Shell也是一个容器,它们提供不同的函数功能,但基本的实现是相似的。
Band对象分三种类型,浏览器栏band(Explorerbands)、工具栏band(ToolBands)和桌面工具栏(Deskbands),而浏览器栏band又有两种表现形式:
垂直和水平的。
那么IE和Shell如何区分并加载这些bands对象呢?
方法是:
你要对不同的band对象,在注册表中注册不同的组件类型(CATID)。
Band样式
组件类型
CATID
垂直的浏览器栏
CATID_InfoBand
00021493-0000-0000-C000-000000000046
水平的浏览器栏
CATID_CommBand
00021494-0000-0000-C000-000000000046
桌面的工具栏
CATID_DeskBand
00021492-0000-0000-C000-000000000046
IE工具栏不使用组件类型注册,而是使用在注册进行CLSID的登记方式。
详细情况见3.3。
在例子程序中,实现了全部四个类型的band对象,垂直浏览器栏(CVerticalBar)显示了一个HTML文件,并且实现了对IE主窗口浏览网页的导航等功能;水平的浏览器栏(CHorizontalBar)是一个编辑窗,它同步显示当前网页的BODY源文件内容;IE工具栏(CToolBar)最简单,只是添加了一个空的工具栏;桌面工具栏(CDeskBar)实现了一个单行编辑窗口,你可以在上面输入命令行或文件名称,回车后它会执行Shell的打开动作。
3.2 必须实现的COM接口
Band对象是IE或Shell的进程内服务器,所以它被包装在DLL中。
而作为COM对象,它必须要实现IUnknown和IClassFactory接口。
(大家可以不同操心,因为我们用ATL写程序,这两个接口是不用我们自己写代码的。
)另外,Band对象还必须实现IDeskBand、IObjectWithSite和IPersistStream三个接口:
IPersistStream是持续性接口的一种。
当IE加载band对象的时候,它通过这个接口的Load方法传递属性值给对象,让其进行初始化;而当卸载前,IE则调用这个接口的Save方法保存对象的属性。
用ATL实现这个接口很简单:
classATL_NO_VTABLECxxx:
......
publicIPersistStreamInitImpl,//添加继承
......
{
public:
BOOLm_bRequiresSave;//IPersistStreamInitImpl所必须的变量
......
BEGIN_COM_MAP(CVerticalBar)
......
COM_INTERFACE_ENTRY2(IPersist,IPersistStreamInit)
COM_INTERFACE_ENTRY2(IPersistStream,IPersistStreamInit)
COM_INTERFACE_ENTRY(IPersistStreamInit)
......
END_COM_MAP()
BEGIN_PROP_MAP(Cxxx)
......//添加需要持续性的属性
END_PROP_MAP()
上面的代码,其实实现的是IPersistStreamInit接口,不过没有关系,因为IPersistStreamInit派生自IPersistStream,实例化了派生类,自然就实例化了基类。
在例子程序中,我只在桌面工具栏对象中添加了持续性属性,用来保存和初始化“命令行”。
另外COM_INTERFACE_ENTRY2(A,B)表示的含义是:
如果想查询A接口的指针,则提供B接口指针来代替。
为什么可以这样那?
因为B接口派生自A接口,那么B接口的前几个函数必然就是A接口的函数了,自然B接口的地址其实和A接口的地址是一样的了。
IObjectWithSite是IE用来对插件进行管理和通讯用的一个接口。
必须要实现这个接口的2个函数:
SetSite()和GetSite()。
当IE加载band对象和释放band对象的时候,都要调用SetSite()函数,那么在这个函数里正好是写初始化和释放操作代码的地方:
STDMETHODIMPCxxx:
:
SetSite(IUnknown*pUnkSite)
{
if(NULL==pUnkSite)//释放band的时候
{
//如果加载的时候,保存了一些接口
//那么现在:
释放它
}
else//加载band的时候
{
m_hwndParent=NULL;//装载band的父窗口(就是带有标题的那个框架窗口)
//这个窗口的句柄,是调用IUnknown:
:
QueryInterface()得到IOleWindow
//然后调用IOleWindow:
:
GetWindow()而获得的。
CComQIPtrspOleWindow(pUnkSite);
if(spOleWindow)spOleWindow->GetWindow(&m_hwndParent);
if(!
m_hwndParent)returnE_FAIL;
//现在,正好是建立子窗口的时机。
//注意,子窗口建立的时候,不要使用WS_VISIBLE属性
......
//在例子程序中,用CAxWindow实现了一个能包容ActiveX的容器窗口(垂直浏览器栏)
//在例子程序中,用WINAPI函数CreateWindow实现了标准窗口(水平浏览器栏、工具栏)
//在例子程序中,用CWindowImpl实现了一个包容窗口(桌面工具栏)
/*********************************************************/
以下部分,根据band对象特有的功能,是可以选择实现的
**********************************************************/
//如果子窗口实现了用户输入,那么必须实现IInputObject接口,
//而该接口是被IE的IInputObjectSite调用的,因此在你的对象
//中,应该保存IInputObjectSite的接口指针。
//在类的头文件中,定义:
//CComQIPtrm_spSite;
m_spSite=pUnkSite;//保存IInputObjectSite指针
if(!
m_spSite)returnE_FAIL;
//你需要控制IE的主框架吗?
//那么在类的头文件中,定义:
//CComQIPtrm_spFrameWB;
//然后,先取得IServiceProvider,再取得IWebBrowser2
CComQIPtrspSP(pUnkSite);
if(!
spSP)returnE_FAIL;
spSP->QueryService(SID_SWebBrowserApp,&m_spFrameWB);
if(!
m_spFrameWB)returnE_FAIL;
//如果你取得了IE主框架的IWebBrowser2指针
//那么,当它发生了什么事情,你难道不想知道吗?
//定义:
CComPtrm_spCP;
CComQIPtr&IID_IConnectionPointContainer>spCPC(m_spFrameWB);
if(spCPC)
{
spCPC->FindConnectionPoint(DIID_DWebBrowserEvents2,&m_spCP);
if(m_spCP)
{
m_spCP->Advise(reinterpret_cast(this),&m_dwCookie);
}
}
//咳~~~不说了,看源码去吧。
这里能干的事情太多了......
}
returnS_OK;
}
IDeskBand是一个特殊的band对象接口,有一个方法函数:
GetBarInfo();
IDockingWindow是IDeskBank的基类,有3个方法函数:
ShowDW()、CloseDW()、ResizeBorderDW();
IOleWindow又是IDockingWindow的基类,有2个方法函数:
GetWindow()、ContextSensitiveHelp();
首先声明IDeskBand,然后要实现IDeskBand接口的共6个函数,这些函数比较简单,不同类型的band对象,其实现方法也都基本一致:
classATL_NO_VTABLECxxx:
......
publicIDeskBand,
......
{
......
BEGIN_COM_MAP(Cxxx)
......
COM_INTERFACE_ENTRY_IID(IID_IDeskBand,IDeskBand)
......
END_COM_MAP()
//IOleWindow
STDMETHODIMPCxxx:
:
GetWindow(HWND*phwnd)
{//取得band对象的窗口句柄
//m_hWnd是建立窗口时候保存的
*phwnd=m_hWnd;
returnS_OK;
}
STDMETHODIMPCxxx:
:
ContextSensitiveHelp(BOOLfEnterMode)
{//上下文帮助,参考IContextMenu接口
returnE_NOTIMPL;
}
//IDockingWindow
STDMETHODIMPCVerticalBar:
:
ShowDW(BOOLbShow)
{//显示或隐藏band窗口
if(m_hWnd)
:
:
ShowWindow(m_hWnd,bShow?
SW_SHOW:
SW_HIDE);
returnS_OK;
}
STDMETHODIMPCVerticalBar:
:
CloseDW(DWORDdwReserved)
{//销毁band窗口
if(:
:
IsWindow(m_hWnd))
:
:
DestroyWindow(m_hWnd);
m_hWnd=NULL;
returnS_OK;
}
STDMETHODIMPCVerticalBar:
:
ResizeBorderDW(LPCRECTprcBorder,IUnknown*punkToolbarSite,BOOLfReserved)
{//当框架窗口的边框大小改变时
returnE_NOTIMPL;
}
//IDeskBand
STDMETHODIMPCVerticalBar:
:
GetBandInfo(DWORDdwBandID,DWORDdwViewMode,DESKBANDINFO*pdbi)
{
//取得band的基本信息,你需要填写pdbi参数作为返回
if(NULL==pdbi)returnE_INVALIDARG;
//如果将来需要调用IOleCommandTarget:
:
Exec()则需要保存这2个参数
m_dwBandID=dwBandID;
m_dwViewMode=dwViewMode;
if(pdbi->dwMask&DBIM_MINSIZE)
{//最小尺寸
pdbi->ptMinSize.x=10;
pdbi->ptMinSize.y=10;
}
if(pdbi->dwMask&DBIM_MAXSIZE)
{//最大尺寸(-1表示4G)
pdbi->ptMaxSize.x=-1;
pdbi->ptMaxSize.y=-1;
}
if(pdbi->dwMask&DBIM_INTEGRAL)
{
pdbi->ptIntegral.x=1;
pdbi->ptIntegral.y=1;
}
if(pdbi->dwMask&DBIM_ACTUAL)
{
pdbi->ptActual.x=0;
pdbi->ptActual.y=0;
}
if(pdbi->dwMask&DBIM_TITLE)
{//窗口标题
wcscpy(pdbi->wszTitle,L"窗口标题");
}
if(pdbi->dwMask&DBIM_MODEFLAGS)
{
pdbi->dwModeFlags=DBIMF_VARIABLEHEIGHT;
}
if(pdbi->dwMask&DBIM_BKCOLOR)
{//如果使用默认的背景色,则移除该标志
pdbi->dwMask&=~DBIM_BKCOLOR;
}
returnS_OK;
}
3.3 选择实现的COM接口
有两个接口不是必须实现的,但也许很有用:
IInputObject和IContextMenu。
如果band对象需要接收用户的输入,那么必须实现IInputObject接口。
IE实现了IInputObjectSite接口,当容器中有多个输入窗口时,它调用IInputObject接口方法去负责管理用户的输入焦点。
在浏览器栏中需要实现3个函数:
UIActivateIO()、HasFocusIO()、TranslateAcceleratorIO()。
当浏览器栏激活或失去活性的时候,IE调用UIActivateIO函数,当激活的时候,浏览器栏一般调用SetFocus去设置它自己窗口的焦点。
当IE需要判断哪个窗口有焦点的时候,它调用HasFocusIO。
当浏览器栏的窗口或其子窗口有输入焦点时,则应返回S_OK,否则返回S_FALSE。
TranslateAcceleratorIO允许对象处理加速键,例子程序中没有实现,所以直接返回S_FALSE。
STDMETHODIMPCExplorerBar:
:
UIActivateIO(BOOLfActivate,LPMSGpMsg)
{
if(fActivate)
SetFocus(m_hWnd);
returnS_OK;
}
STDMETHODIMPCExplorerBar:
:
HasFocusIO(void)
{
if(m_bFocus)
returnS_OK;
returnS_FALSE;
}
STDMETHODIMPCExplorerBar:
:
TranslateAcceleratorIO(LPMSGpMsg)
{
returnS_FALSE;
}
Band对象能够通过包容器的IOleCommandTarget:
:
Exec()调用执行命令。
而IOleCommandTarget接口指针,则可以通过调用包容器的IInputOjbectSite:
:
QueryInterface(IID_IOleCommandTarget,...)函数得到。
CGID_DeskBand是命令组,当一个band对象的GetBandInfo被调用的时候,包容器通过dwBandID参数指定一个ID给band对象,对象要保存住这个ID,以便调用IOleCommandTarget:
:
Exec()的时候使用。
ID的命令有:
∙DBID_BANDINFOCHANGED
Band的信息变化。
设置参数pvaIn为bandID,该ID就是最近一次调用GetBandInfo所得到的值,容器会调用band对象的GetBandInfo函数来更新请求信息。
∙DBID_MAXIMIZEBAND
最大化band。
设置参数pvaIn为bandID,该ID就是最近一次调用?
GetBandInfo?
所得到的值。
∙DBID_SHOWONLY
打开或关闭容器中其它的bands。
设置参数pvaIn为VT_UNKNOWN类型,它可以是如下的值:
值
描述
pUnk
band对象的IUnknown指针,其它的桌面bands将被隐藏
0
隐藏所有的桌面bands
1
显示所有的桌面bands
∙
∙DBID_PUSHCHEVRON
在菜单项左边显示“v”的选择标志。
容器发送一个RB_PUSHCHEVRON消息,当band对象接收到通知消息RBN_CHEVRONPUSHED提示它显示一个"v"的标志。
设置IOleCommandTarget:
:
Exec函数中nCmdExecOpt参数为bandID,该ID是最近一次调用GetBandInfo?
所得到的值,设置IOleCommandTarget:
:
Exec函数中pvaIn参数为VT_I4类型,这是应用程序定义的一个值,它通过通知消息RBN_CHEVRONPUSHED中lAppValue回传给band对象。
3.4 Band对象注册
Band对象必须注册为一个OLE进程内的服务器,并且支持apartment线程公寓。
注册表中默认键的值是表示菜单的文字。
对于浏览器栏,它加到IE菜单的“查看\浏览器栏”中;对于工具栏band,它加到IE菜单的“查看\工具栏”中;对于桌面band,它加到系统任务栏的快捷菜单中。
在菜单资源中,可以使用“&”指明加速键。
通常,一个基本的band对象的注册表项目是:
HKEY_CLASSES_ROOT
CLSID
{你的band对象的CLSID}
(Default)=菜单的文字
InProcServer32
(Default)=DLL的全路径文件名
ThreadingModel=Apartment
工具栏bands还必须把它们的CLSID注册到IE的注册表中。
在HKEY_LOCAL_MACHINE\Software\Microsoft\InternetExplorer\Toolbar下给出CLSID作为键名,而其键值是被忽略的。
HKEY_LOCAL_MACHINE
Software
Microsoft
InternetExplorer
Toolbar
{你的band对象的CLSID}
还有几个可选的注册表项目(例子程序并不是这样实现的)。
比如,你想让浏览器栏显示HTML的话,必须要如下设置注册表:
HKEY_CLASSES_ROOT
CLSID
{你的Band对象的CLSID}
Instance
CLSID
(Default)={4D5C8C2A-D075-11D0-B416-00C04FB90376}
同时,如果要指定一个本地的HTML文件,那么要如下设置:
HKEY_CLASSES_R