1. 程式人生 > >在 Office 系列軟體中建立 COM 外掛工具條,並實現工具條上的彈出選單

在 Office 系列軟體中建立 COM 外掛工具條,並實現工具條上的彈出選單

前兩天,在CSDN瞎逛悠,見一老兄問到此問,卻沒有人作答(頂的人倒還不少,國內的論壇是不是都這樣?),還發了些牢騷,俺也順便跟著發了點牢騷:)
於是坐下來靜下心研究了一下,今日終於成了正果,不敢私吞成果,特搬弄出來,讓大家分享分享(切,無非就是虛榮而已啦,把自己說得那麼偉大?!)!

點選這裡下載工程原始碼

我看還是做一篇教程寫好了,寫清楚一點,呵呵:)
哦,先說明白,俺用的是VC6啊(俺的工程是以Outlook外掛為例的,因為CSDN上那位老兄問的就是Outlook,所以咱就對症下藥啦,不過Office系列的外掛都大同小異啦,只是做具體功能時才會天差地別哦):
1. 新建ATL工程,全部預設,完成Wizard。
2. 新建ATL Object,選擇Simple Object,其他預設。
3. 開啟stdafx.h,這裡需要匯入幾個庫:
#import

 "C:\Documents and Settings\Administrator\My Documents\msoffice9\mso9.dll" rename_namespace("Office")
using namespace Office;

#import "C:\Program Files\Common Files\Microsoft Shared\VBA\VBA6\VBE6EXT.OLB" rename_namespace("VBE6")
using namespace VBE6;

#import "C:\Documents and Settings\Administrator\My Documents\msoffice9\MSOUTL9.OLB" named_guids,rename_namespace("MSOutlook")
using namespace

 MSOutlook;

4. 開啟.rgs檔案,需要新增幾行註冊指令碼:
HKCU
{
 NoRemove Software
 {
  NoRemove Microsoft
  {
   NoRemove Office
   {
    NoRemove Outlook
    {
     NoRemove Addins
     {
      'OutlookAddin.COutlookAddinSample'
      {
       val FriendlyName = s 'Azhi sample'
       val Description = s 'Azhi sample'
       val LoadBehavior = d '00000003'
       val CommandLineSafe = d '00000000'
      }
     }
    }
   }
  }
 }
}
這裡注意了:OutlookAddin.COutlookAddinSample這玩意是該COM的ProgID。

5. 現在開啟COutlookAddinSample.h檔案。
a. 先引入一個庫:
#import
 "C:\Program Files\Common Files\Designer\MSADDNDR.DLL" raw_interfaces_only, raw_native_types, no_namespace, named_guids 

b. 在介面(類)定義中新增對_IDTExtensibility2介面的支援:
class ATL_NO_VTABLE CCOutlookAddinSample : 
 . 
 .
 public IDispatchImpl<_IDTExtensibility2, &IID__IDTExtensibility2, &LIBID_AddInDesignerObjects> 
 .

c. 新增基於_IDTExtensibility2介面方法的過載:
.
// _IDTExtensibility2
public:
 STDMETHOD(OnConnection)(IDispatch * Application, ext_ConnectMode ConnectMode, IDispatch * AddInInst, SAFEARRAY * * custom);
 STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom);
 STDMETHOD(OnAddInsUpdate)(SAFEARRAY * * custom);
 STDMETHOD(OnStartupComplete)(SAFEARRAY * * custom);
 STDMETHOD(OnBeginShutdown)(SAFEARRAY * * custom);
 .
 .

d. 定義一個變數儲存Outlook的Application例項:
private:
 CComPtr<MSOutlook::_Application> m_spApp;

e. 修改COM_MAP對映表:
 .
// COM_INTERFACE_ENTRY(IDispatch) // 刪除此行,以下兩行皆為新新增的
 COM_INTERFACE_ENTRY2(IDispatch, ICOutlookAddinSample)
 COM_INTERFACE_ENTRY(_IDTExtensibility2)
 .
 .
至此,COutlookAddinSample.h檔案的第一輪修改已經完成。
好了,我們接著改(為我們的COM按鈕新增事件響應):
f. 回到檔案頂部,新增申明:
extern _ATL_FUNC_INFO OnClickButtonInfo1;
extern _ATL_FUNC_INFO OnClickButtonInfo2;

g. 回到類定義處,新增對按鈕事件的支援:
class ATL_NO_VTABLE CCOutlookAddinSample : 
 . 
 .
 public IDispEventSimpleImpl<1,CCOutlookAddinSample,&__uuidof(Office::_CommandBarButtonEvents)>,
 public IDispEventSimpleImpl<2,CCOutlookAddinSample,&__uuidof(Office::_CommandBarButtonEvents)>
 .

