Office2000下内部COM插件的程式設計實作
譯者:徐景周
下載下傳示例源代碼
簡介
你也許曾在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的内部插件的全過程。效果圖如下所示:
Office2000下内部COM插件的程式設計實作 打開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 :
public CComObjectRootEx<CComSingleThreadModel>,
……
public IDispEventSimpleImpl<1,COutlookAddin,&__uuidof(Office::_CommandBarButtonEvents)>
2. 聲明_ATL_SINK_INFO結構回調參數資訊。在OutlookAddin.h檔案中加入下面語句: // 按鈕事件響應資訊聲明
extern _ATL_FUNC_INFO OnClickButtonInfo;
在OutlookAddin.cpp檔案中加入定義語句,如下: // 按鈕事件響應資訊定義
_ATL_FUNC_INFO OnClickButtonInfo ={CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};
3. 加入Sink映射,如下: EGIN_SINK_MAP(COutlookAddin)
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()
4. 加入事件函數。在OutlookAddin.h中加入聲明: 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
譯者聯系方式:
位址:陝西省西安市勞動路2号院六單元
郵編:710082
編者EMAIL:[email protected]
未來工作室(Future Studio)