簡介
你也許曾在Office2000下的Word2000、Access2000、Excel2000、PowerPoint2000等軟件中的工具條或菜單條資源中,看到一些其它軟件加入的新的自定義工具條按鈕或菜單條,當點擊它們時,會有其不同的響應發生。下面,讓我們也來實現這些功能,需要說明的是,在這裡我們不用VB/VBA來實現它,而是用VC6中所帶ATL(活動模板庫)3.0來開發具有這種效果的Office2000內部COM插件。在Office2000中,不管是Word2000、Access2000、Excel120000、PowerPoint2000還是Outlook2000等,它們COM插件的編程方法及步驟都是極其相似的(除注冊表中鍵值及導入相應類型庫不同外)。
基礎知識
一個Office2000下的內部COM插件必須實現一個_IDTExtensibility2派發接口,_IDTExtensibility2派發接口被定義在MSADDin Designer類型庫(MSADDNDR.dll/MSADDNDR.tlb)中,通常位於<盤符>/Program Files/Common Files/Designer下。_IDTExtensibility2接口中必須實現下面五個接口函數(一般只需編寫OnConnection和OnDisconnection中代碼),分別如下:
1. OnConnection: 裝載插件到內存時處理(可以通過自動化在程序啟動時自動裝載插件)。
2. OnDisconnection: 從內存中缷載插件時處理。
3. OnAddinsUpdate: COM插件改變時處理。
4. OnStartupComplete: 當應用程序啟動時插件剛裝載完成時處理。
5. OnBeginShutdown: 當應用程序關閉時插件剛缷載完成時處理。
注冊插件
只有在正確注冊了相應應用程序的內部COM插件時,才能被其應用程序加載上。需要在注冊表中創建以下鍵值:
HKEY_CURRENT_USER\Software\Microsoft\Office\<TheOfficeApp>\Addins\<ProgID>
其中,TheOfficeApp表示相應程序名,如:Word、Outlook等,ProgID表示內部COM插件程序的唯一標識符的字符串表示形式,如:Outlook2000Addin.Addin等。
ProgID鍵值下主要創建以下四個鍵值:
1. FriendlyName: 字符串類型,插件的名稱,將在相應程序的COM加載對話框中看到。
2. Description: 字符串類型,插件的描述信息。
3. LoadBehavior: DWORD類型,決定插件將以什麼形式被裝載。當其值為0x03時,為應用程序裝載時被自動裝載(一般使用此值)、當其值為0x08時,為用戶控制激活裝載。
4. CommandLineSafe: DWORD類型,命令行方式,可以設置為0x01(真)或0x00(假)。
其它鍵值的完整描述可參看最新MSDN。
具體實現
下面,我們將以創建一個Outlook2000的內部COM插件為示例,向你一步步的展現如何最小化的創建一個Office2000的內部插件的全過程。效果圖如下所示:
打開VC6.0,在新建工程中選中ATL COM Appwizard,在右側工程名中輸入OutlAddin,點擊下一步,接受默認選項Dynamic Link Library(DLL)不變,可以選中下面的Allow merging of proxy-stub code(允許合並代理/占位)復選框選項,點擊Finish(完成)按鈕完成工程創建。
接著,選取菜單Insert->New ATL Objec項,在彈出的ATL對象向導對話框中選中相應Objects對應右側的Simple Object選項,點擊下一步,在彈出的對話框中ShortName中輸入OutlookAddin,如果需要的話,還可以在Attributes(屬性頁)中選中Support ISupportErrorInfo復選框選項,點OK完成插入ATL對象。
接著通過導入類型庫來實現_IDTExtensibility2接口,編釋好上面所建工程後,在ClassView中的COutlookAddin類上點鼠標右鍵,在彈出的右鍵菜單中選Implement Interface項。在彈出的實現接口對話框中點擊Add Typelib,在彈出的Browse Type Libraries對話框中,向下滾動選取Microsoft Add-in Designer(1.0)子項,點OK按鈕。在彈出的接口列表對話框中選中_IDTExtensibility2接口,點OK按鈕完成導入。系統會自動為你生成空的上面所提到的五個所需接口函數。
接著注冊編譯好的插件,在FileView->Resource Files中,打開OutlookAddin.rgs注冊文件,在該文件的最下面加入下面新的內部插件注冊碼:
// 新增Outlook2k內部插件注冊鍵值
HKCU
{
Software
{
Microsoft
{
Office
{
Outlook
{
Addins
{
''OutlAddin.OutlookAddin''
{
val FriendlyName = s ''Outlook2000插件''
val Description = s ''使用ATL開發的Outlook2000的插件''
val LoadBehavior = d ''00000003''
val CommandLineSafe = d ''00000000''
}
}
}
}
}
}
}
編譯此工程,如果注冊正確的話,將可以在Outlook中COM加載項的插件對話框中看到它的相應名稱。在Office2000中加載或卸載COM插件,一般可以按下面步驟進行:
1. 如果已經將“COM 加載項”命令添加到了“工具”菜單,請跳到第 6 步。
2. 單擊“工具”菜單中的“自定義”命令,然後單擊“命令”選項卡。
3. 在“類別”框中,選擇“工具”。
4. 將“COM 加載項”從“命令”框拖動到“工具”菜單。當“工具”菜單顯示菜單命令時,將鼠標指針指向希望“COM 加載項”命令出現在菜單上的位置,然後釋放鼠標按鈕。
5. 單擊“關閉”按鈕。
6. 單擊“工具”菜單中的“COM 加載項”命令,並執行下列操作之一:
● 要添加加載項,請選中“可使用的加載項”列表中加載項名稱旁邊的復選框。如果所需的加載項不在“可使用的加載項”列表中,請單擊“添加”按鈕,找到要添加的加載項,然後單擊“確定”按鈕。
● 要從內存中卸載加載項,但希望在列表中保持其名稱,請清除加載項名稱旁邊的復選框。
● 若要從列表中刪除加載項的同時,將其從注冊表文件中注冊的加載項列表中刪除,請選擇加載項的名稱,然後單擊“刪除”按鈕。
在Office應用程序中,盡管菜單和工具欄按鈕看上去不太一樣,但實質上它們是相同類型的對象。CommandBars集合包含程序中的所有命令條,如:工具條和菜單條。每一個CommandBars集合都有一個CommandBar對象和它對應,CommandBar 對象可以包含其它的 CommandBar 對象,這些對象是作為按鈕或菜單命令來用的。每一個CommandBar都將通過CommandBarControls 對象被引用,CommandBarControls又可以包含一組CommandBarControl對象。每一個CommandBarControl可以包含一個CommandBar對象,並可以通過它來存取控件屬性。每一個CommandBarControl對象,實際是對應CommandBarControls中的控件集合。CommandBarControl可以有三種表現形式:
● 彈出式(CommandBarPopup):相當於菜單條的一個菜單項。
● 組合框(CommandBarComboBox):類似於工具條中組合框控件。它包括一個工具欄和緊接著工具欄的一個下拉箭頭。單擊該按鈕,將顯示出更多的帶圖標的菜單命令。
● 按鈕(CommandBarButton):相當於標准的工具欄按鈕,即帶有圖標的按鈕。
在下面的示例程序中,我們將在Outlook2K中新建一個工具條並在其上添加二個按鈕,並且在其菜單“工具”中新建一個菜單條,這些操作都可以在OnConnection接口函數中完成。
首先,我們需要在工程中導入Office和Outlook類型庫,可以在Stdafx.h文件中加入下面語句(注意:其中路徑可根據Office所裝路徑自行設定):
// 導入工程所需Office2K及Outlook2K類型庫
#import "e:\Program Files\Microsoft Office\Office\mso9.dll" rename_namespace("Office"), named_guids
using namespace Office;
#import "e:\Program Files\Microsoft Office\Office\MSOUTL9.olb" rename_namespace("Outlook"),
raw_interfaces_only, named_guids
using namespace Outlook;
其次,讓我們來在Outlook中新建一個工具條,並且在其上添加兩個按鈕。
代碼如下:// 裝卸插件時處理
STDMETHOD(OnConnection)(IDispatch * Application,
ext_ConnectMode ConnectMode,
IDispatch * AddInInst,
SAFEARRAY * * custom)
{
CComPtr < Office::_CommandBars> spCmdBars;
// Outlook應用接口_Application
CComQIPtr <Outlook::_Application> spApp(Application);
ATLASSERT(spApp);
// 獲取CommandBars接口
CComPtr<Outlook::_Explorer> spExplorer;
spApp->ActiveExplorer(&spExplorer);
HRESULT hr = spExplorer->get_CommandBars(&spCmdBars);
if(FAILED(hr))
return hr;
ATLASSERT(spCmdBars);
// 新增一個工具條及其上兩個位圖按鈕
CComVariant vName("新增Outlook2K工具條插件");
CComPtr <Office::CommandBar> spNewCmdBar;
// 新增工具條位置
CComVariant vPos(1);
CComVariant vTemp(VARIANT_TRUE); // 臨時
CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
// 用Add方法在指定位置新增一工具條並讓spNewCmdBar指向它
spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);
// 獲取新增工具條的CommandBarControls,從而在其上添加按鈕
CComPtr < Office::CommandBarControls> spBarControls;
spBarControls = spNewCmdBar->GetControls();
ATLASSERT(spBarControls);
//MsoControlType::msoControlButton = 1
CComVariant vToolBarType(1);
//顯示工具條
CComVariant vShow(VARIANT_TRUE);
CComPtr < Office::CommandBarControl> spNewBar;
CComPtr < Office::CommandBarControl> spNewBar2;
// 用CommandBarControls中的Add方法新增第一個按鈕,並讓spNewBar指向它
spNewBar = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
ATLASSERT(spNewBar);
// 用CommandBarControls中的Add方法新增第二個按鈕,並讓spNewBar2指向它
spNewBar2 = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
ATLASSERT(spNewBar2);
// 為每一個按鈕指定_CommandBarButton接口,從面可以指定按鈕的顯示風格等
CComQIPtr < Office::_CommandBarButton> spCmdButton(spNewBar);
CComQIPtr < Office::_CommandBarButton> spCmdButton2(spNewBar2);
ATLASSERT(spCmdButton);
ATLASSERT(spCmdButton2);
// 設置位圖按鈕風格,位圖為32x32大小,將其放入剪切板中用PasteFace()貼在指定按鈕上
HBITMAP hBmp =(HBITMAP)::LoadImage(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDB_BITMAP),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
::CloseClipboard();
::DeleteObject(hBmp);
// 粘貼前設置顯示風格
spCmdButton->PutStyle(Office::msoButtonIconAndCaption);
hr = spCmdButton->PasteFace();
if (FAILED(hr))
return hr;
spCmdButton->PutVisible(VARIANT_TRUE);
spCmdButton->PutCaption(OLESTR("按鈕1"));
spCmdButton->PutEnabled(VARIANT_TRUE);
spCmdButton->PutTooltipText(OLESTR("按鈕1提示信息"));
spCmdButton->PutTag(OLESTR("按鈕1標志"));
// 顯示新增工具條
spNewCmdBar->PutVisible(VARIANT_TRUE);
// 設置第二個工具條按鈕風格
spCmdButton2->PutStyle(Office::msoButtonIconAndCaption);
// 第二個按鈕指定位圖為Outlook2K中預先定義的位圖
spCmdButton2->PutFaceId(1760);
spCmdButton2->PutVisible(VARIANT_TRUE);
spCmdButton2->PutCaption(OLESTR("按鈕2"));
spCmdButton2->PutEnabled(VARIANT_TRUE);
spCmdButton2->PutTooltipText(OLESTR("按鈕2提示信息"));
spCmdButton2->PutTag(OLESTR("按鈕2標志"));
spCmdButton2->PutVisible(VARIANT_TRUE);
m_spButton = spCmdButton;
m_spButton2 = spCmdButton2;
……
接著,讓我們在菜單"工具"中新建一個菜單條。 代碼如下:
_bstr_t bstrNewMenuText(OLESTR("新增菜單條"));
CComPtr < Office::CommandBarControls> spCmdCtrls;
CComPtr < Office::CommandBarControls> spCmdBarCtrls;
CComPtr < Office::CommandBarPopup> spCmdPopup;
CComPtr < Office::CommandBarControl> spCmdCtrl;
CComPtr < Office::CommandBar> spCmdBar;
// 通過CommandBar獲取Outlook主菜單
hr = spCmdBars->get_ActiveMenuBar(&spCmdBar);
if (FAILED(hr))
return hr;
// 獲取菜單條的CommandBarControls
spCmdCtrls = spCmdBar->GetControls();
ATLASSERT(spCmdCtrls);
// 在第5個"工具"菜單下新增一菜單條
CComVariant vItem(5);
spCmdCtrl= spCmdCtrls->GetItem(vItem);
ATLASSERT(spCmdCtrl);
IDispatchPtr spDisp;
spDisp = spCmdCtrl->GetControl();
// 獲取菜單條CommandBarPopup接口
CComQIPtr < Office::CommandBarPopup> ppCmdPopup(spDisp);
ATLASSERT(ppCmdPopup);
spCmdBarCtrls = ppCmdPopup->GetControls();
ATLASSERT(spCmdBarCtrls);
CComVariant vMenuType(1); // 控件類型 - menu
CComVariant vMenuPos(6);
CComVariant vMenuEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
CComVariant vMenuShow(VARIANT_TRUE); // 菜單將顯示
CComVariant vMenuTemp(VARIANT_TRUE); // 臨時
CComPtr < Office::CommandBarControl> spNewMenu;
// 用Add方法創建新的菜單條
spNewMenu = spCmdBarCtrls->Add(vMenuType,
vMenuEmpty,
vMenuEmpty,
vMenuEmpty,
vMenuTemp);
ATLASSERT(spNewMenu);
spNewMenu->PutCaption(bstrNewMenuText);
spNewMenu->PutEnabled(VARIANT_TRUE);
spNewMenu->PutVisible(VARIANT_TRUE);
// 利用CommandBarButton來在菜單條前顯示位圖
CComQIPtr < Office::_CommandBarButton> spCmdMenuButton(spNewMenu);
ATLASSERT(spCmdMenuButton);
spCmdMenuButton->PutStyle(Office::msoButtonIconAndCaption);
// 同新增工具條第一個按鈕位圖相同方法
spCmdMenuButton->PasteFace();
// 顯示菜單
spNewMenu->PutVisible(VARIANT_TRUE);
m_spMenu = spCmdMenuButton;
這樣,通過在Outlook中通過上面提到的方法加載COM插件,就可以看到如圖一所示的界面效果了,但是點擊時沒有響應,最後就讓我們來解決這個問題。
工具條按鈕CommandBarButton派發接口的響應事件是_CommandBarButtonEvents。ATL提供了二種模板類IDispEventImpl<>和IDispEventSimpleImpl<>來實現接口事件的接收,這裡我們使用IDispEventSimpleImpl來實現(因為它不需要額外的類型庫信息)。它需要設置SINK(接收)映射,通過_ATL_SINK_INFO結構來回調參數信息,最終通過DispEventAdvise和DispEventUnadvise來與源接口連接或斷開。實現方法如下:
1. 在COutlookAddin繼承類中加入IDispEventSimpleImpl繼承,代碼如下: class ATL_NO_VTABLE COutlookAddin :
2. 聲明_ATL_SINK_INFO結構回調參數信息。在OutlookAddin.h文件中加入下面語句:
public CComObjectRootEx<CComSingleThreadModel>,
……
public IDispEventSimpleImpl<1,COutlookAddin,&__uuidof(Office::_CommandBarButtonEvents)>// 按鈕事件響應信息聲明
在OutlookAddin.cpp文件中加入定義語句,如下:
extern _ATL_FUNC_INFO OnClickButtonInfo;// 按鈕事件響應信息定義
3. 加入Sink映射,如下:
_ATL_FUNC_INFO OnClickButtonInfo ={CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};EGIN_SINK_MAP(COutlookAddin)
4. 加入事件函數。在OutlookAddin.h中加入聲明:
SINK_ENTRY_INFO(1, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton1, &OnClickButtonInfo)
SINK_ENTRY_INFO(2, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton2, &OnClickButtonInfo)
SINK_ENTRY_INFO(3, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickMenu, &OnClickButtonInfo)
END_SINK_MAP()void __stdcall OnClickButton1(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);
在OutlookAddin.cpp中加入實現: // 工具條按鈕1點擊事件響應函數
void __stdcall COutlookAddin::OnClickButton1(IDispatch* /*Office::_CommandBarButton* */ Ctrl,
VARIANT_BOOL * CancelDefault)
{
USES_CONVERSION;
CComQIPtr<Office::_CommandBarButton> pCommandBarButton(Ctrl);
HINSTANCE result=ShellExecute(NULL, _T("open"), _T("http://www.vckbase.com"), NULL,NULL, SW_SHOW);
}
5. 最後,打開或斷開與接口的連接。方法如下
● 在OnConnection接口函數的最後部分,加入下面代碼來打開連接:
CommandButton1Events::DispEventAdvise((IDispatch*)m_spButton);
● 在OnDisconnection接口函數中,加入下面代碼來斷開連接:
CommandButton1Events::DispEventUnadvise((IDispatch*)m_spButton);
到此就完成一個Office內部插件的最小需求了,大家可以編譯後打開Outlook2000看看效果如何,詳細代碼可參看文章所帶示例源碼,內有詳細注釋。
參考文獻:
Building an Office2K COM addin with VC++/ATL -- Amit Dey
ATL開發指南(第二版) – Tom Armstrong & Ron Patton
本文配套源碼