h. 在類中新增型別定義:
 typedef IDispEventSimpleImpl<1,CCOutlookAddinSample, &__uuidof(Office::_CommandBarButtonEvents)> CommandButtonEvents1;
 typedef IDispEventSimpleImpl<2,CCOutlookAddinSample, &__uuidof(Office::_CommandBarButtonEvents)> CommandButtonEvents2;

i. 新增事件連線對映:
BEGIN_SINK_MAP(CCOutlookAddinSample)
 SINK_ENTRY_INFO(1,__uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton1, &OnClickButtonInfo1)
 SINK_ENTRY_INFO(2,__uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton2, &OnClickButtonInfo2)
END_SINK_MAP()

j. 新增兩個方法以響應按鈕事件:
// ICOutlookAddinSample
public:
 VOID __stdcall OnClickButton1(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);
 VOID __stdcall OnClickButton2(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);

k. 定義兩個變數儲存按鈕例項:
private:
 .
 .
 CComPtr<Office::_CommandBarButton> m_spButton1;
 CComPtr<Office::_CommandBarButton> m_spButton2;
 .
恩,至此該檔案就全部修改完畢了(別得意哦,下面還有很多事要做啊)。

6. 開啟COutlookAddinSample.cpp檔案。
a. 定義事件連線變數:
_ATL_FUNC_INFO OnClickButtonInfo1 = {CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};
_ATL_FUNC_INFO OnClickButtonInfo2 = {CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};

b. 新增按鈕事件響應方法:
VOID __stdcall CCOutlookAddinSample::OnClickButton1(IDispatch* /*Office::_CommandBarButton* */ Ctrl,VARIANT_BOOL * CancelDefault)
{
 MessageBox(GetForegroundWindow(),"¹þ¹þ£¬²âÊÔ°´Å¥£¡","sample",MB_OK | MB_ICONINFORMATION);
}

VOID __stdcall CCOutlookAddinSample::OnClickButton2(IDispatch* /*Office::_CommandBarButton* */ Ctrl,VARIANT_BOOL * CancelDefault)
{
 MessageBox(GetForegroundWindow(),"¹þ¹þ£¬²âÊÔµ¯³ö²Ëµ¥£¡","sample",MB_OK | MB_ICONINFORMATION);
}

c. 新增繼承自_IDTExtensibility2介面的方法:
STDMETHODIMP CCOutlookAddinSample::OnConnection(IDispatch * Application, ext_ConnectMode ConnectMode, IDispatch * AddInInst, SAFEARRAY * * custom)
{
 return S_OK;
}

STDMETHODIMP CCOutlookAddinSample::OnDisconnection(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom)
{
 return S_OK;
}
STDMETHODIMP CCOutlookAddinSample::OnAddInsUpdate(SAFEARRAY * * custom)
{
 return E_NOTIMPL;
}

STDMETHODIMP CCOutlookAddinSample::OnStartupComplete(SAFEARRAY * * custom)
{
 return E_NOTIMPL;
}

STDMETHODIMP CCOutlookAddinSample::OnBeginShutdown(SAFEARRAY * * custom)
{
 return E_NOTIMPL;
}
休息一下,到這裡,這個外掛的基本框架已經搭建完畢。

以下我們接著完成按鈕和彈出選單的建立以及按鈕事件到方法的連線:
e. 在OnConnection方法中首先獲得CommandBars介面指標
 .
 CComQIPtr<MSOutlook::_Application> spApp(Application);
 ATLASSERT(spApp);

 CComPtr<MSOutlook::_Explorer> spExplorer;
 spExplorer = spApp->ActiveExplorer();
 ATLASSERT(spExplorer);

 CComPtr<Office::_CommandBars> spCmdBars;
 HRESULT  hr = spExplorer->get_CommandBars(&spCmdBars);
 .
 .
在Outlook中比Word以及Excel都要多用一個方法才能得到CommandBars。
在Word以及Excel中直接可以通過Application物件獲得:spApp->get_CommandBars(&spCmdBars);

f. 接著建立自己的CommandBar:
 CComVariant vName("sample");
 CComVariant vPos(Office::msoBarTop);
 CComVariant vTemp(VARIANT_TRUE);
 CComVariant vEmpty(DISP_E_PARAMNOTFOUND,VT_ERROR);
 CComPtr<Office::CommandBar>  spNewCmdBar;

 spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);

 // 得到CommandBar的Controls集合,通過集合的Add方法新增自己的按鈕以及其他
 CComPtr<Office::CommandBarControls> spBarControls;
 spBarControls = spNewCmdBar->GetControls();
 ATLASSERT(spBarControls);

