除了可以向IE中添加自定义菜单外,我们q可以向IE的工h上添加自定义的按钮。自定义按钮同自定义菜单COM扩展的实现几乎一P 除了在注册时需要添加的注册表项不同?注意Q同菜单扩展一P自定义的按钮扩展也必?ins cite="mailto:Han%20Lei" datetime="2003-09-11T19:16">?/ins>IE5及以后的版本才支持? 创徏COMlg 下面我们要创建的IE工具条按钮要E微复杂一些,当点LQ不再只是显CZ个简单的对话框了Q而是让当前浏览器览我的个h|站 http://hubdog.csdn.net。同前一节一P 首先创徏ActiveX LibraryQ保存ؓIEButton.dprQ然后再新徏一个名为TIEHomeButton?COM ObjectQ保存向导生成的文g为CIEButton.pas? 同样的按钮扩展也需要实现IOleCommandTarget接口Q同时ؓ了能?a name="baidusnap1">调用IE的功能,?b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用览器浏览指定的|址Q我们还需要实?IObjectWithSite接口。完成的cȝ定义如下Q?
type
TIEHomeButton = class(TComObject, IOleCommandTarget, IObjectWithSite)
private
ShellBrowser: IShellBrowser;
IE:IWebBrowser;
protected
//IOleCommandTarget接口定义
function QueryStatus(CmdGroup: PGUID; cCmds: Cardinal;
prgCmds: POleCmd; CmdText: POleCmdText): HResult; stdcall;
function Exec(CmdGroup: PGUID; nCmdID, nCmdexecopt: DWORD;
const vaIn: OleVariant; var vaOut: OleVariant): HResult; stdcall;
//IObjectWithSite接口定义
function SetSite(const pUnkSite: IUnknown): HResult; stdcall;
function GetSite(const riid: TIID; out site: IUnknown): HResult; stdcall;
end;
其中IObjectWithSite接口有SetSite?b style="COLOR: black; BACKGROUND-COLOR: #ffff66">GetSiteҎ。其中IE会在W一ơ加载工h按钮扩展?b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用SetSiteQ将览器的function TIEHomeButton.SetSite(const pUnkSite: IInterface): HResult;
var
Service:IServiceProvider;
begin
ShellBrowser := pUnkSite as IShellBrowser;
Service:=ShellBrowser as IServiceProvider;
Service.QueryService(IWebBrowserApp,IWebBrowser2, IE);
Result := S_OK;
end;
IE同时q会不时?b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用GetSiteҎ来从我们保存的pUnkSite接口获得指定的riid的接口,
function TIEHomeButton.GetSite(const riid: TIID;
out site: IInterface): HResult;
begin
if Supports(ShellBrowser, riid, site) then
Result := S_OK
else
Result := E_NOTIMPL;
end;
如果pUnkSite指针支持该接口,则返回S_OKQ否则返回E_最后,我们需要在
IOleCommandTarget
接口中实?/code>Exec
Ҏ来执行浏览网站的功能Q?/code>
IWebBrowser2接口的NavigateҎ可以多个参数Q?q里我们只需要指定要览的Url可以了Q其它参数都讄为空Q用EmptyParam预定义|?
function TIEHomeButton.Exec(CmdGroup: PGUID; nCmdID, nCmdexecopt: DWORD;
const vaIn: OleVariant; var vaOut: OleVariant): HResult;
begin
Result := S_OK;
IE.Navigate(''http://hubdog.csdn.net'', emptyParam,emptyParam,emptyParam,emptyParam);
end;
注册扩展
要想让IE能够在启动后正确昄自定义的工具条按钮扩展,需要在注册表中填写一些配|信息?
1. 首先要在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Extensions\目下新Z个关键字Q名为扩展的Guid的字W串形式?
2. 然后在新建的HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Extensions\<扩展的Guid>关键字下再创Z个名为CLSID 目Q设定gؓ{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}?
3. 然后在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Extensions\<扩展Guid>关键字下d名ؓClsidExtension的字D,q回gؓ按钮扩展的Guid的字W串形式?
4. 默认Ӟ一个新加的扩展按钮不会马上昄在工h上,但是我们可以在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Extensions\<扩展Guid>关键字下dDefault Visible字段Qƈ讑֮其gؓYesQ这样IE启动时会自动昄我们d的按钮,但是要注意如果用户在d按钮前用了工具?右键菜单中的自定义…命令调整过工具条按钮的昄讄Q则我们的扩展按钮不会自动出CQ必L通过自定义对话框来手工添加或?b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用重置按钮恢复默认讄来显C添加的按钮Q见下图Q?
大小Q像素) | CZ |
20x20 | ![]() |
16x16 | ![]() |
注册q程
接下来是~写后的注册代码Q都是一些对注册表的操作,注意删除时是ҎGuidq行删除的,因ؓGuid是唯一的|
//d工具条按?
procedure AddToolbarBtn(Visible: Boolean; BtnText, HotIcon,
Icon, Guid: string);
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
with Reg do
try
RootKey := HKEY_LOCAL_MACHINE;
OpenKey(''\Software\Microsoft\Internet Explorer\Extensions\'' + Guid, True);
if Visible then
WriteString(''Default Visible'', ''Yes'')
else
WriteString(''Default Visible'', ''No'');
WriteString(''ButtonText'', BtnText);
WriteString(''HotIcon'', HotIcon);
WriteString(''Icon'', Icon);
WriteString(''CLSID'', ''{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}'');
WriteString(''ClsidExtension'', Guid);
CloseKey;
finally
Free;
end;
end;
//按Guid删除按钮
procedure RemoveToolbarBtn(Guid: string);
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
with Reg do
begin
RootKey := HKEY_LOCAL_MACHINE;
DeleteKey(''\Software\Microsoft\Internet Explorer\Extensions\'' + Guid);
free;
end;
end;
然后~写COMlg工厂c,调用注册和删除注册表的Ҏ来实现COMlg的注册和反注册:
type
TIEHomeButtonFactory = class(TComObjectFactory)
public
procedure UpdateRegistry(Register: Boolean); override;
end;
?/code>function GetDllName: string;
var
Buffer: array[0..261] of Char;
begin
GetModuleFileName(HInstance, Buffer, SizeOf(Buffer));
Result := string(Buffer);
end;
procedure TIEHomeButtonFactory.UpdateRegistry(Register: Boolean);
begin
inherited;
if Register then
AddToolbarBtn(true, ''HomeButton'', GetDllName+'',1234'', GetDllName+'',1234'', GuidToString(classid))
else
RemoveToolbarBtn(GuidToString(classid));
end;
的图标作?br />按钮图标Q这里ؓ了简便v见,
HotIcon
?/code>Icon
使用的是同一个图标?/code>
调用览器的功能完成我们的需要,通过调用览器的接口我们可以实现一些更加实用更加复杂的功能Q后面我们将q一步探讨?img src ="http://www.shnenglu.com/yishanhante/aggbug/19751.html" width = "1" height = "1" />
]]>
图一 在浏览栏中可以创建很多子菜单或选项Q用戯以不同方式选择q些子菜单或选项提供的功能,打开IE或者资源管理器Q从“查看”菜单中选择“浏览栏”,可以看到Windows提供了几U标准的览栏菜单,如“搜索(SearchQ?“收藏夹QFavoritesQ”, 和“历史记录(HistoryQ?以及“文件夹QAll FoldersQ”。(如图二) |
图二 Z创徏定制的浏览栏Q必ȝE实玎ͼ然后注册它们。Windows在外壻IShellQ?.71中引入了区对象。它提供与普通窗口一L功能。但因ؓ它是以IE或外壳ؓ容器的COM对象Q所以实现v来就与普通窗口有所不同。图一中显C的是一个简单的览栏例子。图中有一个垂直的览栏和一个水q的览栏?br /> 工具栏区对象 工具栏区对象U工hQ它是在IE5.0中引入用以支持单选工hQradio toolbarQ特性的。IE工具栏实际上是一个Rebar控gQ它包含了几个工hQtoolbarQ控件。通过创徏工具栏,你可以将某个区对象功能添加到Rebar控g中。不论是在IE中还是在资源理器中Q区对象都是一LQ所以工h也是一个通用H口。(如图三) ![]() 图三 用户可以从“查看”菜单中的“工h”子菜单中选择昄单选工hQ也可以在工h区域单击鼠标右键从它的上下文菜单中选择昄单选工h?br /> 囑֛ 桌面区的初始动位置在Q务栏Q(如图?br /> 图五 用户可以桌面区拖到桌面上,q时它就成了一个普通窗口:Q如囑օQ?br /> 囑օ |
二、实现区对象
管可以像用普通窗口一样用区对象Q但它们毕竟是COM对象Q存在于某个容器之中。如览栏和工具栏位于IE之中Q桌面区位于外壳之中。虽然它们的功能不同Q但其基本实现非常相伹{一个主要的差别是它们的注册方式不同Q而注册方式的不同又决定了对象的类型及其容器。这一部分我们先讨论所有区对象实现的共性。其它的实现l节可参?a target="_self">垂直览栏例子程?/a>?br />区对象除了要实现 IUnknown ?IClassFactory 两个接口之外Q所有的区对象还必须实现以下q几个接口:
对于如何注册区对象的q一步讨参见注册部分?br />如果某个区对象接受用戯入,它还必须实现IInputObject接口。如果要往上下文菜单中d菜单目Q还必须实现IContextMenu接口。注意:工具栏区对象不支持上下文菜单?br /> 因ؓ区对象实现的是子H口Q所以它们还必须有窗口过E来处理Windows的消息?br /> 区对象可以通过其IOleCommandTarget接口发送命令到它的容器。ؓ了得到这个接口的指针Q必调用容器的IInputObjectSite::QueryInterfaceҎ DBID_BANDINFOCHANGED——Band的信息已改变。参数pvaIn的值应该是最q一ơ调用所用的band标示W。容器将调用q个标示W所指的band对象的IDeskBand::GetBandInfoҎh更新的信息?br />DBID_MAXIMIZEBAND——容器将最大化band。参数pvaIn的值应该是最q一ơ调用所用的band标示W?br />DBID_SHOWONLY——关闭或打开容器中其它band。参数pvaIn的gؓVT_UNKNOWNcdQ可以取下列g一Q?br />
注册 区对象必ME内服务器(in-processQ注册。其U程模型必须为“Apartment”。也是说区对象必须以DLL的Ş式来实现。用来描q服务器注册条目的缺省值是一个菜单文本串。就拿浏览栏来说。这个菜单出现在资源理器或IE “查看(ViewQ”菜单的“浏览栏QExplorer BarQ”子菜单中。而工h的菜单则出现在资源管理器或IE “查看(ViewQ”菜单的“工hQToolbarsQ”子菜单中。桌面区出现在Q务栏上下文菜单的“工hQToolbarsQ”子菜单中。作单资源,提供键盘快捷的方法与一般菜单快捷键相同。也是?amp;”字W放在某个单词字母前表示q个字母昄下划U来指示快捷键?br />通常区对象的注册条目如下: HKEY_CLASSES_ROOT ... CLSID ... {Band 对象?CLSID GUID} = "菜单文本? InProcServer32 = "DLL 路径? ThreadingModel = "Apartment"工具栏区对象必须q要注册对象的CLSID。ؓ此必dHKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar下创Z个REG_SZ|用工h区对象的CLSID GUID串命名。如Q?br /> HKEY_LOCAL_MACHINE Software Microsoft Internet Explorer Toolbar { Band 对象?CLSID GUID } 除此之外Q还有几个可选的注册值可以加到注册表中,本文的例子中未用这些倹{?
HKEY_CLASSES_ROOT ... CLSID ... {Band 对象?CLSID GUID} = "菜单文本? InProcServer32 = "DLL 路径? ThreadingModel = "Apartment" Instance CLSID = "{4D5C8C2A-D075-11D0-B416-00C04FB90376}" InitPropertyBag Url = "HTML文g" ... HKEY_CURRENT_USER ... Software ... Microsoft ... Internet Explorer ... Explorer Bars { Band 对象?CLSID GUID } BarSize = "23,01,00,00,00,00,00,00" 你可以通过~程的方式来处理区对象类?CATID 的注册。创Z个组件类别管理器对象(CLSID_StdComponentCategoriesMgr)q请求一个指向ICatRegister接口的指针。将区对象的CLSID和CATID传递到ICatRegister::RegisterClassImplCategories?br />三、定制浏览栏的一个简单例?/a> q个例子展示了前面所介绍q的垂直览栏的整个实现q程。它借助了^台SDKQPlatform SDK——在msdn中可以找刎ͼ中关于band对象C代码。其中还包括了水qx览栏和桌面band的实C码。详l实现细节请参见QCommBand.cpp和DeskBand.cpp?br />创徏定制览栏的基本q程是这LQ?br />
DLL函数 所有三U区对象被打包在一个DLL中,它输Z下的函数Q?br />
注册定制的浏览栏 有了COM对象后,必须Ҏ览栏的CLSIDq行注册。另外如果要与IE或资源管理器 协调q行Q还必须q行的恰当的lgU类QCATID_InfoBandQ注册。这个工作由DllRegisterServer处理。浏览栏例子代码有关的处理部分如下: ... //注册览栏对? if(!RegisterServer(CLSID_SampleExplorerBar, TEXT("垂直览栏例?))) return SELFREG_E_CLASS; //注册览栏的对象lgU类 if(!RegisterComCat(CLSID_SampleExplorerBar, CATID_InfoBand)) return SELFREG_E_CLASS; ...区对象的注册使用通常的COMq程Q它q有函数RegisterServer处理?br />除了CLSID之外Q这个区对象服务器还必须注册一个以上的lgU类。这实际上是垂直览栏和水^览栏实C间的主要差别。这个过E的处理是通过创徏一个组件种cȝ理器对象QCLSID_StdComponentCategoriesMgrQ,q用ICatRegister::RegisterClassImplCategoriesҎ来注册区对象服务器。在q个例子中,lgU类注册的处理是通过浏览栏的CLSID和CATID传递到U有函数RegisterComCat完成的: BOOL RegisterComCat(CLSID clsid, CATID CatID) { ICatRegister *pcr; HRESULT hr = S_OK ; CoInitialize(NULL); hr = CoCreateInstance( CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (LPVOID*)&pcr); if(SUCCEEDED(hr)) { hr = pcr->RegisterClassImplCategories(clsid, 1, &CatID); pcr->Release(); } CoUninitialize(); return SUCCEEDED(hr); } |
IUnknown 构造函敎ͼ析构函数和IUnknown实现比较单,本文在此不讨论。细节请参见源代码?br />IObjectWithSite接口 当用户选择某个览栏时Q容器调用相应band对象的IObjectWithSite::SetSiteҎ。参数将被设|成q个现场QSiteQ的IUnknown指针?br />通常QSetSite实现应该完成下列步骤Q?br />
STDMETHODIMP CExplorerBar::SetSite(IUnknown* punkSite) { //如果某个现场被把持,则释攑֮ if(m_pSite) { m_pSite->Release(); m_pSite = NULL; } //如果punkSite 不ؓNULL, 建立一个新的现? if(punkSite) { //获取父窗? IOleWindow *pOleWindow; m_hwndParent = NULL; if(SUCCEEDED(punkSite->QueryInterface(IID_IOleWindow, (LPVOID*)&pOleWindow))) { pOleWindow->GetWindow(&m_hwndParent); pOleWindow->Release(); } if(!m_hwndParent) return E_FAIL; if(!RegisterAndCreateWindow()) return E_FAIL; //获取柄保存IInputObjectSite指针 if(SUCCEEDED(punkSite->QueryInterface(IID_IInputObjectSite, (LPVOID*)&m_pSite))) { return S_OK; } return E_FAIL; } return S_OK; }q个例子的GetSite只简单地用SetSite保存的现场指针实C对现场QueryInterfaceҎ的调用?br /> STDMETHODIMP CExplorerBar::GetSite(REFIID riid, LPVOID *ppvReturn) { *ppvReturn = NULL; if(m_pSite) return m_pSite->QueryInterface(riid, ppvReturn); return E_FAIL; }H口创徏q有方法RegisterAndCreateWindow负责。如果这个窗口不存在Q此Ҏ浏览栏H口创徏成一个大适当的子H口Q它的父H口是由SetSite获得的那个窗口。子H口的句柄存储在m_hwnd变量中?br /> BOOL CExplorerBar::RegisterAndCreateWindow(void) { //如果q个H口不存在,则创建它 if(!m_hWnd) { //子窗口不能没有父H口 if(!m_hwndParent) { return FALSE; } //如果H口cL有注册,则必L? WNDCLASS wc; if(!GetClassInfo(g_hInst, EB_CLASS_NAME, &wc)) { ZeroMemory(&wc, sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = g_hInst; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0, 0, 192)); wc.lpszMenuName = NULL; wc.lpszClassName = EB_CLASS_NAME; if(!RegisterClass(&wc)) { //如果注册p|Q下面的CreateWindow函数失? } } RECT rc; GetClientRect(m_hwndParent, &rc); //创徏q个H口。WndProc 徏立m_hWnd变量 CreateWindowEx( 0, EB_CLASS_NAME, NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_BORDER, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, m_hwndParent, NULL, g_hInst, (LPVOID)this); } return (NULL != m_hWnd); }IPersistStream接口 IE调用浏览栏的IPersistStream接口Q以便允许这个浏览栏加蝲或存储持久性数据。如果没有持久性数据,q个Ҏ仍然必须q回一个成功代码。IPersistStream接口从IPersistl承而来Q所以要实现五个ҎQ?br />GetClassID, IsDirty, Load, Save, GetSizeMax?br />本文的这个浏览栏例子不用持久性数据,q且只有IPersistStream的最实现。GetClassIDq回对象的CLSIDQCLSID_SampleExplorerBarQ,其余的方法返回S_OK, 或者S_FALSE, 或?E_NOTIMPL。有关细节请参见IPersistStream的实现?br /> IDeskBand接口 IDeskBand接口是区对象专用接口。它只有一个方法。IDeskBand接口从IDockingWindowl承而来Q而IDockingWindow又从IOleWindowl承而来?br />IOleWindow有两个方法:GetWindow ?ContextSensitiveHelp。浏览栏例子的GetWindow实现q回览栏的子窗口句柄m_hwnd。因Z实现上下文敏感帮助,所以ContextSensitiveHelpq回E_NOTIMPL?br />IDockingWindow接口有三个方法:ShowDW, CloseDW, ?ResizeBorder。ResizeBorder不在M区对象中使用Q应该返回E_NOTIMPL。ShowDWҎҎ其不同的参数值控制浏览栏H口的显C或隐藏Q?br /> STDMETHODIMP CExplorerBar::ShowDW(BOOL fShow) { if(m_hWnd) { if(fShow) { //昄H口 ShowWindow(m_hWnd, SW_SHOW); } else { //隐藏H口 ShowWindow(m_hWnd, SW_HIDE); } } return S_OK; } CloseDWҎ摧毁览栏窗口: STDMETHODIMP CExplorerBar::CloseDW(DWORD dwReserved) { ShowDW(FALSE); if(IsWindow(m_hWnd)) DestroyWindow(m_hWnd); m_hWnd = NULL; return S_OK; }其余的方法,如GetBandInfo是IDeskBand专用的。IE使用它来指定览栏的标示W以及视图模式。IEq可能填写DESKBANDINFOl构的dwMask成员从浏览栏h更多的信息,q个l构用第三个参数传递。GetBandInfo应该存储q个标示W和视图模式q用所h的数据填写DESKBANDINFOl构。下面是本文览栏例子所实现GetBandInfoQ?br /> STDMETHODIMP CExplorerBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi) { if(pdbi) { m_dwBandID = dwBandID; m_dwViewMode = dwViewMode; if(pdbi->dwMask & DBIM_MINSIZE) { pdbi->ptMinSize.x = MIN_SIZE_X; pdbi->ptMinSize.y = MIN_SIZE_Y; } if(pdbi->dwMask & DBIM_MAXSIZE) { 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) { lstrcpyW(pdbi->wszTitle, L"览栏例?); } if(pdbi->dwMask & DBIM_MODEFLAGS) { pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT; } if(pdbi->dwMask & DBIM_BKCOLOR) { //通过Udq个标志来用默认的背景颜色 pdbi->dwMask &= ~DBIM_BKCOLOR; } return S_OK; } return E_INVALIDARG; }IInputObject接口 如果某个band对象要接受用戯入。那必d现IInputObject接口。IE实现IInputObjectSiteq用IInputObjectl护用户的输入焦炏V浏览栏需要实C个方法:UIActivateIO, HasFocusIO, ?TranslateAcceleratorIO?br />IE调用UIActivateIO通知览栏它以被ȀzL者被|灰。当被激zLQ浏览栏例子调用SetFocus来设|窗口输入焦炏V?br />当要定哪个H口有输入焦ҎQIE调用HasFocusIO。如果浏览栏的窗口或它的子窗口之一有输入焦点,HasFocusIOq回S_OK。否则,它返回S_FALSE?br />TranslateAcceleratorIO允许对象处理键盘加速键。本文浏览栏例子没有实现q个ҎQ所以它q回S_FALSE?br />览栏例子实现IInputObjectSite的细节如下: STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg) { if(fActivate) SetFocus(m_hWnd); return S_OK; } STDMETHODIMP CExplorerBar::HasFocusIO(void) { if(m_bFocus) return S_OK; return S_FALSE; } STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg) { return S_FALSE; }H口q程 因ؓ区对象的昄用的是子H口Q所以它必须实现H口q程来处理Windows消息。浏览栏例子实现了一个最单的版本Q它的窗口过E只处理了五个消息:WM_NCCREATE, WM_PAINT, WM_COMMAND, WM_SETFOCUS, ?WM_KILLFOCUS。如果要实现更多的功能,很容易扩充它处理其它的消息?br /> LRESULT CALLBACK CExplorerBar::WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam) { CExplorerBar *pThis = (CExplorerBar*)GetWindowLong(hWnd, GWL_USERDATA); switch (uMessage) { case WM_NCCREATE: { LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam; pThis = (CExplorerBar*)(lpcs->lpCreateParams); SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis); //讄H口句柄 pThis->m_hWnd = hWnd; } break; case WM_PAINT: return pThis->OnPaint(); case WM_COMMAND: return pThis->OnCommand(wParam, lParam); case WM_SETFOCUS: return pThis->OnSetFocus(); case WM_KILLFOCUS: return pThis->OnKillFocus(); } return DefWindowProc(hWnd, uMessage, wParam, lParam); }q里WM_COMMAND消息处理器简单地q回零。WM_PAINT消息处理器创建文本ƈ昄在资源管理器或IE的区对象中?br /> LRESULT CExplorerBar::OnPaint(void) { PAINTSTRUCT ps; RECT rc; BeginPaint(m_hWnd, &ps); GetClientRect(m_hWnd, &rc); SetTextColor(ps.hdc, RGB(255, 255, 255)); SetBkMode(ps.hdc, TRANSPARENT); DrawText(ps.hdc, TEXT("览栏例?), -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(m_hWnd, &ps); return 0; }WM_SETFOCUS ?WM_KILLFOCUS消息处理器通过调用本现场的IInputObjectSite::OnFocusChangeISҎ通知输入焦点现场改变Q?br /> LRESULT CExplorerBar::OnSetFocus(void) { FocusChange(TRUE); return 0; } LRESULT CExplorerBar::OnKillFocus(void) { FocusChange(FALSE); return 0; } void CExplorerBar::FocusChange(BOOL bFocus) { m_bFocus = bFocus; //通知焦点已改变的输入对象现场 if(m_pSite) { m_pSite->OnFocusChangeIS((IDockingWindow*)this, bFocus); } }四、ȝ 区对象提供了灉|和强大的扩展方式Q通过定制览栏得IE的功能大为增强。桌面区的实现扩展了普通窗口的能力。尽需要一些对COM的编E,但终I以子窗口的形式提供了一U用L面。从而今后的许多这U编E实现都能用cM的Windows~程技术。虽然本文所讨论的例子只提供了有限的功能Q但它示范了区对象全部的Ҏ,q且可以在此基础上进行扩充来创徏独特和功能强大的的用L面? |
图一
图一中画U圈的地方,分别UC“垂直的览器栏”、“水q的览器栏”、“工h”和“桌面工h”。这些“栏”,都可以在 IE 的“查看”菜单中或鼠标右键的上下文快h式菜单中昄或隐藏v来。这些界面窗口的实现Q其实就是实CU?COM 接口对象Q而这个对象叫 band。这个概念实在是只能意会而无法言传的Q我M能在文章中把它翻译ؓ“L靠在 IE ȝ口边上的对象”吧Q^_^
另外Q还有一个词?site。这个很好翻译,叫“站点”!。呵呵,我敢打包,如果你要能理解这个翻译在计算机类文章中的含义Q那只能恭喜你了,你的智慧太高了。(都是学计机软g的hQ做人的差距咋就q么大呢Q)在本文章中Qsite 可以q样理解QIE 的主框架四周Q就好比是“汽车站”,那些 band 对象Q就好比是“汽车”。band 汽RL可以停靠在“汽车站”上。所以,site 是“站点”,它也?COM 接口的对象(IObjectWithSite、IInputObjectSiteQ?br />
3.1 基本 band 对象
Band 对象Q从 Shell 4.71(IE 5.0) 开始提供支持。Band 是一?COM 对象Q必L在一个容器中M用,当然使用它们好象用普通窗口是一L。IE 是一个容器,桌面 Shell 也是一个容器,它们提供不同的函数功能,但基本的实现是相似的?br /> Band 对象分三U类型,览器栏 bandQExplorer bandsQ、工h bandQTool BandsQ和桌面工具?Desk bands)Q而浏览器?band 又有两种表现形式Q垂直和水^的。那?IE ?Shell 如何区分q加载这?bands 对象呢?Ҏ是:你要对不同的 band 对象Q在注册表中注册不同的组件类型(CATIDQ?br />
Band 样式 | lgcd | CATID |
垂直的浏览器?/td> | CATID_InfoBand | 00021493-0000-0000-C000-000000000046 |
水^的浏览器?/td> | CATID_CommBand | 00021494-0000-0000-C000-000000000046 |
桌面的工h | CATID_DeskBand | 00021492-0000-0000-C000-000000000046 |
class ATL_NO_VTABLE Cxxx : ...... public IPersistStreamInitImpl, // dl承 ...... { public: BOOL m_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) ...... // d需要持l性的属? END_PROP_MAP()上面的代码,其实实现的是 IPersistStreamInit 接口Q不q没有关p,因ؓ IPersistStreamInit z?IPersistStreamQ实例化了派生类Q自然就实例化了基类。在例子E序中,我只在桌面工h对象中添加了持箋性属性,用来保存和初始化“命令行”。另?COM_INTERFACE_ENTRY2(AQB)表示的含义是Q如果想查询A接口的指针,则提供B接口指针来代ѝؓ什么可以这样那Q因为B接口z自A接口Q那么B接口的前几个函数必然是A接口的函CQ自然B接口的地址其实和A接口的地址是一L了?br /> IObjectWithSite ?IE 用来Ҏ件进行管理和通讯用的一个接口。必要实现q个接口?个函敎ͼSetSite() ?GetSite()。当 IE 加蝲 band 对象和释?band 对象的时候,都要调用 SetSite()函数Q那么在q个函数里正好是写初始化和释放操作代码的地方Q?
STDMETHODIMP Cxxx::SetSite(IUnknown *pUnkSite) { if( NULL == pUnkSite ) // 释放 band 的时? { // 如果加蝲的时候,保存了一些接? // 那么现在Q释攑֮ } else // 加蝲 band 的时? { m_hwndParent = NULL; // 装蝲 band 的父H口(是带有标题的那个框架窗? // q个H口的句柄,是调?IUnknown::QueryInterface() 得到 IOleWindow // 然后调用 IOleWindow::GetWindow() 而获得的? CComQIPtr< IOleWindow, &IID_IOleWindow > spOleWindow(pUnkSite); if( spOleWindow ) spOleWindow->GetWindow(&m_hwndParent); if( !m_hwndParent ) return E_FAIL; // 现在Q正好是建立子窗口的时机? // 注意Q子H口建立的时候,不要使用 WS_VISIBLE 属? ... ... // 在例子程序中Q用 CAxWindow 实现了一个能包容ActiveX的容器窗?垂直览器栏) // 在例子程序中Q用 WIN API 函数 CreateWindow 实现了标准窗?水^览器栏、工h) // 在例子程序中Q用 CWindowImpl 实现了一个包容窗?桌面工具? /*********************************************************/ 以下部分Q根?band 对象Ҏ的功能,是可以选择实现? **********************************************************/ // 如果子窗口实C用户输入Q那么必d?IInputObject 接口Q? // 而该接口是被 IE ?IInputObjectSite 调用的,因此在你的对? // 中,应该保存 IInputObjectSite 的接口指针? // 在类的头文g中,定义Q? // CComQIPtr< IInputObjectSite, &IID_IInputObjectSite > m_spSite; m_spSite = pUnkSite; // 保存 IInputObjectSite 指针 if( !m_spSite ) return E_FAIL; // 你需要控?IE 的主框架吗? // 那么在类的头文g中,定义Q? // CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 > m_spFrameWB; // 然后Q先取得 IServiceProvider,再取?IWebBrowser2 CComQIPtr < IServiceProvider, &IID_IServiceProvider> spSP(pUnkSite); if( !spSP ) return E_FAIL; spSP->QueryService( SID_SWebBrowserApp, &m_spFrameWB ); if( !m_spFrameWB) return E_FAIL; // 如果你取得了 IE L架的 IWebBrowser2 指针 // 那么Q当它发生了什么事情,你难道不想知道吗Q? // 定义QCComPtr m_spCP; CComQIPtr< IConnectionPointContainer, &IID_IConnectionPointContainer> spCPC( m_spFrameWB ); if( spCPC ) { spCPC->FindConnectionPoint( DIID_DWebBrowserEvents2, &m_spCP ); if( m_spCP ) { m_spCP->Advise( reinterpret_cast< IDispatch * >( this ), &m_dwCookie ); } } // 咳~~~ 不说了,看源码去吧。这里能q的事情太多?.. ... } return S_OK; }IDeskBand 是一个特D的 band 对象接口Q有一个方法函敎ͼGetBarInfo()Q?br />IDockingWindow ?IDeskBank 的基c,?个方法函敎ͼShowDW()、CloseDW()、ResizeBorderDW()Q?br />IOleWindow 又是 IDockingWindow 的基c,?个方法函敎ͼGetWindow()、ContextSensitiveHelp()Q?
class ATL_NO_VTABLE Cxxx : ...... public IDeskBand, ...... { ...... BEGIN_COM_MAP(Cxxx) ...... COM_INTERFACE_ENTRY_IID(IID_IDeskBand, IDeskBand) ...... END_COM_MAP() // IOleWindow STDMETHODIMP Cxxx::GetWindow(HWND * phwnd) { // 取得 band 对象的窗口句? // m_hWnd 是徏立窗口时候保存的 *phwnd = m_hWnd; return S_OK; } STDMETHODIMP Cxxx::ContextSensitiveHelp(BOOL fEnterMode) { // 上下文帮助,参?IContextMenu 接口 return E_NOTIMPL; } // IDockingWindow STDMETHODIMP CVerticalBar::ShowDW(BOOL bShow) { // 昄或隐?band H口 if( m_hWnd ) ::ShowWindow( m_hWnd, bShow ? SW_SHOW : SW_HIDE); return S_OK; } STDMETHODIMP CVerticalBar::CloseDW(DWORD dwReserved) { // 销?band H口 if( ::IsWindow( m_hWnd ) ) ::DestroyWindow( m_hWnd ); m_hWnd = NULL; return S_OK; } STDMETHODIMP CVerticalBar::ResizeBorderDW(LPCRECT prcBorder, IUnknown* punkToolbarSite, BOOL fReserved) { // 当框架窗口的Ҏ大小改变? return E_NOTIMPL; } // IDeskBand STDMETHODIMP CVerticalBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi) { // 取得 band 的基本信息,你需要填?pdbi 参数作ؓq回 if( NULL == pdbi ) return E_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) { // H口标题 wcscpy(pdbi->wszTitle,L"H口标题"); } if(pdbi->dwMask & DBIM_MODEFLAGS) { pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT; } if(pdbi->dwMask & DBIM_BKCOLOR) { // 如果使用默认的背景色Q则U除该标? pdbi->dwMask &= ~DBIM_BKCOLOR; } return S_OK; }3.3 选择实现?COM 接口
STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg) { if(fActivate) SetFocus(m_hWnd); return S_OK; } STDMETHODIMP CExplorerBar::HasFocusIO(void) { if(m_bFocus) return S_OK; return S_FALSE; } STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg) { return S_FALSE; }Band 对象能够通过包容器的 IOleCommandTarget::Exec() 调用执行命o。?IOleCommandTarget 接口指针Q则可以通过调用包容器的 IInputOjbectSite::QueryInterfaceQIID_IOleCommandTarget,...Q?函数得到。CGID_DeskBand 是命令组Q当一?band 对象?GetBandInfo 被调用的时候,包容器通过 dwBandID 参数指定一?ID l?band 对象Q对象要保存住这个IDQ以便调?IOleCommandTarget::Exec()的时候用。ID 的命令有Q?
?/font> | 描述 |
---|---|
pUnk | band 对象?IUnknown 指针Q其它的桌面 bands 被隐藏 |
0 | 隐藏所有的桌面 bands |
1 | 昄所有的桌面 bands |
3.4 Band 对象注册
Band 对象必须注册Z?OLE q程内的服务器,q且支持 apartment U程公寓。注册表中默认键的值是表示菜单的文字。对于浏览器栏,它加?IE 菜单的“查看\览器栏”中Q对于工h band Q它加到 IE 菜单的“查看\工具栏”中Q对于桌?bandQ?它加到系lQ务栏的快捯单中。在菜单资源中,可以使用?amp;”指明加速键?br />
通常Q一个基本的 band 对象的注册表目是:
HKEY_CLASSES_ROOT
CLSID
{你的 band 对象?CLSID}
(Default) = 菜单的文?
InProcServer32
(Default) = DLL 的全路径文g?
ThreadingModel= Apartment
工具?bands q必L它们?CLSID 注册?IE 的注册表中?br />
?HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar 下给?CLSID 作ؓ键名Q而其键值是被忽略的?br />
HKEY_LOCAL_MACHINE
Software
Microsoft
Internet Explorer
Toolbar
{你的 band 对象?CLSID}
q有几个可选的注册表项?例子E序q不是这样实现的)。比如,你想让浏览器栏显C?HTML 的话Q必要如下讄注册表:
HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象?CLSID}
Instance
CLSID
(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}
同时Q如果要指定一个本地的 HTML 文gQ那么要如下讄Q?
HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象?CLSID}
Instance
InitPropertyBag
Url
另外Q还可以指定览器栏的宽和高Q当Ӟ它是依赖于这个栏是纵向还是横向的。其实这个项目无所谓,因ؓ当用戯整了览器栏的大后Q会自动保存在注册表中的?br />
HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
Explorer Bars
{你的 Band 对象?CLSID}
BarSize
BarSize 键的cd必须?REG_BINARY cdQ它?个字节。左起前4个字节,是用16q制表示的像素宽度或高度Q后4个字节保留,你应该设|ؓ0。下面是一个可以在览器栏上显C?HTML 文g的全部注册表目的例子,默认宽度?91Q?x123Q个像素点:
HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象?CLSID}
(Default) = 菜单文字
InProcServer32
(Default) = DLL 的全路径文g?
ThreadingModel= Apartment
Instance
CLSID
(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}
InitPropertyBag
Url= 你的 HTML 文g?br />
HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
Explorer Bars
{你的 Band 对象?CLSID}
BarSize= 23 01 00 00 00 00 00 00
对于注册表的讄Q用 ATL 实现其实是异常简单的。打开工程?xxx.rgs 文gQƈ手工~辑一下就可以了?下面q个文g源码Q是例子E序?IE 工具栏的注册表样式,HKLM 是需要手工添加的Q因为它不用组件类型方式注册。而对于其它类型的 band 对象只要在类声明中添加:
BEGIN_CATEGORY_MAP(Cxxx) // 向注册表中注?COM cd IMPLEMENTED_CATEGORY(CATID_InfoBand) // 垂直样式的浏览器? END_CATEGORY_MAP()IE 工具栏类?band 对象的?rgs”文?pre>HKCR // q个目?ATL 帮你生成的,你只要手工修改“菜单上的文字”就可以? { Bands.ToolBar.1 = s ''ToolBar Class'' { CLSID = s ''{ 你的 CLSID }'' } Bands.ToolBar = s ''ToolBar Class'' { CLSID = s ''{ 你的 CLSID }'' CurVer = s ''Bands.ToolBar.1'' } NoRemove CLSID { ForceRemove { 你的 CLSID } = s ''用在菜单上的文字(&T)'' { ProgID = s ''Bands.ToolBar.1'' VersionIndependentProgID = s ''Bands.ToolBar'' ForceRemove ''Programmable'' InprocServer32 = s ''%MODULE%'' { val ThreadingModel = s ''Apartment'' } ''TypeLib'' = s ''{xxxx-xxxx-xxxxxxxxxxxxxxx}'' } } } HKLM // q个目是手工添加的IE工具栏所Ҏ? { Software { Microsoft { ''Internet Explorer'' { NoRemove Toolbar { ForceRemove val { 你的 CLSID } = s ''随便l个说明性文字串'' } } } } } 四?ATL 实现