天天看點

使用VC++/ATL建立一個Office2K AddIn Com 元件

原文: http://www.xaradio.com/errorpages/GeneralError.htm?aspxerrorpath=/ShowFAQ.aspx

概況:

通過這篇文章,我們将會了解怎樣使用純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 * 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* 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), 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< 4,CAddin,

&__uuidof(Outlook::ApplicationEvents)> AppEvents;

....

....

....

BEGIN_SINK_MAP(CAddin)

....

SINK_ENTRY_INFO(4,__uuidof(Outlook::ApplicationEvents),

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(并不直接關聯到應用程式)。沒有做不到的隻有想不到的。

繼續閱讀