g. 為自己的CommandBar新增按鈕:
 CComVariant vToolBarType(Office::msoControlButton);
 CComVariant vShow(VARIANT_TRUE);
 CComPtr<Office::CommandBarControl> spNewBar;

 spNewBar = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
 ATLASSERT(spNewBar);

 CComQIPtr<Office::_CommandBarButton> spCmdButton1(spNewBar);
 ATLASSERT(spCmdButton1);

 spCmdButton1->PutStyle(Office::msoButtonIconAndCaption);
 spCmdButton1->PutVisible(VARIANT_TRUE);
 spCmdButton1->PutCaption("sample1");
 spCmdButton1->PutEnabled(VARIANT_TRUE);
 spCmdButton1->PutTooltipText("sample1");
 spCmdButton1->PutTag("sample1");

 HBITMAP hBmp = (HBITMAP)LoadImage(_Module.GetResourceInstance(),MAKEINTRESOURCE(IDB_SAMPLE),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
 ::OpenClipboard(NULL);
 ::EmptyClipboard();
 ::SetClipboardData(CF_BITMAP,(HANDLE)hBmp);
 ::CloseClipboard();
 ::DeleteObject(hBmp);
 hr = spCmdButton1->PasteFace();
 if(FAILED(hr))return hr;

h. 好了,我們就要建立彈出選單了,看好了:
// 首先我們需要建立一個Popup型別的CommandBarButton
// 實際上微軟稱他為:CommandBarPopup

 CComVariant vPopupType(Office::msoControlPopup);
 spNewBar = spBarControls->Add(vPopupType, vEmpty, vEmpty, vEmpty, vShow);
 ATLASSERT(spNewBar);

 CComQIPtr<Office::CommandBarPopup> spNewPopup(spNewBar);
 ATLASSERT(spNewPopup);

 spNewPopup->PutVisible(VARIANT_TRUE);
 spNewPopup->PutCaption("popup");
 spNewPopup->PutEnabled(VARIANT_TRUE);
 spNewPopup->PutTooltipText("popup");
 spNewPopup->PutTag("popup");

 // 同樣可以有自己的Controls集合
 // 以後往該集合中新增的按鈕都是這個Popup中的一個選單項啦
 CComPtr<Office::CommandBarControls> spPopupControls;
 spPopupControls = spNewPopup->GetControls();
 ATLASSERT(spPopupControls);

i. 恩,建立一個選單項試試:
 spNewBar = spPopupControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
 ATLASSERT(spNewBar);

 CComQIPtr<Office::_CommandBarButton> spCmdButton2(spNewBar);
 ATLASSERT(spCmdButton2);

 spCmdButton2->PutStyle(Office::msoButtonIconAndCaption);
 spCmdButton2->PutVisible(VARIANT_TRUE);
 spCmdButton2->PutCaption("sample2");
 spCmdButton2->PutEnabled(VARIANT_TRUE);
 spCmdButton2->PutTooltipText("sample2");
 spCmdButton2->PutTag("sample2");

 hBmp = (HBITMAP)LoadImage(_Module.GetResourceInstance(),MAKEINTRESOURCE(IDB_SAMPLE),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
 ::OpenClipboard(NULL);
 ::EmptyClipboard();
 ::SetClipboardData(CF_BITMAP,(HANDLE)hBmp);
 ::CloseClipboard();
 ::DeleteObject(hBmp);
 hr = spCmdButton2->PasteFace();
 if(FAILED(hr))return hr;

j. 現在可以儲存變數以及進行事件連線了:
 m_spApp = spApp;

 m_spButton1 = spCmdButton1;
 hr = CommandButtonEvents1::DispEventAdvise((IDispatch*)m_spButton1);
 if(FAILED(hr))return hr;

 m_spButton2 = spCmdButton2;
 hr = CommandButtonEvents2::DispEventAdvise((IDispatch*)m_spButton2);
 if(FAILED(hr))return hr;
到這裡,OnConnection方法就已經完成了:)

7. 在OnDisconnection方法中斷開事件的連線:
 HRESULT hr = CommandButtonEvents1::DispEventUnadvise((IDispatch*)m_spButton1);
 if(FAILED(hr))return hr;

 hr = CommandButtonEvents2::DispEventUnadvise((IDispatch*)m_spButton2);
 if(FAILED(hr))return hr;

好了,終於又完成一篇文章了。
寫的很累啊,即使是像我這樣CP,也覺得很累啊(老了?!)!
呵呵,也不知道寫的是不是清楚,或許大家看起來很覺得不知所然吧?
唉,文學細胞太少,看樣子不是做文學的料啊,實在不明白的還是請大家讀我的工程原始碼吧:)