原文:http://www.xaradio.com/errorpages/GeneralError.htm?aspxerrorpath=/ShowFAQ.aspx
作者:Amit Dey 譯:劉濤
近來,我寫了一個outlook2000的Addin Com作為我建立CRM
工具的工程的一部分。當我為這個工程寫代碼的時候,我想這可能是一個很好的題目,因為我在internet上找到的與Office相關的資料大部分是VB/VBA
相關的,幾乎沒有與ATL相關的。
在這篇文章里的代碼并沒有進行優化,為了使讀者便于跟隨,我盡量將它寫的淺顯易懂。我寫這篇文章花了一些時間,并且也盡了我的最大努力,萬一還存在什么錯
誤,請爽快的給我發封郵件。如果你喜歡這篇文章或者覺得它讀起來很有趣,并給我一個高的評價或是發郵件告訴我你的看法,我將非常高興。謝謝!
概況:
通過這篇文章,我們將會了解怎樣使用純ATL Com 對象編寫Outlook2000/2K+
COM addin程序。我們將從寫一個最基本的Com
AddIn程序開始。接下來我將向你們展示怎樣將標準的界面元素比如工具欄或是菜單項加入到outlook中去,并響應他們的事件。緊接著,我們要為Outlook's
Tools->Options加入我們自己編寫的屬性表。接著我們將看一些相關的注冊鍵和ATL向導的一些非常有用的特征并且學習有效地使用他們。
雖然我們寫的是一個Outlook2000 COM
addin的程序。但是Office2000的應用程序,比如Word,Access等等,他們的Com
AddIn的寫法是非常相似的。除了注冊鍵和接口,其余的部分基本上是一樣的。
我假設你是一個VC++
Com的開發人員,并且也有一些基于ATL的組件開發和OLE/自動化方面的經驗,盡管這也不是必須的。創建和測試這個AddIn程序,你必須安裝Office2000,至少有outlook2000。程序代碼使用VC++
6.0
sp3+/ATL3.0創建,使用的操作系統是:安裝了Office2000的Windows2000。
開始:
Office AddIn 是一個可以動態擴充和增強的Com
自動化組件,可以控制任何的Office應用程序。微軟的Office2000和以后的版本都支持創建Add_Ins的一個新的、統一的應用設計架構。AddIn通常都被置于一個ActiveX動態庫中(進程內服務器),并且能被用戶動態的從主程序中引導和卸載。
Office AddIn 必須實現 _IDTExtensibility2
接口。IDTExtensibility2接口定義于MSADDin Designer typelibrary
(MSADDNDR.dll/MSADDNDR.tlb)文件中。一般在/Program Files/Common
Files/Designer目錄下。
接口象這樣定義:
enum {
ext_cm_AfterStartup = 0,
ext_cm_Startup = 1,
ext_cm_External = 2,
ext_cm_CommandLine = 3
} ext_ConnectMode;
enum {
ext_dm_HostShutdown = 0,
ext_dm_UserClosed = 1
} ext_DisconnectMode;
...
...
...
interface _IDTExtensibility2 : IDispatch
{
[id(0x00000001)]
HRESULT OnConnection(
[in] IDispatch* Application,
[in] ext_ConnectMode ConnectMode,
[in] IDispatch* AddInInst,
[in] SAFEARRAY(VARIANT)* custom);
[id(0x00000002)]
HRESULT OnDisconnection(
[in] ext_DisconnectMode RemoveMode,
[in] SAFEARRAY(VARIANT)* custom);
[id(0x00000003)]
HRESULT OnAddInsUpdate([in] SAFEARRAY(VARIANT)* custom);
[id(0x00000004)]
HRESULT OnStartupComplete([in] SAFEARRAY(VARIANT)* custom);
[id(0x00000005)]
HRESULT OnBeginShutdown([in] SAFEARRAY(VARIANT)* custom);
};
所有的Com
AddIn繼承于IDTExtensibility2,而且必須實現他的五個方法。
當AddIn被引導和卸載的時候,OnConnection 和 OnDisconnection,
就像他們的名字顯示的一樣。AddIn程序可以被引導,也可以在應用程序使用過程中被用戶啟動或者通過自動化和enumerator
ext_Connect 指示連接到那些模塊。當一組Com
AddIn組件被改變,那么OnAddinsUpdate被調用。OnStartupComplete
只有在應用程序使用過程中啟動Com
AddIn組件時才被調用,如果AddIn在主應用程序被關掉的時候斷開與主應用程序的連接,那么OnBeginShutdown
被調用。
注冊AddIn組件:
使用主應用程序注冊AddIn組件,我們需要在注冊表目錄:
HKEY_CURRENT_USER\Software\Microsoft\Office\<TheOfficeApp>\Addins\<ProgID>
下創建兩個子鍵,這里ProgID指的是Addin
Com對象的唯一標識符。別的入口通過AddIn提供的關于他自己的信息和制定的引導選項給主應用程的是:
FriendlyName – 字符串 –
主應用程序顯示的這個AddIn程序的名字。
Description – 字符串 – 對AddIn的描述.
LoadBehavior - DWORD 值.
–一個決定AddIn怎樣被主應用程序引導的值的組合。 設置成 0x03
表示主應用程序啟動時引導,設置成0x08表示由用戶來激活。
CommandLineSafe - DWORD 值. 0x01(TRUE) 或者 0x00(FALSE).
對于所有值和可選項的完整描述,請參考MSDN。
創建一個小的Com AddIn:
現在我們了解了足夠的知識,應該朝前一步編寫一個小的Outlook2K COM
addin。創建一個新的ATL COM Appwizard
工程,命名為OutlookAddin。記住如果你把他命名成別的,他可能會不能運行(開個玩笑)。
在向導的第一個對話框中接收默認的服務器類型Dynamic Link
Library(DLL),檢查Allow merging of proxy-stub
code,選擇這個可選項,點擊完成。接著點擊OK,產生工程文件。
下一步,點擊Insert->New ATL
Object菜單項,通過從Category中選擇Objects從Objects列表中選擇Simple
Object插入一個ATL simple
object到工程中。點擊Next,輸入”AddIn”作為ShortName,在屬性表里選上Support
ISupportErrorInfo。接受剩下的默認選項,然后點擊OK。
到現在為止,向導已經給我們了一個置于動態鏈接庫中的自動化兼容的、DispInterface-savvy的進程內的Com對象。默認的情況下,一個加到Com對象上的指定注冊值的注冊腳本文件被提交給我們。Build這個工程,看看一切是否運行良好。
如果你想我一樣雄心勃勃,起碼在繼續往下進行前還應該編譯你工程中的.idl文件。現在就去做吧。
接下來我們為AddIn寫一些特定的代碼去實現IDTExtensibility2
接口。在類視圖里,我們在CAddIn類上右鍵點擊,選擇Implement
Interface,這將帶出ATL Implement Interface 向導。點擊Add
Typelib,在Browse Typelibraries對話框里向下滾動,選上Microsoft
Add-in
Designer(1.0),點擊OK。在AddinDesignerObjects列表中選擇_IDTExtensibility2接口點擊OK。
向導為IDTExtensibility2接口的五個方法中每一個生成默認的實現,將他們加到CAddIn類中,并且更新
COM_INTERFACE_MAP()宏。當然在加有些有用的代碼之前每個方法都只會返回E_NOTIMPL。現在,為ComAddIn進行必要的注
冊,我們的Com
AddIn已經就緒了。
使用主應用程序注冊我們的Addin組件。如果是outlook2000,打開工程的AddIn.rgs注冊腳本文件。把下面的代碼加到文件的結尾。
HKCU
{
Software
{
Microsoft
{
Office
{
Outlook
{
Addins
{
'OutlookAddin.Addin'
{
val FriendlyName = s 'ADOutlook2K Addin'
val Description = s 'ATLCOM Outlook Addin'
val LoadBehavior = d '00000008'
val CommandLineSafe = d '00000000'
}
}
}
}
}
}
}
既然我們希望在程序啟動的時候AddIn被引導,那么LoadBehavior設置為3。現在Build這個工程。如果一切順利,那么將會創建成功并且注
冊了這個AddIn。為了測試這個AddIn,我們要運行這個工程并輸入完整的outlook.exe的完整的路徑(\Program
Files\Microsoft
Office\Office\Outlook.exe),或者在注冊了這個DLL之后從VC++IDE環境外運行outlook。如果你的AddIn被成
功的注冊了,那么在outlook里,點擊Tools->Options,在Other頁點擊Advanced
Options->COM
Addins,我們的AddIn應該已經出現在可獲得的AddIns的列表中。字符串是我們在腳本中為'FriendlyName'指定的值。
AddIn可以被編寫來執行各種不同的任務。典型的,包括為outlook添加一些界面元素,比如工具條和菜單項,而且用戶可以控制AddIn。通過點擊這個工具條按鈕和菜單項,用戶可以實現AddIn的功能。接下來我們將制作這樣一個工具條和附加的菜單項。
命令與征服:
在Office應用程序中,菜單和工具條被組合在一個名叫“CommandBars
“的完全可編程的集合中。CommandBars通常是可共享可編程的對象,并且作為所有的office應用程序的一部分被暴露。CommandBars
代表一個同一的機制,通過他可以將單個的工具條和菜單項加到相應的應用程序里。每一個CommandBars由幾個獨立的CommandBar對象組成。
每一個CommandBar又由CommandBarControl對象集合組成,這個集合被叫做CommandBarControls。
CommandBarControls代表了一個復雜的對象和組成它的子對象層次。一個CommandBarControl能被包含在一個
CommandBar中,并且通過控件的CommandBar屬性訪問。最后每一個在控件的CommandBarControls集合中的
CommandBarControl即可能是CommandBarComboBox、CommandBarButton(工具條按鈕)也可能是
CommandBarPopup(彈出式菜單)。我很希望我能畫出一個代表這個對象層次的圖例,但是我很不擅長這個(我很誠實!)。我保證在MSDN中一
定有關于MS
Office CommandBars描述的圖例。
在我們的AddIn里,我想加入以下的界面元素:
? 在一個新的工具條里加入兩個位圖按鈕。
? 在“Tool“菜單里添加一個新的帶位圖的彈出式菜單項。
首先,我們應該將office和outlook的類型庫導入到我們的工程中。我們打開stdAfx.h,然后添加以下語句:
#import "C:\Program Files\Microsoft Office\Office\mso9.dll" \
rename_namespace("Office") named_guids
using namespace Office;
#import "C:\Program Files\Microsoft
Office\Office\MSOUTL9.olb"
rename_namespace("Outlook"), raw_interfaces_only, named_guids
using namespace Outlook;
注意:你應該改變這些路徑,是他們匹配你安裝的office的路徑。
好了,現在讓我們來看看代碼。首先式ToolBand和ToolBar Button。
在outlook模塊里,Application
對象位于代表整個應用程序的對象層次的最頂層。通過他的ActiveExplorer
方法我們可以得到代表當前窗口的Explorer對象。下來我們使用GetCommandBars方法得到CommandBars對象(他是
outlook工具條和菜單項的集合)。我們使用CommandBars集合的Add方法加上相應的參數就可以添加一個新的工具條。如果想向工具條中加入
按鈕只需要得到工具條的CommandBarControls集合,接著調用他的Add方法。最后我們為那些對應于按鈕的
CommandBarButton對象(我們可以用它來設置按鈕的風格和別的屬性,比如標題、提示和文本等等)。
代碼片斷如下:
STDMETHODIMP CAddin::OnConnection(IDispatch * Application,
ext_ConnectMode ConnectMode,
IDispatch * AddInInst, SAFEARRAY * * custom)
{
CComPtr < Office::_CommandBars>
spCmdBars;
CComPtr < Office::CommandBar> spCmdBar;
// QI() for _Application
CComQIPtr <Outlook::_Application> spApp(Application);
ATLASSERT(spApp);
// get the CommandBars interface that represents Outlook's
//toolbars & menu items
CComPtr<Outlook::_Explorer>
spExplorer;
spApp->ActiveExplorer(&spExplorer);
HRESULT hr =
spExplorer->get_CommandBars(&spCmdBars);
if(FAILED(hr))
return hr;
ATLASSERT(spCmdBars);
// now we add a new toolband to
Outlook
// to which we'll add 2 buttons
CComVariant vName("OutlookAddin");
CComPtr <Office::CommandBar> spNewCmdBar;
// position it below all toolbands
//MsoBarPosition::msoBarTop = 1
CComVariant vPos(1);
CComVariant vTemp(VARIANT_TRUE); // menu is
temporary
CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
//Add a new toolband through Add method
// vMenuTemp holds an unspecified parameter
//spNewCmdBar points to the newly created toolband
spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty,
vTemp);
//now get the toolband's
CommandBarControls
CComPtr < Office::CommandBarControls> spBarControls;
spBarControls = spNewCmdBar->GetControls();
ATLASSERT(spBarControls);
//MsoControlType::msoControlButton = 1
CComVariant vToolBarType(1);
//show the toolbar?
CComVariant vShow(VARIANT_TRUE);
CComPtr < Office::CommandBarControl>
spNewBar;
CComPtr < Office::CommandBarControl> spNewBar2;
// add first button
spNewBar = spBarControls->Add(vToolBarType, vEmpty, vEmpty,
vEmpty, vShow);
ATLASSERT(spNewBar);
// add 2nd button
spNewBar2 = spBarControls->Add(vToolBarType, vEmpty, vEmpty,
vEmpty, vShow);
ATLASSERT(spNewBar2);
_bstr_t
bstrNewCaption(OLESTR("Item1"));
_bstr_t bstrTipText(OLESTR("Tooltip for Item1"));
// get CommandBarButton interface for each
toolbar button
// so we can specify button styles and stuff
// each button displays a bitmap and caption next to it
CComQIPtr < Office::_CommandBarButton>
spCmdButton(spNewBar);
CComQIPtr < Office::_CommandBarButton>
spCmdButton2(spNewBar2);
ATLASSERT(spCmdButton);
ATLASSERT(spCmdButton2);
// to set a bitmap to a button, load a 32x32
bitmap
// and copy it to clipboard. Call CommandBarButton's
PasteFace()
// to copy the bitmap to the button face. to use
// Outlook's set of predefined bitmap, set button's FaceId to
//the
// button whose bitmap you want to use
HBITMAP hBmp
=(HBITMAP)::LoadImage(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDB_BITMAP1),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
// put bitmap into Clipboard
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
::CloseClipboard();
::DeleteObject(hBmp);
// set style before setting bitmap
spCmdButton->PutStyle(Office::msoButtonIconAndCaption);
HRESULT hr =
spCmdButton->PasteFace();
if (FAILED(hr))
return hr;
spCmdButton->PutVisible(VARIANT_TRUE);
spCmdButton->PutCaption(OLESTR("Item1"));
spCmdButton->PutEnabled(VARIANT_TRUE);
spCmdButton->PutTooltipText(OLESTR("Tooltip for Item1"));
spCmdButton->PutTag(OLESTR("Tag for Item1"));
//show the toolband
spNewCmdBar->PutVisible(VARIANT_TRUE);
spCmdButton2->PutStyle(Office::msoButtonIconAndCaption);
//specify predefined bitmap
spCmdButton2->PutFaceId(1758);
spCmdButton2->PutVisible(VARIANT_TRUE);
spCmdButton2->PutCaption(OLESTR("Item2"));
spCmdButton2->PutEnabled(VARIANT_TRUE);
spCmdButton2->PutTooltipText(OLESTR("Tooltip for Item2"));
spCmdButton2->PutTag(OLESTR("Tag for Item2"));
spCmdButton2->PutVisible(VARIANT_TRUE);
//..........
//..........
//code to add new menubar to be added here
//read on
//..........
我們用相似的方法來給outlook的Tools菜單添加菜單項,我們照以下方法做。CommandBars的ActiveMenuBar屬性返回一個表
示在Application容器中活動的菜單。我們通過GetControls方法找到活動的菜單控件集合。我們想要加入一個彈出式的菜單項到
outlook的Tools菜單(第6個菜單項),我們從Activemenubars控件集合中可以找到第6個菜單項,直接調用Add方法創建一個新的
菜單項并且將他連接到Tools菜單。這里沒有什么新東西。
相應的代碼片斷如下所示:
//......
//code to add toolbar here
//......
_bstr_t bstrNewMenuText(OLESTR("New Menu
Item"));
CComPtr < Office::CommandBarControls> spCmdCtrls;
CComPtr < Office::CommandBarControls> spCmdBarCtrls;
CComPtr < Office::CommandBarPopup> spCmdPopup;
CComPtr < Office::CommandBarControl> spCmdCtrl;
// get CommandBar that is Outlook's main
menu
hr = spCmdBars->get_ActiveMenuBar(&spCmdBar);
if (FAILED(hr))
return hr;
// get menu as CommandBarControls
spCmdCtrls = spCmdBar->GetControls();
ATLASSERT(spCmdCtrls);
// we want to add a menu entry to Outlook's
6th(Tools) menu //item
CComVariant vItem(5);
spCmdCtrl= spCmdCtrls->GetItem(vItem);
ATLASSERT(spCmdCtrl);
IDispatchPtr spDisp;
spDisp = spCmdCtrl->GetControl();
// a CommandBarPopup interface is the actual
menu item
CComQIPtr < Office::CommandBarPopup>
ppCmdPopup(spDisp);
ATLASSERT(ppCmdPopup);
spCmdBarCtrls =
ppCmdPopup->GetControls();
ATLASSERT(spCmdBarCtrls);
CComVariant vMenuType(1); // type of control
- menu
CComVariant vMenuPos(6);
CComVariant vMenuEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
CComVariant vMenuShow(VARIANT_TRUE); // menu should be
visible
CComVariant vMenuTemp(VARIANT_TRUE); // menu is
temporary
CComPtr < Office::CommandBarControl> spNewMenu;
// now create the actual menu item and add it
spNewMenu = spCmdBarCtrls->Add(vMenuType, vMenuEmpty,
vMenuEmpty,
vMenuEmpty, vMenuTemp);
ATLASSERT(spNewMenu);
spNewMenu->PutCaption(bstrNewMenuText);
spNewMenu->PutEnabled(VARIANT_TRUE);
spNewMenu->PutVisible(VARIANT_TRUE);
//we'd like our new menu item to look cool and display
// an icon. Get menu item as a CommandBarButton
CComQIPtr < Office::_CommandBarButton>
spCmdMenuButton(spNewMenu);
ATLASSERT(spCmdMenuButton);
spCmdMenuButton->PutStyle(Office::msoButtonIconAndCaption);
// we want to use the same toolbar bitmap for menuitem
too.
// we grab the CommandBarButton interface so we can add
// a bitmap to it through PasteFace().
spCmdMenuButton->PasteFace();
// show the menu
spNewMenu->PutVisible(VARIANT_TRUE);
return S_OK;
}
點擊F5,如果一切都沒問題,那么工程將成功建立,并且你將第一次看見你的AddIn程序的運行。現在我們運行outlook來測試我們的AddIn。在'Executable
for
Debug'對話框,設置outlook可執行程序的當前路徑,現在我們準備測試。在outlook中點擊Tools->Option,點擊Other頁面,點擊Advanced
Options。在Advanced Option對話框中,點擊Com AddIns
按鈕。接著從可獲得的AddIns列表中選擇我們的AddIn并點擊OK。當我們的AddIn被引導,一個停靠工具條將被創建,你也可以看到你加入到Tools菜單的菜單項。
他們就在那里!一個有你寫的AddIn的outlook,一個帶有很酷的工具條和新的菜單項的擴展的outlook!感謝ATL!你的小于50Kb的AddIn同樣提供了輕量級的有意義的Com服務。享受這一刻吧!
單單放置兩個工具條按鈕和一個菜單項并沒有什么用處,除非我們寫命令處理代碼和響應他們的事件。現在我們回到正題。當然在這里,點擊不同的按鈕和菜單項,我們緊緊彈出簡單的對話框。這就是你添加AddIn功能的地方。從CRM
工具、自動聯系管理、郵件通知、郵件過濾到高級的文檔管理到各種各樣的應用,Com
AddIns可以執行各種各樣的任務的驗證。
CommandBarButton控件暴露了一個點擊事件(當用戶點擊一個Command Bar
按鈕時觸發)。當用戶點擊工具條按鈕或者是點擊菜單項的時候我們將使用這個事件去運行代碼。對于這些,我們的Com
AddIn對象不得不處理_CommandBarButtonEvents事件。點擊事件被聲明如下:
//...
//....Office objects typelibrary
//....
[id(0x00000001), helpcontext(0x00038271)]
void Click(
[in] CommandBarButton* Ctrl,
[in, out] VARIANT_BOOL* CancelDefault);
//....
//...
我們不得不做所有我們能做的事情去實現那些將被事件源通過規范的連接點協議調用的接收器接口(無論什么時候一個工具條按鈕或菜單項被點擊)。通過回調函數我們可以得到一個源CommandBarButton
對象的指針和一個用來接受和取消默認操作的布爾值。就像實現一個dispatch接收器接口一樣,那也不是什么新東西,作為一個ATL程序員你可能要花一段時間去做這些。
但是對于那些非初始化的,ATL為ATLCom對象提供兩個模板類IDispEventImpl<>
和 IDispEventSimpleImpl<>
,這為IDispatch接口提供了實現。我更喜歡用輕量級的IDispEventSimpleImpl,因為它不需要另外的類型庫信息。你的類緊緊源于
IDispEventSimpleImpl<>。建立你的接收器映射,通過_ATL_SINK_INFO結構體設置你的回調參數,最后調用
DispEventAdvise
和
DispEventUnadvise從源接口連接和斷開。對于我們的工具條按鈕和菜單項,如果我們要寫一個單一的回調函數來處理所有的事件,那么,一旦我
們有一個指向觸發事件的CommandBarButton的指針,我們可以使用GetCaption去得到這個按鈕的文本,在這個基礎上,我們可以執行一
些選擇性的動作。但是對于這個例子,我們為每一個事件編寫一個回調函數。
下面是編寫的步驟:
使你的類繼承于IDispSimpleEventImpl-第一個參數是封裝在ActiveX控件中的子窗口的ID。但是對于我們來說,它可以是任何預先定義的唯一標識事件源的整數(在這里指的是第一個工具條按鈕)。
class ATL_NO_VTABLE CAddin :
public CComObjectRootEx < CComSingleThreadModel>,
.....
.....
public
IDispEventSimpleImpl<1,CAddin,&__uuidof(Office::_CommandBarButtonEvents>
建立回調函數-第一個我們定義的,如下所示:
void __stdcall OnClickButton(IDispatch *
/*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL *
CancelDefault);
接下來我們使用_ATL_SINK_INFO結構去描述回調參數。打開AddIn.h文件,在文件頂部添加如下聲明:
? extern _ATL_FUNC_INFO OnClickButtonInfo;
接著打開AddIn.cpp,添加如下定義:
? _ATL_FUNC_INFO OnClickButtonInfo
={CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};
OnClickButton是非常基礎的,就像下面的:
? void __stdcall CAddin::OnClickButton(IDispatch*
/*Office::_CommandBarButton* */ Ctrl,
? VARIANT_BOOL * CancelDefault)
? {
? USES_CONVERSION;
? CComQIPtr<Office::_CommandBarButton>
pCommandBarButton(Ctrl);
? //the button that raised the event. Do something with
this...
? MessageBox(NULL, "Clicked Button1", "OnClickButton",
MB_OK);
?
? }
我們使用ATL宏BEGIN_SINK_MAP() 和
END_SINK_MAP()建立接收器消息映射。接收器消息映射由SINK_ENTRY_XXX組成。接收器消息映射提供定義事件的Dispatch
ID和處理他的成員函數。
? BEGIN_SINK_MAP(CAddin)
? SINK_ENTRY_INFO(1,
__uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01,
? OnClickButton, &OnClickButtonInfo)
? END_SINK_MAP()
現在每一件事情都到位了,我們不得不使用DispEventAdvise() and
DispEventUnadvise()連接和斷開事件源.我們的CAddIn類的OnConnection()
和OnDisconnection()僅僅是替代了這些。對于DispEventAdvise() and
DispEventUnadvise()的參數分別是事件源上的任何的接口和任何被期望的事件源上的接口。
//connect to event source in OnConnection
// m_spButton member variable is a smart pointer to
_CommandBarButton
// that is used to cache the pointer to the first toolbar
button.
DispEventAdvise((IDispatch*)m_spButton,&DIID__CommandBarButtonEvents);
//when I'm done disconnect from the event source
//some where in OnDisconnection()
DispEventUnadvise((IDispatch*)m_spButton);
為我們的命令按鈕和菜單項實現Dispatch
接收器是很相似的,寫處理代碼并且連接和斷開他們就像上面的描述。如果每一步都進行的暢通無阻,在你Rebuild你的程序并且運行它。無論什么時候,按鈕和菜單項被點擊,你的回調函數將被執行。
添加屬性頁:
在這篇文章里我們最后要學會去做的是添加我們自己的“Option“屬性頁到outlook的Tools->Option的屬性表中。
下來我們要加一個頁到outlook的option菜單里作為我們我們的AddIn的一部分。我們將象ActiveX控件一樣實現實現屬性頁。當用戶點擊
Tools->Option菜單項,應用程序對象發出一個OptionsPagesAdd事件(通過outlook對象模塊中的
_ApplicationEvents接口)。
dispinterface ApplicationEvents
{
....
[id(0x0000f005), helpcontext(0x0050df87)]
void OptionsPagesAdd([in] PropertyPages* Pages);
....
}
[
odl,
uuid(00063080-0000-0000-C000-000000000046),
helpcontext(0x0053ec78),
dual,
oleautomation
]
....
....
interface PropertyPages : IDispatch {
[id(0x0000f000), propget, helpcontext(0x004deb87)]
HRESULT Application([out, retval] _Application**
Application);
....
....
[id(0x0000005f), helpcontext(0x00526624)]
HRESULT Add([in] VARIANT Page,
[in, optional] BSTR Title);
[id(0x00000054), helpcontext(0x00526625)]
HRESULT Remove([in] VARIANT Index);
};
OptionsPagesAdd事件傳遞給我們我們一個PropertyPages
Dispatch接口,他的Add方法用來添加頁。Add方法的參數是我們的控件的ProgID和新的頁的標題文本。相似的,我們調用Remove()方法和要刪除頁的索引來刪除頁。
現在我們來加一個ActiveX復合控件。我們點擊Insert->new ATL
Object.從Category中選擇Controls,從Object列表中選擇Lite Composite
Control,點擊OK。在ShortName中輸入PropPage,在屬性頁面選上Support
ISupportErrorInfo選項。點擊Ok,接受所有的默認選項。
現在我們要來實現PropertyPage接口。在類視圖里右鍵點擊CPropPage,選擇Implement
Interface,點擊Add TypeLib按鈕。選中Microsoft Outlook 9.0 Object
Library 點擊OK。從接口列表中選擇PropertyPage點擊OK。
向導自動為PropertyPage接口添加三個方法:Apply()、Get_Dirty()、GetPageInfo()
。現在做下面的修改,在Com Map中把這一行:
COM_INTERFACE_ENTRY(IDispatch)
改成:
COM_INTERFACE_ENTRY2(IDispatch,IPropPage)
以排除不明確的地方。
接下來實現IDispatch,我們使用IDispatchImpl<>模板類。我們在CPropPage類的聲明部分用以下代碼:
public IDispatchImpl <
Outlook::PropertyPage,&__uuidof(Outlook::PropertyPage),
&LIBID_OUTLOOKADDINLib>
替換掉:
class ATL_NO_VTABLE CPropPage :
public CComObjectRootEx<CComSingleThreadModel>,
public IDispatchImpl<IPropPage, &IID_IPropPage,
&LIBID_TRAILADDINLib>,
....
....
public PropertyPage
從PropPage.h文件的頂部刪掉多余的#import語句。類型庫已經在stdAfx.h中導入了,因此這里沒有必要再導入。
下來我們要連接和斷開ApplicationEvents接口,并為他寫回調函數。你已經知道該做什么了。我們再次使用
IDispEventSimpleImpl<>為ApplicationEvents建立Dispatch接收器,更新接收器映射,為
OptionsAddPage事件寫回調函數。因為我們多次使用了IDispEventSimpleImpl<>,
我們為每一個接口事件使用TypeDef。代碼片段如下:
extern _ATL_FUNC_INFO OnOptionsAddPagesInfo;
class ATL_NO_VTABLE CAddin :
....
....
public
IDispEventSimpleImpl<4,CAddin,&__uuidof(Outlook::ApplicationEvents)>
{
public:
//typedef for applicationEvents sink implementation
typedef IDispEventSimpleImpl</*nID =*/ 4,CAddin,
&__uuidof(Outlook::ApplicationEvents)> AppEvents;
....
....
....
BEGIN_SINK_MAP(CAddin)
....
SINK_ENTRY_INFO(4,__uuidof(Outlook::ApplicationEvents),
/*dispid*/0xf005,OnOptionsAddPages,&OnOptionsAddPagesInfo)
END_SINK_MAP()
public:
//callback method for OptionsAddPages event
void __stdcall OnOptionsAddPages(IDispatch *Ctrl);
};
//in PropPage.cpp file
_ATL_FUNC_INFO OnOptionsAddPagesInfo =
(CC_STDCALL,VT_EMPTY,1,{VT_DISPATCH}};
void __stdcall CAddin::OnOptionsAddPages(IDispatch* Ctrl)
{
CComQIPtr<Outlook::PropertyPages> spPages(Ctrl);
ATLASSERT(spPages);
//ProgId of the propertypage control
CComVariant varProgId(OLESTR("OutlookAddin.PropPage"));
//tab text
CComBSTR bstrTitle(OLESTR("OutlookAddin"));
HRESULT hr =
spPages->Add((_variant_t)varProgId,(_bstr_t)bstrTitle);
if(FAILED(hr))
ATLTRACE("\nFailed adding propertypage");
}
最后,在OnConnection和OnDisConnection里,調用DispEventAdvise 和
DispEventUnadvise連接和斷開ApplicationEvents。現在一切就緒,我們ReBuild工程。下來點擊F5,點擊
Outlook的Tools->Options菜單。你應該看見了我們新加的頁。但是當我們點擊這個新的頁,一個對話框將出現告訴我們屬性頁不能被
顯示。發生了什么?難道我們的辛苦勞動白費了?
發生這個情況的原因是:盡管我們的屬性頁創建了,但是outlook并沒有得到關于這個頁的鍵盤行為的任何信息。IOleControl的
GetControlInfo方法的ATL的默認實現返回E_NOTIMPL,因此包容器無法為這個屬性頁和包容器處理擊鍵事件。因此我們的頁不能被顯
示。修改這個問題,只需重載GetControlInfo()方法,讓他返回S_OK。
在.PropPage.h里添加如下聲明:
STDMETHOD(GetControlInfo)(LPCONTROLINFO lpCI);
我們在PropPage.cpp文件里重載GetControlInfo()方法,僅僅將返回值改為S_OK,代碼如下:
STDMETHODIMP CPropPage::GetControlInfo(LPCONTROLINFO lpCI)
{
return S_OK;
}
就是這些了。現在再次Build工程,點擊outlook的tools->Option,激活我們的頁,現在我們的屬性頁應該正確無誤的顯示了。
我們的學習要結束了。我們能在office里我們能做的事情無窮無盡。因為在一個AddIn里你可以獲得父應用程序的內部對象模塊,你能做所有主應用程序能做的事,或者更多。另外你也能使用別的接口比如MS
Assistant(并不直接關聯到應用程序)。沒有做不到的只有想不到的。