原文: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(并不直接关联到应用程序)。没有做不到的只有想不到的。