打造个性化的Internet Explorer
作者:孫輝
在Microsoft的軟件哲學(xué)中,框架窗口是一個(gè)十分重要的角色,這類(lèi)窗口簡(jiǎn)直無(wú)處不在。所謂框架窗口,就是四個(gè)窗口邊上具有停靠對(duì)象能力的窗口對(duì)象,從現(xiàn)象上看,框架窗口有十分特別的“邊”,Microsoft構(gòu)造的許許多多的東西都可以在其邊上“靠泊”,這就是所謂的“Docking”,Internet Explorer是Microsoft的一個(gè)典型的運(yùn)用框架窗口的代表作品。今天的IE,在不知不覺(jué)中,你會(huì)發(fā)現(xiàn)工具欄中會(huì)多出一些東西來(lái),這一切的緣由歸根到底都是“邊”在作祟,本文試圖拋磚引玉,將你帶入豐富多彩的IE擴(kuò)展世界。
Band,IE擴(kuò)展的利器
Band,是Windows Shell對(duì)象,當(dāng)Microsoft將Internet Explorer與Windows Shell徹底集成時(shí),Band對(duì)象就成為Windows Shell的一個(gè)活躍對(duì)象,典型的Band對(duì)象有IE的搜索欄、收藏欄、歷史欄以及IE的各種工具欄等等。Microsoft公開(kāi)的Band對(duì)象有四類(lèi),一類(lèi)是桌面Band,如位于任務(wù)欄上的“快速啟動(dòng)”、“Windows Media Player”等工具條。而其他三種Band,則均出現(xiàn)在IE之中,包括工具欄Band(如MSN Search工具欄)、Explorer Band(如歷史、媒體等出現(xiàn)在IE左側(cè)的窗口)和通訊Band(如位于用戶(hù)區(qū)下方的“討論”窗口)。
Band對(duì)象停靠在對(duì)應(yīng)于IE的框架窗口的四周,因此,給IE的界面提供了靈活的擴(kuò)張機(jī)制。由于IE的用戶(hù)區(qū)基本上用于HTML對(duì)象的瀏覽,因此現(xiàn)在流行的B/S結(jié)構(gòu)不能充分地運(yùn)用IE靈活、強(qiáng)大的擴(kuò)展機(jī)制,一旦可以將具有C/S結(jié)構(gòu)特征的自定義子窗口掛接到IE的四周,將可以將IE打造成同時(shí)具備B/S、C/S體系優(yōu)勢(shì)的開(kāi)發(fā)框架。現(xiàn)在,Microsoft已經(jīng)在Office系列產(chǎn)品中體現(xiàn)類(lèi)似的思路,例如在Microsoft Word以及Excel中,Microsoft引進(jìn)了Actions Pane對(duì)象,使得位于中心的Office文檔可以與停靠在一邊的Actions Pane對(duì)象進(jìn)行通訊、交互操作等等,如圖1所示。
圖1? Word右側(cè)的“任務(wù)窗格”對(duì)象是個(gè)典型的Docking對(duì)象,這里體現(xiàn)了Microsoft最新的Smart Document技術(shù)
開(kāi)發(fā)你的Band對(duì)象,讓IE因你而變
從IE4開(kāi)始,Band對(duì)象就已經(jīng)存在了,但其復(fù)雜的接口使大多數(shù)開(kāi)發(fā)人員望而卻步。一個(gè)典型的Band對(duì)象是一個(gè)COM對(duì)象,需要實(shí)現(xiàn)如下幾個(gè)接口:
每個(gè)接口各自包含一些列方法。接口IDeskBand決定了Band對(duì)象的基本行為,接口IObjectWithSite提供了Band對(duì)象與IE的通訊渠道,接口IInputObject提供了Band對(duì)象的輸入消息處理,特別該接口是處理對(duì)話框消息的關(guān)鍵所在,當(dāng)需要實(shí)現(xiàn)桌面Band對(duì)象時(shí),接口IPersistStream提供實(shí)現(xiàn)相關(guān)的信息存儲(chǔ)(如Band對(duì)象的位置)。關(guān)于Band對(duì)象的一般介紹,可以參考Microsoft Internet SDK技術(shù)文檔,由于Microsoft僅提供了很少的文檔與范例,因此Band對(duì)象基本上被廣大開(kāi)發(fā)者忽略了。事實(shí)上,IE內(nèi)部的擴(kuò)展性十分強(qiáng)大(當(dāng)然,強(qiáng)大也意味著危險(xiǎn)),就其擴(kuò)展編程接口而言,IE的擴(kuò)展性并不輸給FireFox。
理論上,只要支持COM技術(shù)的開(kāi)發(fā)工具,都可以用來(lái)構(gòu)造Band對(duì)象,然而對(duì)于更深層的COM技術(shù)細(xì)節(jié)而言,ATL/MFC類(lèi)庫(kù)還是最佳的選擇。毋庸置疑,MFC具備強(qiáng)大的界面構(gòu)造能力,然而,由于缺乏一個(gè)有效的銜接,MFC類(lèi)庫(kù)與深層COM技術(shù)開(kāi)發(fā)一直被一層很薄的“紙”隔開(kāi)了,這一點(diǎn)不能不說(shuō)是個(gè)遺憾。
CTangramBandImpl,一個(gè)Band對(duì)象的起點(diǎn)
首先,我們需要一個(gè)構(gòu)造Band對(duì)象的起點(diǎn),為此我們構(gòu)造一個(gè)基類(lèi),以實(shí)現(xiàn)上面提到的四個(gè)接口,CTangramBandImpl對(duì)象的詳細(xì)實(shí)現(xiàn)細(xì)節(jié)請(qǐng)參考我們提供的源代碼,以下是其基本構(gòu)造:
class CTangramBandImpl :
??? public IDeskBand,
??? public IObjectWithSite,
??? public IPersistStream,
??? public IInputObject
{
public:
??? CTangramBandImpl(void);
??? virtual ~CTangramBandImpl(void);
// IDeskBand
public:
??? STDMETHOD(GetBandInfo)(DWORD dwBandID, DWORD dwViewMode,
??? DESKBANDINFO* pdbi);
// IObjectWithSite
public:
??? STDMETHOD(SetSite)(IUnknown* pUnkSite);
??? STDMETHOD(GetSite)(REFIID riid, void **ppvSite);
// IOleWindow
public:
??? STDMETHOD(GetWindow)(HWND* phwnd);
??? STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode);
// IDockingWindow
public:
??? STDMETHOD(CloseDW)(unsigned long dwReserved);
??? STDMETHOD(ResizeBorderDW)(const RECT* prcBorder,
??? IUnknown* punkToolbarSite, BOOL fReserved);
??? STDMETHOD(ShowDW)(BOOL fShow);
// IPersist
public:
??? STDMETHOD(GetClassID)(CLSID *pClassID);
// IPersistStream
public:
??? STDMETHOD(IsDirty)(void);
??? STDMETHOD(Load)(IStream *pStm);
??? STDMETHOD(Save)(IStream *pStm, BOOL fClearDirty);
??? STDMETHOD(GetSizeMax)(ULARGE_INTEGER *pcbSize);
// IInputObject
public:
??? STDMETHOD(HasFocusIO)(void);
??? STDMETHOD(TranslateAcceleratorIO)(LPMSG lpMsg);
??? STDMETHOD(UIActivateIO)(BOOL fActivate, LPMSG lpMsg);
public:
??? BOOL m_bFocus;
??? BOOL m_bCommBand;
??? void FocusChange(BOOL);
protected:
??? virtual BOOL RegisterAndCreateWindow(){return false;};
??? virtual void SetBandTitle(DESKBANDINFO* pdbi){};
??? CFrameWnd* m_pFrameWnd;
??? DWORD m_dwBandID;
??? DWORD m_dwViewMode;
??? BOOL m_bShow;
??? BOOL m_bEnterHelpMode;
??? HWND m_hWndParent;
??? IInputObjectSite* m_pSite;
};
對(duì)于IE,除了工具欄外,我們將停靠于IE框架左側(cè)的Band稱(chēng)為Explorer Band,而將停靠于框架底邊的Band稱(chēng)為通訊Band(Communication Band),從COM的角度看,這兩類(lèi)對(duì)象分別屬于不同的對(duì)象范疇(Category),所有這兩類(lèi)對(duì)象均被羅列在IE的View菜單的“Explorer Bar”子菜單中。我們實(shí)現(xiàn)的Explorer Band對(duì)象的類(lèi)結(jié)構(gòu)如下:
class ATL_NO_VTABLE CBand : public CTangramBandImpl,
??? public CComObjectRootEx<CComSingleThreadModel>,
??? public CComCoClass<CBand, &CLSID_Band>,
??? public IDispatchImpl<IBand, &IID_IBand, &LIBID_TANGRAMBANDLib>
{
public:
??? CBand();
??? DECLARE_REGISTRY_RESOURCEID(IDR_BAND)
??? DECLARE_PROTECT_FINAL_CONSTRUCT()
??? BEGIN_CATEGORY_MAP(CBand)
??????? IMPLEMENTED_CATEGORY(CATID_InfoBand)
??????? IMPLEMENTED_CATEGORY(CATID_DeskBand)
??? END_CATEGORY_MAP()
??? BEGIN_COM_MAP(CBand)
??????? COM_INTERFACE_ENTRY(IBand)
??????? COM_INTERFACE_ENTRY(IOleWindow)
??????? COM_INTERFACE_ENTRY(IObjectWithSite)
??????? COM_INTERFACE_ENTRY_IID(IID_IDockingWindow, IDockingWindow)
??????? COM_INTERFACE_ENTRY_IID(IID_IInputObject, IInputObject)
??????? COM_INTERFACE_ENTRY_IID(IID_IDeskBand, IDeskBand)
??????? COM_INTERFACE_ENTRY(IPersist)
??????? COM_INTERFACE_ENTRY(IPersistStream)
??????? COM_INTERFACE_ENTRY(IDispatch)
??? END_COM_MAP()
protected:
??? virtual BOOL RegisterAndCreateWindow();
??? virtual void SetBandTitle(DESKBANDINFO* pdbi);
};
代碼片斷:
BEGIN_CATEGORY_MAP(CBand)
??? IMPLEMENTED_CATEGORY(CATID_InfoBand)
??? IMPLEMENTED_CATEGORY(CATID_DeskBand)
END_CATEGORY_MAP()
表明CBand對(duì)象隸屬兩個(gè)對(duì)象范疇,分別是桌面Band以及Explorer Band,因此,此類(lèi)對(duì)象可以停靠于Windows Shell的桌面,同時(shí)也可以停靠于IE框架窗口的左側(cè),當(dāng)你在桌面的工具欄中點(diǎn)擊鼠標(biāo)右鍵,選擇Toolbars時(shí),你會(huì)看到創(chuàng)建桌面TangramBand的菜單項(xiàng):
Communication Band對(duì)象CHBand的類(lèi)結(jié)構(gòu)與CBand的基本一致,最大的區(qū)別是:
BEGIN_CATEGORY_MAP(CHBand)
??? IMPLEMENTED_CATEGORY(CATID_CommBand)
END_CATEGORY_MAP()
由于Band對(duì)象實(shí)現(xiàn)了接口IObjectWithSite,因此,Band對(duì)象可以通過(guò)該接口得到Band對(duì)象所從屬的IE實(shí)例。為此,我們注意到CTangramBandImpl中已經(jīng)實(shí)現(xiàn)了IObjectWithSite的方法SetSite,我們可以在其中添加如下代碼:
IOleCommandTarget* pCmdTarget = NULL;
IWebBrowser2* m_pBrowser;
HRESULT hr = pUnkSite->QueryInterface(IID_IOleCommandTarget,
??? (LPVOID*)&pCmdTarget);
if(SUCCEEDED(hr))
{
??? IServiceProvider* pSP;
??? hr = pCmdTarget->QueryInterface(IID_IServiceProvider,
??????? (LPVOID*)&pSP);
??? pCmdTarget->Release();
??? if (SUCCEEDED(hr))
??? {
??????? hr = pSP->QueryService(SID_SWebBrowserApp,
??????????? IID_IWebBrowser2, (LPVOID*)&m_pBrowser);
??????? if(hr==S_OK)
??????????? TRACE(_T("Get m_pBrowser: %x\n"),m_pBrowser);
??????? pSP->Release();
??? }
}
這樣就可以以得到這個(gè)Band對(duì)象對(duì)應(yīng)的IE實(shí)例(即上述代碼中的m_pBrowser)。通過(guò)m_pBrowser,Band對(duì)象可以訪問(wèn)當(dāng)前瀏覽器瀏覽的頁(yè)面、調(diào)用網(wǎng)頁(yè)中的腳本代碼以及實(shí)現(xiàn)其他的Band對(duì)象與網(wǎng)頁(yè)之間的交互功能。
實(shí)現(xiàn)基于MFC的Band對(duì)象
為了將MFC類(lèi)庫(kù)的強(qiáng)大功能自然納入Band對(duì)象,我們需要一個(gè)基于MFC的Frame窗口。我們注意到,CTangramBandImpl已經(jīng)包含了一個(gè)成員變量:
CFrameWnd* m_pFrameWnd;
這個(gè)變量建立了一座銜接Band對(duì)象與MFC類(lèi)庫(kù)的橋梁,在每個(gè)Band對(duì)象的RegisterAndCreateWindow函數(shù)中,均包含如下代碼:
BOOL CHBand::RegisterAndCreateWindow()
{
??? RECT rect;
??? ::GetClientRect(m_hWndParent, &rect);
??? AFX_MANAGE_STATE(AfxGetStaticModuleState());
??? theApp.m_pBand = this;
??? m_pFrameWnd = new CTangramBandFrame();
??? CCreateContext m_Context;
??? m_Context.m_pNewViewClass = RUNTIME_CLASS(TestFormView);
??? CWnd* pWnd = CWnd::FromHandle(m_hWndParent);
??? m_pFrameWnd->Create(NULL,_T("TangramBand"),
??????? WS_CHILD, CRect(rect.left, rect.top,
??????? rect.right - rect.left, rect.bottom - rect.top),
??????? pWnd, NULL, NULL, &m_Context);
??? theApp.m_pBand = NULL;
??? return true;
}
我們注意到,這段代碼創(chuàng)建了一個(gè)MFC CFrameWnd對(duì)象m_pFrameWnd。由此,一切關(guān)于MFC的代碼技巧自然而然地盤(pán)活了,我們看到一個(gè)典型的MFC框架被融入IE之中:
BOOL CTangramBandFrame::OnCreateClient(LPCREATESTRUCT lpcs,
??? CCreateContext* pContext)
{
??? if(!m_pBand->m_bCommBand)
??? {
??????? m_SplitterWnd.CreateStatic(this, 2, 1);
??????? m_SplitterWnd.CreateView(0, 0, RUNTIME_CLASS(TestFormView),
??????????? CSize(0, 100), NULL);
??????? m_SplitterWnd.CreateView(1, 0, RUNTIME_CLASS(TestFormView2),
??????????? CSize(0, 100), NULL);
??? }
??? else
??? {
??????? m_SplitterWnd.CreateStatic(this, 1, 2);
??????? m_SplitterWnd.CreateView(0, 0, RUNTIME_CLASS(TestFormView),
??????????? CSize(150, 100), NULL);
??????? m_SplitterWnd.CreateView(0, 1, RUNTIME_CLASS(TestFormView2),
??????????? CSize(0, 0), NULL);
??? }
??? return true;
}
由于Band對(duì)象存在于一個(gè)動(dòng)態(tài)鏈接庫(kù)中,故創(chuàng)建MFC對(duì)象時(shí)要注意在合適的位置調(diào)用代碼:
AFX_MANAGE_STATE(AfxGetStaticModuleState());
以使得MFC能夠正確處理消息。
輸入消息處理
接口IInputObject用于管理Band對(duì)象的輸入以及對(duì)話框、加速鍵消息,關(guān)于MFC消息,由于Band對(duì)象本質(zhì)上是一種IE插件,故消息隊(duì)列由IE控制,因此MFC窗口的消息處理是個(gè)關(guān)鍵問(wèn)題。當(dāng)Band對(duì)象捕獲輸入焦點(diǎn)時(shí),我們必須保證MFC窗口消息被正確處理,下述代碼:
STDMETHODIMP CTangramBandImpl::TranslateAcceleratorIO(LPMSG lpMsg)
{
??? AFX_MANAGE_STATE(AfxGetStaticModuleState());
??? if(m_bFocus&&theApp.m_pWnd)
??? {
??????? theApp.m_pWnd->PreTranslateMessage(lpMsg);
??????? TRACE(_T("TranslateAcceleratorIO\n"));
??????? return S_OK;
??? }
??? return S_FALSE;
}
用來(lái)處理當(dāng)前具有輸入焦點(diǎn)的MFC窗口theApp.m_pWnd的相關(guān)消息,因此具有輸入焦點(diǎn)的MFC窗口必須重載函數(shù)PreTranslateMessage,特別是CFormView的派生對(duì)象,在范例代碼中我們給出了重載該函數(shù)的例子。
調(diào)試與中文處理
Band對(duì)象的調(diào)試?yán)淌荌E,因此需要修改工程配置,使得調(diào)試過(guò)程定向到IE。為使得Band對(duì)象能夠正確接受中文輸入,代碼工程必須是基于UNICODE的。
您可以到雜志的站點(diǎn)(http://mag.csdn.net/msdn)上下載本刊附帶的源代碼,其中的MsdnJxControl解決方案中有本文所提到的項(xiàng)目的完整實(shí)現(xiàn)。該解決方案已經(jīng)為您做好了所有的配置工作,但還是希望您能夠通過(guò)屬性面板仔細(xì)觀察該解決方案的具體設(shè)置。
小結(jié)
正如Microsoft系列的其他產(chǎn)品一樣,Internet Explorer中存在著豐富的擴(kuò)展空間。對(duì)于Microsoft而言,框架窗口的四周存在著可停靠對(duì)象的口岸,因此你可以在這些岸邊開(kāi)拓你的軟件空間以展示你的軟件魅力,如果你讀到了本文,那么你就開(kāi)始吧,充滿誘惑的海岸,等待你去開(kāi)拓。
本文引用通告地址: http://blog.csdn.net/dotnet_editor/services/trackbacks/455812.aspx
總結(jié)
以上是生活随笔為你收集整理的打造个性化的Internet Explorer的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 1093芯片做正弦波逆变器_正弦波逆变器
- 下一篇: R语言实战 R语言读取不同文件类型中数据