ATL 实现定制的 IE 浏览器栏、工具栏和桌面工具栏 下载源代码
二、概念
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 实现这个接口很简单: class ATL_NO_VTABLE Cxxx : ...... public IPersistStreamInitImpl, // 添加继承 ...... { 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) ...... // 添加需要持续性的属性 END_PROP_MAP() 上面的代码,其实实现的是 IPersistStreamInit 接口,不过没有关系,因为 IPersistStreamInit 派生自 IPersistStream,实例化了派生类,自然就实例化了基类。在例子程序中,我只在桌面工具栏对象中添加了持续性属性,用来保存和初始化“命令行”。另外 COM_INTERFACE_ENTRY2(A,B)表示的含义是:如果想查询A接口的指针,则提供B接口指针来代替。为什么可以这样那?因为B接口派生自A接口,那么B接口的前几个函数必然就是A接口的函数了,自然B接口的地址其实和A接口的地址是一样的了。 STDMETHODIMP Cxxx::SetSite(IUnknown *pUnkSite) { if( NULL == pUnkSite ) // 释放 band 的时候 { // 如果加载的时候,保存了一些接口 // 那么现在:释放它 } else // 加载 band 的时候 { m_hwndParent = NULL; // 装载 band 的父窗口(就是带有标题的那个框架窗口) // 这个窗口的句柄,是调用 IUnknown::QueryInterface() 得到 IOleWindow // 然后调用 IOleWindow::GetWindow() 而获得的。 CComQIPtr< IOleWindow, &IID_IOleWindow > spOleWindow(pUnkSite); if( spOleWindow ) spOleWindow->GetWindow(&m_hwndParent); if( !m_hwndParent ) return E_FAIL; // 现在,正好是建立子窗口的时机。 // 注意,子窗口建立的时候,不要使用 WS_VISIBLE 属性 ... ... // 在例子程序中,用 CAxWindow 实现了一个能包容ActiveX的容器窗口(垂直浏览器栏) // 在例子程序中,用 WIN API 函数 CreateWindow 实现了标准窗口(水平浏览器栏、工具栏) // 在例子程序中,用 CWindowImpl 实现了一个包容窗口(桌面工具栏) /*********************************************************/ 以下部分,根据 band 对象特有的功能,是可以选择实现的 **********************************************************/ // 如果子窗口实现了用户输入,那么必须实现 IInputObject 接口, // 而该接口是被 IE 的 IInputObjectSite 调用的,因此在你的对象 // 中,应该保存 IInputObjectSite 的接口指针。 // 在类的头文件中,定义: // CComQIPtr< IInputObjectSite, &IID_IInputObjectSite > m_spSite; m_spSite = pUnkSite; // 保存 IInputObjectSite 指针 if( !m_spSite ) return E_FAIL; // 你需要控制 IE 的主框架吗? // 那么在类的头文件中,定义: // CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 > m_spFrameWB; // 然后,先取得 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 主框架的 IWebBrowser2 指针 // 那么,当它发生了什么事情,你难道不想知道吗? // 定义:CComPtr 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 ); } } // 咳~~~ 不说了,看源码去吧。这里能干的事情太多了... ... } return S_OK; } IDeskBand 是一个特殊的 band 对象接口,有一个方法函数:GetBarInfo(); 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 窗口 if( m_hWnd ) ::ShowWindow( m_hWnd, bShow ? SW_SHOW : SW_HIDE); return S_OK; } STDMETHODIMP CVerticalBar::CloseDW(DWORD dwReserved) { // 销毁 band 窗口 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 参数作为返回 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) { // 窗口标题 wcscpy(pdbi->wszTitle,L"窗口标题"); } if(pdbi->dwMask & DBIM_MODEFLAGS) { pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT; } if(pdbi->dwMask & DBIM_BKCOLOR) { // 如果使用默认的背景色,则移除该标志 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() 调用执行命令。而 IOleCommandTarget 接口指针,则可以通过调用包容器的 IInputOjbectSite::QueryInterface(IID_IOleCommandTarget,...) 函数得到。CGID_DeskBand 是命令组,当一个 band 对象的 GetBandInfo 被调用的时候,包容器通过 dwBandID 参数指定一个 ID 给 band 对象,对象要保存住这个ID,以便调用 IOleCommandTarget::Exec()的时候使用。ID 的命令有:
3.4 Band 对象注册 BEGIN_CATEGORY_MAP(Cxxx) // 向注册表中注册 COM 类型 IMPLEMENTED_CATEGORY(CATID_InfoBand) // 垂直样式的浏览器栏 END_CATEGORY_MAP() IE 工具栏类型 band 对象的“.rgs”文件 HKCR // 这个项目是 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 // 这个项目是手工添加的IE工具栏所特有的 { Software { Microsoft { ''Internet Explorer'' { NoRemove Toolbar { ForceRemove val { 你的 CLSID } = s ''随便给个说明性文字串'' } } } } } 四、 ATL 实现 |