原文:http://www.xaradio.com/errorpages/GeneralError.htm?aspxerrorpath=/ShowFAQ.aspx
作者:Amit Dey 譯:劉濤
近來,我寫了一個(gè)outlook2000的Addin Com作為我建立CRM
工具的工程的一部分。當(dāng)我為這個(gè)工程寫代碼的時(shí)候,我想這可能是一個(gè)很好的題目,因?yàn)槲以趇nternet上找到的與Office相關(guān)的資料大部分是VB/VBA
相關(guān)的,幾乎沒有與ATL相關(guān)的。
在這篇文章里的代碼并沒有進(jìn)行優(yōu)化,為了使讀者便于跟隨,我盡量將它寫的淺顯易懂。我寫這篇文章花了一些時(shí)間,并且也盡了我的最大努力,萬一還存在什么錯(cuò)
誤,請爽快的給我發(fā)封郵件。如果你喜歡這篇文章或者覺得它讀起來很有趣,并給我一個(gè)高的評(píng)價(jià)或是發(fā)郵件告訴我你的看法,我將非常高興。謝謝!
概況:
通過這篇文章,我們將會(huì)了解怎樣使用純ATL Com 對象編寫Outlook2000/2K+
COM addin程序。我們將從寫一個(gè)最基本的Com
AddIn程序開始。接下來我將向你們展示怎樣將標(biāo)準(zhǔn)的界面元素比如工具欄或是菜單項(xiàng)加入到outlook中去,并響應(yīng)他們的事件。緊接著,我們要為Outlook's
Tools->Options加入我們自己編寫的屬性表。接著我們將看一些相關(guān)的注冊鍵和ATL向?qū)У囊恍┓浅S杏玫奶卣鞑⑶覍W(xué)習(xí)有效地使用他們。
雖然我們寫的是一個(gè)Outlook2000 COM
addin的程序。但是Office2000的應(yīng)用程序,比如Word,Access等等,他們的Com
AddIn的寫法是非常相似的。除了注冊鍵和接口,其余的部分基本上是一樣的。
我假設(shè)你是一個(gè)VC++
Com的開發(fā)人員,并且也有一些基于ATL的組件開發(fā)和OLE/自動(dòng)化方面的經(jīng)驗(yàn),盡管這也不是必須的。創(chuàng)建和測試這個(gè)AddIn程序,你必須安裝Office2000,至少有outlook2000。程序代碼使用VC++
6.0
sp3+/ATL3.0創(chuàng)建,使用的操作系統(tǒng)是:安裝了Office2000的Windows2000。
開始:
Office AddIn 是一個(gè)可以動(dòng)態(tài)擴(kuò)充和增強(qiáng)的Com
自動(dòng)化組件,可以控制任何的Office應(yīng)用程序。微軟的Office2000和以后的版本都支持創(chuàng)建Add_Ins的一個(gè)新的、統(tǒng)一的應(yīng)用設(shè)計(jì)架構(gòu)。AddIn通常都被置于一個(gè)ActiveX動(dòng)態(tài)庫中(進(jìn)程內(nèi)服務(wù)器),并且能被用戶動(dòng)態(tài)的從主程序中引導(dǎo)和卸載。
Office AddIn 必須實(shí)現(xiàn) _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,而且必須實(shí)現(xiàn)他的五個(gè)方法。
當(dāng)AddIn被引導(dǎo)和卸載的時(shí)候,OnConnection 和 OnDisconnection,
就像他們的名字顯示的一樣。AddIn程序可以被引導(dǎo),也可以在應(yīng)用程序使用過程中被用戶啟動(dòng)或者通過自動(dòng)化和enumerator
ext_Connect 指示連接到那些模塊。當(dāng)一組Com
AddIn組件被改變,那么OnAddinsUpdate被調(diào)用。OnStartupComplete
只有在應(yīng)用程序使用過程中啟動(dòng)Com
AddIn組件時(shí)才被調(diào)用,如果AddIn在主應(yīng)用程序被關(guān)掉的時(shí)候斷開與主應(yīng)用程序的連接,那么OnBeginShutdown
被調(diào)用。
注冊AddIn組件:
使用主應(yīng)用程序注冊AddIn組件,我們需要在注冊表目錄:
HKEY_CURRENT_USER\Software\Microsoft\Office\<TheOfficeApp>\Addins\<ProgID>
下創(chuàng)建兩個(gè)子鍵,這里ProgID指的是Addin
Com對象的唯一標(biāo)識(shí)符。別的入口通過AddIn提供的關(guān)于他自己的信息和制定的引導(dǎo)選項(xiàng)給主應(yīng)用程的是:
FriendlyName – 字符串 –
主應(yīng)用程序顯示的這個(gè)AddIn程序的名字。
Description – 字符串 – 對AddIn的描述.
LoadBehavior - DWORD 值.
–一個(gè)決定AddIn怎樣被主應(yīng)用程序引導(dǎo)的值的組合。 設(shè)置成 0x03
表示主應(yīng)用程序啟動(dòng)時(shí)引導(dǎo),設(shè)置成0x08表示由用戶來激活。
CommandLineSafe - DWORD 值. 0x01(TRUE) 或者 0x00(FALSE).
對于所有值和可選項(xiàng)的完整描述,請參考MSDN。
創(chuàng)建一個(gè)小的Com AddIn:
現(xiàn)在我們了解了足夠的知識(shí),應(yīng)該朝前一步編寫一個(gè)小的Outlook2K COM
addin。創(chuàng)建一個(gè)新的ATL COM Appwizard
工程,命名為OutlookAddin。記住如果你把他命名成別的,他可能會(huì)不能運(yùn)行(開個(gè)玩笑)。
在向?qū)У牡谝粋€(gè)對話框中接收默認(rèn)的服務(wù)器類型Dynamic Link
Library(DLL),檢查Allow merging of proxy-stub
code,選擇這個(gè)可選項(xiàng),點(diǎn)擊完成。接著點(diǎn)擊OK,產(chǎn)生工程文件。
下一步,點(diǎn)擊Insert->New ATL
Object菜單項(xiàng),通過從Category中選擇Objects從Objects列表中選擇Simple
Object插入一個(gè)ATL simple
object到工程中。點(diǎn)擊Next,輸入”AddIn”作為ShortName,在屬性表里選上Support
ISupportErrorInfo。接受剩下的默認(rèn)選項(xiàng),然后點(diǎn)擊OK。
到現(xiàn)在為止,向?qū)б呀?jīng)給我們了一個(gè)置于動(dòng)態(tài)鏈接庫中的自動(dòng)化兼容的、DispInterface-savvy的進(jìn)程內(nèi)的Com對象。默認(rèn)的情況下,一個(gè)加到Com對象上的指定注冊值的注冊腳本文件被提交給我們。Build這個(gè)工程,看看一切是否運(yùn)行良好。
如果你想我一樣雄心勃勃,起碼在繼續(xù)往下進(jìn)行前還應(yīng)該編譯你工程中的.idl文件。現(xiàn)在就去做吧。
接下來我們?yōu)锳ddIn寫一些特定的代碼去實(shí)現(xiàn)IDTExtensibility2
接口。在類視圖里,我們在CAddIn類上右鍵點(diǎn)擊,選擇Implement
Interface,這將帶出ATL Implement Interface 向?qū)А|c(diǎn)擊Add
Typelib,在Browse Typelibraries對話框里向下滾動(dòng),選上Microsoft
Add-in
Designer(1.0),點(diǎn)擊OK。在AddinDesignerObjects列表中選擇_IDTExtensibility2接口點(diǎn)擊OK。
向?qū)镮DTExtensibility2接口的五個(gè)方法中每一個(gè)生成默認(rèn)的實(shí)現(xiàn),將他們加到CAddIn類中,并且更新
COM_INTERFACE_MAP()宏。當(dāng)然在加有些有用的代碼之前每個(gè)方法都只會(huì)返回E_NOTIMPL。現(xiàn)在,為ComAddIn進(jìn)行必要的注
冊,我們的Com
AddIn已經(jīng)就緒了。
使用主應(yīng)用程序注冊我們的Addin組件。如果是outlook2000,打開工程的AddIn.rgs注冊腳本文件。把下面的代碼加到文件的結(jié)尾。
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'
}
}
}
}
}
}
}
既然我們希望在程序啟動(dòng)的時(shí)候AddIn被引導(dǎo),那么LoadBehavior設(shè)置為3。現(xiàn)在Build這個(gè)工程。如果一切順利,那么將會(huì)創(chuàng)建成功并且注
冊了這個(gè)AddIn。為了測試這個(gè)AddIn,我們要運(yùn)行這個(gè)工程并輸入完整的outlook.exe的完整的路徑(\Program
Files\Microsoft
Office\Office\Outlook.exe),或者在注冊了這個(gè)DLL之后從VC++IDE環(huán)境外運(yùn)行outlook。如果你的AddIn被成
功的注冊了,那么在outlook里,點(diǎn)擊Tools->Options,在Other頁點(diǎn)擊Advanced
Options->COM
Addins,我們的AddIn應(yīng)該已經(jīng)出現(xiàn)在可獲得的AddIns的列表中。字符串是我們在腳本中為'FriendlyName'指定的值。
AddIn可以被編寫來執(zhí)行各種不同的任務(wù)。典型的,包括為outlook添加一些界面元素,比如工具條和菜單項(xiàng),而且用戶可以控制AddIn。通過點(diǎn)擊這個(gè)工具條按鈕和菜單項(xiàng),用戶可以實(shí)現(xiàn)AddIn的功能。接下來我們將制作這樣一個(gè)工具條和附加的菜單項(xiàng)。
命令與征服:
在Office應(yīng)用程序中,菜單和工具條被組合在一個(gè)名叫“CommandBars
“的完全可編程的集合中。CommandBars通常是可共享可編程的對象,并且作為所有的office應(yīng)用程序的一部分被暴露。CommandBars
代表一個(gè)同一的機(jī)制,通過他可以將單個(gè)的工具條和菜單項(xiàng)加到相應(yīng)的應(yīng)用程序里。每一個(gè)CommandBars由幾個(gè)獨(dú)立的CommandBar對象組成。
每一個(gè)CommandBar又由CommandBarControl對象集合組成,這個(gè)集合被叫做CommandBarControls。
CommandBarControls代表了一個(gè)復(fù)雜的對象和組成它的子對象層次。一個(gè)CommandBarControl能被包含在一個(gè)
CommandBar中,并且通過控件的CommandBar屬性訪問。最后每一個(gè)在控件的CommandBarControls集合中的
CommandBarControl即可能是CommandBarComboBox、CommandBarButton(工具條按鈕)也可能是
CommandBarPopup(彈出式菜單)。我很希望我能畫出一個(gè)代表這個(gè)對象層次的圖例,但是我很不擅長這個(gè)(我很誠實(shí)!)。我保證在MSDN中一
定有關(guān)于MS
Office CommandBars描述的圖例。
在我們的AddIn里,我想加入以下的界面元素:
? 在一個(gè)新的工具條里加入兩個(gè)位圖按鈕。
? 在“Tool“菜單里添加一個(gè)新的帶位圖的彈出式菜單項(xiàng)。
首先,我們應(yīng)該將office和outlook的類型庫導(dǎo)入到我們的工程中。我們打開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;
注意:你應(yīng)該改變這些路徑,是他們匹配你安裝的office的路徑。
好了,現(xiàn)在讓我們來看看代碼。首先式ToolBand和ToolBar Button。
在outlook模塊里,Application
對象位于代表整個(gè)應(yīng)用程序的對象層次的最頂層。通過他的ActiveExplorer
方法我們可以得到代表當(dāng)前窗口的Explorer對象。下來我們使用GetCommandBars方法得到CommandBars對象(他是
outlook工具條和菜單項(xiàng)的集合)。我們使用CommandBars集合的Add方法加上相應(yīng)的參數(shù)就可以添加一個(gè)新的工具條。如果想向工具條中加入
按鈕只需要得到工具條的CommandBarControls集合,接著調(diào)用他的Add方法。最后我們?yōu)槟切?yīng)于按鈕的
CommandBarButton對象(我們可以用它來設(shè)置按鈕的風(fēng)格和別的屬性,比如標(biāo)題、提示和文本等等)。
代碼片斷如下:
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菜單添加菜單項(xiàng),我們照以下方法做。CommandBars的ActiveMenuBar屬性返回一個(gè)表
示在Application容器中活動(dòng)的菜單。我們通過GetControls方法找到活動(dòng)的菜單控件集合。我們想要加入一個(gè)彈出式的菜單項(xiàng)到
outlook的Tools菜單(第6個(gè)菜單項(xiàng)),我們從Activemenubars控件集合中可以找到第6個(gè)菜單項(xiàng),直接調(diào)用Add方法創(chuàng)建一個(gè)新的
菜單項(xiàng)并且將他連接到Tools菜單。這里沒有什么新東西。
相應(yīng)的代碼片斷如下所示:
//......
//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;
}
點(diǎn)擊F5,如果一切都沒問題,那么工程將成功建立,并且你將第一次看見你的AddIn程序的運(yùn)行。現(xiàn)在我們運(yùn)行outlook來測試我們的AddIn。在'Executable
for
Debug'對話框,設(shè)置outlook可執(zhí)行程序的當(dāng)前路徑,現(xiàn)在我們準(zhǔn)備測試。在outlook中點(diǎn)擊Tools->Option,點(diǎn)擊Other頁面,點(diǎn)擊Advanced
Options。在Advanced Option對話框中,點(diǎn)擊Com AddIns
按鈕。接著從可獲得的AddIns列表中選擇我們的AddIn并點(diǎn)擊OK。當(dāng)我們的AddIn被引導(dǎo),一個(gè)停靠工具條將被創(chuàng)建,你也可以看到你加入到Tools菜單的菜單項(xiàng)。
他們就在那里!一個(gè)有你寫的AddIn的outlook,一個(gè)帶有很酷的工具條和新的菜單項(xiàng)的擴(kuò)展的outlook!感謝ATL!你的小于50Kb的AddIn同樣提供了輕量級(jí)的有意義的Com服務(wù)。享受這一刻吧!
單單放置兩個(gè)工具條按鈕和一個(gè)菜單項(xiàng)并沒有什么用處,除非我們寫命令處理代碼和響應(yīng)他們的事件。現(xiàn)在我們回到正題。當(dāng)然在這里,點(diǎn)擊不同的按鈕和菜單項(xiàng),我們緊緊彈出簡單的對話框。這就是你添加AddIn功能的地方。從CRM
工具、自動(dòng)聯(lián)系管理、郵件通知、郵件過濾到高級(jí)的文檔管理到各種各樣的應(yīng)用,Com
AddIns可以執(zhí)行各種各樣的任務(wù)的驗(yàn)證。
CommandBarButton控件暴露了一個(gè)點(diǎn)擊事件(當(dāng)用戶點(diǎn)擊一個(gè)Command Bar
按鈕時(shí)觸發(fā))。當(dāng)用戶點(diǎn)擊工具條按鈕或者是點(diǎn)擊菜單項(xiàng)的時(shí)候我們將使用這個(gè)事件去運(yùn)行代碼。對于這些,我們的Com
AddIn對象不得不處理_CommandBarButtonEvents事件。點(diǎn)擊事件被聲明如下:
//...
//....Office objects typelibrary
//....
[id(0x00000001), helpcontext(0x00038271)]
void Click(
[in] CommandBarButton* Ctrl,
[in, out] VARIANT_BOOL* CancelDefault);
//....
//...
我們不得不做所有我們能做的事情去實(shí)現(xiàn)那些將被事件源通過規(guī)范的連接點(diǎn)協(xié)議調(diào)用的接收器接口(無論什么時(shí)候一個(gè)工具條按鈕或菜單項(xiàng)被點(diǎn)擊)。通過回調(diào)函數(shù)我們可以得到一個(gè)源CommandBarButton
對象的指針和一個(gè)用來接受和取消默認(rèn)操作的布爾值。就像實(shí)現(xiàn)一個(gè)dispatch接收器接口一樣,那也不是什么新東西,作為一個(gè)ATL程序員你可能要花一段時(shí)間去做這些。
但是對于那些非初始化的,ATL為ATLCom對象提供兩個(gè)模板類IDispEventImpl<>
和 IDispEventSimpleImpl<>
,這為IDispatch接口提供了實(shí)現(xiàn)。我更喜歡用輕量級(jí)的IDispEventSimpleImpl,因?yàn)樗恍枰硗獾念愋蛶煨畔ⅰD愕念惥o緊源于
IDispEventSimpleImpl<>。建立你的接收器映射,通過_ATL_SINK_INFO結(jié)構(gòu)體設(shè)置你的回調(diào)參數(shù),最后調(diào)用
DispEventAdvise
和
DispEventUnadvise從源接口連接和斷開。對于我們的工具條按鈕和菜單項(xiàng),如果我們要寫一個(gè)單一的回調(diào)函數(shù)來處理所有的事件,那么,一旦我
們有一個(gè)指向觸發(fā)事件的CommandBarButton的指針,我們可以使用GetCaption去得到這個(gè)按鈕的文本,在這個(gè)基礎(chǔ)上,我們可以執(zhí)行一
些選擇性的動(dòng)作。但是對于這個(gè)例子,我們?yōu)槊恳粋€(gè)事件編寫一個(gè)回調(diào)函數(shù)。
下面是編寫的步驟:
使你的類繼承于IDispSimpleEventImpl-第一個(gè)參數(shù)是封裝在ActiveX控件中的子窗口的ID。但是對于我們來說,它可以是任何預(yù)先定義的唯一標(biāo)識(shí)事件源的整數(shù)(在這里指的是第一個(gè)工具條按鈕)。
class ATL_NO_VTABLE CAddin :
public CComObjectRootEx < CComSingleThreadModel>,
.....
.....
public
IDispEventSimpleImpl<1,CAddin,&__uuidof(Office::_CommandBarButtonEvents>
建立回調(diào)函數(shù)-第一個(gè)我們定義的,如下所示:
void __stdcall OnClickButton(IDispatch *
/*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL *
CancelDefault);
接下來我們使用_ATL_SINK_INFO結(jié)構(gòu)去描述回調(diào)參數(shù)。打開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是非常基礎(chǔ)的,就像下面的:
? 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和處理他的成員函數(shù)。
? BEGIN_SINK_MAP(CAddin)
? SINK_ENTRY_INFO(1,
__uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01,
? OnClickButton, &OnClickButtonInfo)
? END_SINK_MAP()
現(xiàn)在每一件事情都到位了,我們不得不使用DispEventAdvise() and
DispEventUnadvise()連接和斷開事件源.我們的CAddIn類的OnConnection()
和OnDisconnection()僅僅是替代了這些。對于DispEventAdvise() and
DispEventUnadvise()的參數(shù)分別是事件源上的任何的接口和任何被期望的事件源上的接口。
//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);
為我們的命令按鈕和菜單項(xiàng)實(shí)現(xiàn)Dispatch
接收器是很相似的,寫處理代碼并且連接和斷開他們就像上面的描述。如果每一步都進(jìn)行的暢通無阻,在你Rebuild你的程序并且運(yùn)行它。無論什么時(shí)候,按鈕和菜單項(xiàng)被點(diǎn)擊,你的回調(diào)函數(shù)將被執(zhí)行。
添加屬性頁:
在這篇文章里我們最后要學(xué)會(huì)去做的是添加我們自己的“Option“屬性頁到outlook的Tools->Option的屬性表中。
下來我們要加一個(gè)頁到outlook的option菜單里作為我們我們的AddIn的一部分。我們將象ActiveX控件一樣實(shí)現(xiàn)實(shí)現(xiàn)屬性頁。當(dāng)用戶點(diǎn)擊
Tools->Option菜單項(xiàng),應(yīng)用程序?qū)ο蟀l(fā)出一個(gè)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事件傳遞給我們我們一個(gè)PropertyPages
Dispatch接口,他的Add方法用來添加頁。Add方法的參數(shù)是我們的控件的ProgID和新的頁的標(biāo)題文本。相似的,我們調(diào)用Remove()方法和要?jiǎng)h除頁的索引來刪除頁。
現(xiàn)在我們來加一個(gè)ActiveX復(fù)合控件。我們點(diǎn)擊Insert->new ATL
Object.從Category中選擇Controls,從Object列表中選擇Lite Composite
Control,點(diǎn)擊OK。在ShortName中輸入PropPage,在屬性頁面選上Support
ISupportErrorInfo選項(xiàng)。點(diǎn)擊Ok,接受所有的默認(rèn)選項(xiàng)。
現(xiàn)在我們要來實(shí)現(xiàn)PropertyPage接口。在類視圖里右鍵點(diǎn)擊CPropPage,選擇Implement
Interface,點(diǎn)擊Add TypeLib按鈕。選中Microsoft Outlook 9.0 Object
Library 點(diǎn)擊OK。從接口列表中選擇PropertyPage點(diǎn)擊OK。
向?qū)ё詣?dòng)為PropertyPage接口添加三個(gè)方法:Apply()、Get_Dirty()、GetPageInfo()
。現(xiàn)在做下面的修改,在Com Map中把這一行:
COM_INTERFACE_ENTRY(IDispatch)
改成:
COM_INTERFACE_ENTRY2(IDispatch,IPropPage)
以排除不明確的地方。
接下來實(shí)現(xiàn)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語句。類型庫已經(jīng)在stdAfx.h中導(dǎo)入了,因此這里沒有必要再導(dǎo)入。
下來我們要連接和斷開ApplicationEvents接口,并為他寫回調(diào)函數(shù)。你已經(jīng)知道該做什么了。我們再次使用
IDispEventSimpleImpl<>為ApplicationEvents建立Dispatch接收器,更新接收器映射,為
OptionsAddPage事件寫回調(diào)函數(shù)。因?yàn)槲覀兌啻问褂昧薎DispEventSimpleImpl<>,
我們?yōu)槊恳粋€(gè)接口事件使用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里,調(diào)用DispEventAdvise 和
DispEventUnadvise連接和斷開ApplicationEvents。現(xiàn)在一切就緒,我們ReBuild工程。下來點(diǎn)擊F5,點(diǎn)擊
Outlook的Tools->Options菜單。你應(yīng)該看見了我們新加的頁。但是當(dāng)我們點(diǎn)擊這個(gè)新的頁,一個(gè)對話框?qū)⒊霈F(xiàn)告訴我們屬性頁不能被
顯示。發(fā)生了什么?難道我們的辛苦勞動(dòng)白費(fèi)了?
發(fā)生這個(gè)情況的原因是:盡管我們的屬性頁創(chuàng)建了,但是outlook并沒有得到關(guān)于這個(gè)頁的鍵盤行為的任何信息。IOleControl的
GetControlInfo方法的ATL的默認(rèn)實(shí)現(xiàn)返回E_NOTIMPL,因此包容器無法為這個(gè)屬性頁和包容器處理擊鍵事件。因此我們的頁不能被顯
示。修改這個(gè)問題,只需重載GetControlInfo()方法,讓他返回S_OK。
在.PropPage.h里添加如下聲明:
STDMETHOD(GetControlInfo)(LPCONTROLINFO lpCI);
我們在PropPage.cpp文件里重載GetControlInfo()方法,僅僅將返回值改為S_OK,代碼如下:
STDMETHODIMP CPropPage::GetControlInfo(LPCONTROLINFO lpCI)
{
return S_OK;
}
就是這些了。現(xiàn)在再次Build工程,點(diǎn)擊outlook的tools->Option,激活我們的頁,現(xiàn)在我們的屬性頁應(yīng)該正確無誤的顯示了。
我們的學(xué)習(xí)要結(jié)束了。我們能在office里我們能做的事情無窮無盡。因?yàn)樵谝粋€(gè)AddIn里你可以獲得父應(yīng)用程序的內(nèi)部對象模塊,你能做所有主應(yīng)用程序能做的事,或者更多。另外你也能使用別的接口比如MS
Assistant(并不直接關(guān)聯(lián)到應(yīng)用程序)。沒有做不到的只有想不到的。