一个VC++ Outlook COM加载项,用于发布自定义表单






4.27/5 (7投票s)
2005年4月6日
4分钟阅读

189516

1491
本文将介绍如何以编程方式从自定义 Outlook 表单发布和检索数据。
引言
当您为 Microsoft Outlook 创建 Outlook COM/ATL 插件时,例如工具栏或新的弹出菜单,有时您会以编程方式发布自定义表单并检索其数据。我在这里假设您知道如何创建 Outlook 表单模板(oft 文件)以及如何为 MS Outlook 创建 COM/ATL 插件。如果您从未创建过自定义表单或 Outlook COM 插件,我建议您在继续阅读本文之前,先阅读 背景文章。在本示例代码中,我们将加载一个 Outlook 工具栏,其中包含一个显示选定联系人项的按钮和一个属性页,该属性页可以在默认 Outlook 联系人表单和我们的自定义联系人表单之间切换。按钮会检索选定联系人的用户数据并显示其相对值。
背景
您必须先阅读一些关于 Outlook COM 插件、Outlook 对象模型和 Outlook 表单自定义的文章。您可以在此处阅读其中一些文章:
- 使用 Microsoft Outlook 2002 开发自定义表单.
- 使用 Visual Studio .NET 编程 Microsoft Outlook.
- Microsoft Outlook 自定义表单.
- 使用 VC++/ATL 构建 Office2K COM 插件.
- 向 Microsoft Outlook 添加新工具栏和按钮 - 教程 - 1.
- 使用 VC++/ATL 开发 Office 2003 COM 插件.
使用代码
首先,我们必须创建一个 Outlook 表单模板。创建一个新的联系人表单,并进入自定义联系人表单设计模式。插入一个名为“昵称”的新自定义字段,将选项卡重命名为“(p.2)”为“自定义”,然后将其保存到一个名为“Custom.oft”的 Outlook 表单模板(oft)文件中。您应该创建像这样的自定义表单:
我们现在假设您已将此自定义表单保存为“C:\Custom.oft”,并且尚未发布它。
使用 .NET,您可以通过选择新的 ATL COM 从向导开始创建 COM 插件,然后选择 DLL 项目。现在,在通过向导添加 ATL 对象后,转到实现接口 -> 添加类型库 -> Microsoft Add-in Designer -> _IDTExtensibility2。如果您有 Microsoft Outlook 2003,请将以下内容添加到您的 StdAfx.h 文件中。
// Microsoft Visual C++ will insert additional // declarations immediately before the previous line. //change these to your dll paths #import "C:\Programmi\file comuni\Microsoft Shared\Office10\mso.dll" rename_namespace("Office"), named_guids using namespace Office; #import "C:\Programmi\Microsoft Office\Office11\MSOUTL.olb" rename_namespace("Outlook"), named_guids, raw_interfaces_only using namespace Outlook;
添加到您的 StdAfx.h。如果您有 Outlook XP 或 2000,请参阅 背景链接 以获取更多详细信息。
创建您的 ATL/COM 插件:在工具栏中添加一个 _CommandBarButton
,并在映射表中添加其 _CommandBarButtonEvents
。
class ATL_NO_VTABLE OutlookAddin : public CComObjectRootEx<CComSingleThreadModel>, //.... public IDispEventSimpleImpl<1,OutlookAddin, &__uuidof(Office::_CommandBarButtonEvents)>, public IDispEventSimpleImpl<2,OutlookAddin, &__uuidof(Outlook::ApplicationEvents)> { public: typedef IDispEventSimpleImpl</*nID =*/ 1,OutlookAddin, &__uuidof(Office::_CommandBarButtonEvents)> CommandButton2Events; typedef IDispEventSimpleImpl</*nID =*/ 2,OutlookAddin, &__uuidof(Outlook::ApplicationEvents)> AppEvents; public: //... Wizard add more code here protected: BEGIN_COM_MAP(OutlookAddin) //... END_COM_MAP() BEGIN_SINK_MAP(OutlookAddin) SINK_ENTRY_INFO(1, __uuidof(Office::_CommandBarButtonEvents), /*dispid*/ 0x01, OnClickButton, &OnClickButtonInfo) SINK_ENTRY_INFO(2,__uuidof(Outlook::ApplicationEvents), /*dispid*/0xf005,OnOptionsAddPages,&OnOptionsAddPagesInfo) END_SINK_MAP()
按如下方式更改 OnConnection()
和 OnDisconnection()
方法:
STDMETHOD(OnConnection)(IDispatch * Application, ext_ConnectMode ConnectMode, IDispatch * AddInInst, SAFEARRAY * * custom){ HRESULT hr; COleVariant covOptional((long) DISP_E_PARAMNOTFOUND, VT_ERROR); CComQIPtr <Outlook::_Application> spApp(Application); ATLASSERT(spApp); m_spApp = spApp FormTemplate *pFM=FormTemplate::GetInstance(m_spApp); spApp->ActiveExplorer(&m_spExplorer); // get the CommandBars interface that represents Outlook's // toolbars & menu items hr = m_spExplorer->get_CommandBars(&m_spCmdBars); if(FAILED(hr)) return hr; ATLASSERT(m_spCmdBars); CComVariant vName("Outlook Toolbar"); CComPtr <Office::CommandBar> spNewCmdBar; CComVariant vPos(1); CComVariant vTemp(VARIANT_TRUE); CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR); spNewCmdBar = m_spCmdBars->Add(vName, vPos, vEmpty, vTemp); CComPtr < Office::CommandBarControls> spBarControls; spBarControls = spNewCmdBar->GetControls(); ATLASSERT(spBarControls); CComVariant vToolBarType(1); CComVariant vShow(VARIANT_TRUE); m_spButton = AddIconButton("View Custom Form","Go Custom Contact", IDB_BITMAP2,true,VARIANT_TRUE,spBarControls); //IDB_BITMAP2 is a bitmap custom id CommandButton2Events::DispEventAdvise((IDispatch*)m_spButton); AppEvents::DispEventAdvise((IDispatch*)m_spApp); //show the toolband spNewCmdBar->PutVisible(VARIANT_TRUE); return S_OK; } STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom){ CommandButton2Events::DispEventUnadvise((IDispatch*)m_spButton); AppEvents::DispEventUnadvise((IDispatch*)m_spApp); return S_OK; }}
AddIconButton()
方法将一个通用的自定义图标按钮添加到您的工具栏。
CComPtr < Office::_CommandBarButton> OutlookAddin::AddIconButton(const std::string& caption, const std::string& tooltip, const int& imageId, bool transparence, const VARIANT_BOOL& vBeginGroup, const CComPtr < Office::CommandBarControls>& pBarControls) { CComVariant vToolBarType(1); CComVariant vShow(VARIANT_TRUE); CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR); CComPtr < Office::CommandBarControl> spNewBar; // add button spNewBar = pBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow); ATLASSERT(spNewBar); spNewBar->put_BeginGroup(vBeginGroup); // 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> spButton(spNewBar); ATLASSERT(spButton); UINT colorMap; if (transparence) colorMap = LR_LOADMAP3DCOLORS|LR_LOADTRANSPARENT; else colorMap = LR_LOADMAP3DCOLORS; HBITMAP hBmp =(HBITMAP)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(imageId),IMAGE_BITMAP,0,0,colorMap); // put bitmap into Clipboard ::OpenClipboard(NULL); ::EmptyClipboard(); ::SetClipboardData(CF_BITMAP, (HANDLE)hBmp); ::CloseClipboard(); ::DeleteObject(hBmp); // set style before setting bitmap spButton->PutStyle(Office::msoButtonIconAndCaption); HRESULT hr = spButton->PasteFace(); if (FAILED(hr)) return hr; std::string tag = "Tag "+caption; spButton->PutVisible(VARIANT_TRUE); if (caption.length() > 1) spButton->PutCaption(caption.c_str()); spButton->PutEnabled(VARIANT_TRUE); spButton->PutTooltipText(tooltip.c_str()); spButton->PutTag(tag.c_str()); return spButton; }
现在添加 FormTemplate
和 PropPage
类。第一个类管理自定义联系人表单模板,第二个类在 Outlook 中可视化一个新的选项页。
class ATL_NO_VTABLE PropPage : //PropPage.h public CComObjectRootEx<CComSingleThreadModel>, public IDispatchImpl<IPropPage, &IID_IPropPage, &LIBID_OUTLOOKADDINLib>, public IPersistStreamInitImpl<PropPage>, public IOleControlImpl<PropPage>, public IOleObjectImpl<PropPage>, public IOleInPlaceActiveObjectImpl<PropPage>, public IViewObjectExImpl<PropPage>, public IOleInPlaceObjectWindowlessImpl<PropPage>, public ISupportErrorInfo, public CComCoClass<PropPage, &CLSID_PropPage>, public CComCompositeControl<PropPage>, public IDispatchImpl < Outlook::PropertyPage, &__uuidof(Outlook::PropertyPage),&LIBID_OUTLOOKADDINLib> { public: PropPage() { m_bWindowOnly = TRUE; CalcExtent(m_sizeExtent); m_pForm = FormTemplate::GetInstance(); } //[snip] interface methods added by Wizard protected: LRESULT OnBnClickedButtonLoad(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/); LRESULT OnBnClickedRadioCustom(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/); LRESULT OnBnClickedRadioDefault(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/); private: FormTemplate* m_pForm; bool m_bCustomForm; }; OBJECT_ENTRY_AUTO(__uuidof(PropPage), PropPage)
FormTemplate
类是一个单例类,它实现了一些加载/卸载自定义表单的方法。
class FormTemplate //FormTemplate.h { public: static FormTemplate* GetInstance(CComPtr<Outlook::_Application> spApp = 0); //FormTemplate is a Singleton Class protected: FormTemplate(CComPtr<Outlook::_Application> spApp):m_spApp(spApp) { } public: int LoadCustomForm(void); int LoadDefaultForm(void); protected: ~FormTemplate(void); std::string ToStdString(BSTR bsData) const; private: CComQIPtr <Outlook::_Application> m_spApp; static FormTemplate* _instance; }; #endif
现在您已准备好以编程方式发布您的新联系人表单。我这里假设您将在联系人文件夹本身中发布。
此外,如果您想将所有现有项目转换为新的自定义联系人表单,您还需要更改现有项目的 Message
类。这样,您就可以告诉 Outlook 在打开联系人项时应使用哪个表单。
int FormTemplate::LoadCustomForm(void) //load, publicize and convert to your custom contact form { HRESULT hr; CComQIPtr <Outlook::_ContactItem> CustomContactItem; CComQIPtr <Outlook::_ContactItem> NewContactItem; CComPtr <Outlook::FormDescription> CustomFormDescription; CComVariant myFolder(DISP_E_PARAMNOTFOUND, VT_ERROR); _bstr_t bstrName_T (_T("MAPI")); BSTR bstrName; bstrName = bstrName_T.copy(); CComPtr <Outlook::_NameSpace> olNs; m_spApp->GetNamespace(bstrName,&olNs); CComQIPtr <Outlook::MAPIFolder> oContacts; Outlook::OlDefaultFolders enumODF = olFolderContacts; hr = olNs->GetDefaultFolder(enumODF,&oContacts); //change this to your Custom.oft path bstrName_T = "C:\\Custom.oft"; bstrName = bstrName_T.copy(); hr = m_spApp->CreateItemFromTemplate(bstrName, myFolder,(IDispatch**) &CustomContactItem); if(FAILED(hr)) { string msg=(string)"Impossible to load "+ToStdString(bstrName); MessageBox(NULL,msg.c_str(),"Outlook Addin",MB_ICONERROR); return -1; } Outlook::OlInspectorClose oic; CComVariant varContacts (oContacts); //**** publish form in Contacts folder **** bstrName_T = _T("Custom"); bstrName = bstrName_T.copy(); Outlook::OlFormRegistry ofr = olFolderRegistry; hr = CustomContactItem->get_FormDescription(&CustomFormDescription); if (FAILED(hr)) { MessageBox(NULL,"Nothing form description", "Outlook Addin",MB_ICONERROR); return -1; } CustomFormDescription->put_Name(bstrName); hr = CustomFormDescription->PublishForm(ofr, varContacts); if (FAILED(hr)) { MessageBox(NULL,"Publish failed", "Outlook Addin",MB_ICONERROR); return -1; } //***** Convert existing items ***** CComQIPtr <Outlook::_Items> spItems; oContacts->get_Items(&spItems); BSTR oldMsgClass,newMsgClass; _bstr_t oldMsgClass_T (_T("IPM.Contact")); oldMsgClass = oldMsgClass_T.copy(); _bstr_t newMsgClass_T (_T("IPM.Contact.Custom")); newMsgClass = newMsgClass_T.copy(); long ItemCount; spItems->get_Count(&ItemCount); int changes =0; oic = olSave; for (int n = 1; n<=ItemCount;++n) { CComVariant nItem (n); IDispatch* itm; hr = spItems->Item(nItem,&itm); if (FAILED(hr)) { MessageBox(NULL,"Conversion error", "Outlook Addin",MB_ICONERROR); return -1; } CComQIPtr <Outlook::_ContactItem> spItem; hr = itm->QueryInterface(&spItem); if (FAILED(hr)) { continue; //IPM.DistList } BSTR curMsgClass; spItem->get_MessageClass(&curMsgClass); if (FAILED(hr)) { MessageBox(NULL,"Conversion error", "Outlook Addin",MB_ICONERROR); return -1; } string curMsgClassStr = ToStdString(curMsgClass); string oldMsgClassStr = ToStdString(oldMsgClass); if (curMsgClassStr.compare(oldMsgClassStr) ==0) { spItem->put_MessageClass(newMsgClass); spItem->Save(); spItem->Copy((IDispatch**)&NewContactItem); hr = spItem->Delete(); if (FAILED(hr)) { MessageBox(NULL,"Conversion error", "Outlook Addin",MB_ICONERROR); return -1; } else NewContactItem->Close(oic); changes++; } } oic = olDiscard; CustomContactItem->Close(oic); MessageBox(NULL,"Custom form loaded correctly", "Outlook Addin",MB_ICONINFORMATION); return 0;;}
配套方法 LoadDefaultForm()
会将您所有现有的联系人项重新转换为默认联系人表单。
int FormTemplate::LoadDefaultForm(void) //reload default form { _bstr_t bstrName_T (_T("MAPI")); BSTR bstrName; bstrName = bstrName_T.copy(); CComPtr <Outlook::_NameSpace> olNs; m_spApp->GetNamespace(bstrName,&olNs); CComQIPtr <Outlook::MAPIFolder> oContacts; Outlook::OlDefaultFolders enumODF = olFolderContacts; olNs->GetDefaultFolder(enumODF,&oContacts); BSTR defaultMsgClass; _bstr_t defaultMsgClass_T (_T("IPM.Contact")); defaultMsgClass = defaultMsgClass_T.copy(); CComQIPtr <Outlook::_Items> spItems; oContacts->get_Items(&spItems); long ItemCount; spItems->get_Count(&ItemCount); IDispatch* pItem,*pItemCopy; CComQIPtr<Outlook::_ContactItem> pContact; spItems->GetFirst(&pItem); for (int n = 1; n<=ItemCount;n++) { HRESULT hr=pItem->QueryInterface(&pContact); if (FAILED(hr)) { HRESULT nres = spItems->GetNext(&pItem); if (FAILED (nres)) break; continue; //IPM.DistList } pContact->put_MessageClass(defaultMsgClass); pContact->Save(); pContact->Copy(&pItemCopy); pContact->Delete(); OlInspectorClose oic = olSave; pContact->Close(oic); HRESULT nres = spItems->GetNext(&pItem); if (FAILED (nres)) break; } MessageBox(NULL,"Default form loaded correctly", "Outlook Addin",MB_ICONINFORMATION); return 0; }
现在,向您的项目添加一个属性页,其中包含两个单选按钮和一个按钮:这允许您在默认表单和自定义表单之间切换。
为此,您应该在项目中添加一个对话框(IDD_PROPPAGE
),大致如下:
有关如何为 Outlook 创建属性页的更多详细信息,请参阅 背景文章,或参阅本文中链接的演示项目。
PropPage
实现捕获按钮事件。
// PropPage.cpp : Implementation of PropPage #include "stdafx.h" #include "PropPage.h" #include "Addin.h" #include ".\proppage.h" STDMETHODIMP PropPage::GetControlInfo(LPCONTROLINFO lpCI){ m_bCustomForm=true; CheckRadioButton(IDC_RADIO1,IDC_RADIO2,IDC_RADIO1); return S_OK; } LRESULT PropPage::OnBnClickedButtonLoad(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/){ if (m_bCustomForm) m_pForm->LoadCustomForm(); else m_pForm->LoadDefaultForm(); return 0; } LRESULT PropPage::OnBnClickedRadioCustom(WORD /*wNotifyCode*/, WORD /*wID*/, HWND hWndCtl, BOOL& /*bHandled*/){ m_bCustomForm = true; return 0; } LRESULT PropPage::OnBnClickedRadioDefault(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/){ m_bCustomForm = false; return 0; }
在 OutlookAddin
类中,您必须添加属性页。
void __stdcall OutlookAddin::OnOptionsAddPages(IDispatch* Ctrl) //Add PropertyPage via his GUID { CComQIPtr<Outlook::PropertyPages> spPages(Ctrl); ATLASSERT(spPages); CComVariant varProgId(OLESTR("OutlookAddin.PropPage")); CComBSTR bstrTitle(OLESTR("Addin")); HRESULT hr = spPages->Add((_variant_t)varProgId,(_bstr_t)bstrTitle); if(FAILED(hr)) ATLTRACE("\nFailed adding propertypage"); }
现在,您可以创建 OutlookAddin.idl 文件到您的项目中,该文件会在 Windows 注册表中创建适当的 GUID。
#include "olectl.h" import "oaidl.idl"; import "ocidl.idl"; [ object, uuid(8A0A29F8-D8C8-4F6D-ABA3-1A8B1B7A463E), dual, helpstring("IAddin Interface"), pointer_default(unique) ] interface IAddin : IDispatch { }; [ object, uuid(F148A063-7F9B-42B7-BE86-E33E956641C0), dual, nonextensible, helpstring("IPropPage Interface"), pointer_default(unique) ] interface IPropPage : IDispatch{ }; [ uuid(38C2F8F4-2700-4201-8031-9B16DFEF34E0), version(1.0), helpstring("OutlookAddin 1.0 Type Library") ] library OUTLOOKADDINLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(AA4D594A-D7A6-457E-A583-E7F3303415BE), helpstring("Addin Class") ] coclass Addin { [default] interface IAddin; }; [ uuid(9B3FFDCF-2235-472B-9BAF-FBA916EFC936), helpstring("PropPage Class") ] coclass PropPage { [default] interface IPropPage; }; };
最后,我们编写按钮事件代码。当用户按下工具栏按钮时,插件会为选定的自定义联系人打开自定义表单。
void __stdcall OutlookAddin::OnClickButton(IDispatch* Ctrl, VARIANT_BOOL * CancelDefault){ _bstr_t bstrFullName = m_spButton->GetCaption(); CComQIPtr <Outlook::_ContactItem> pContactItem; CComQIPtr <Outlook::_Inspector> pInspector; LPDISPATCH pContact = GetSelectedItem(); HRESULT hr = pContact->QueryInterface(&pContactItem); if (FAILED(hr)) { MessageBox(NULL,"Select a contact item, please", "Outlook Addin",MB_ICONERROR); return; } pContactItem->Display(); //you can add UserProperties bottom code here m_spApp->ActiveInspector(&pInspector); BSTR pageName; _bstr_t pageStr ("Custom"); pageName = pageStr.copy(); pInspector->SetCurrentFormPage(pageName); }
现在,您的插件可以管理您的自定义联系人表单并检索默认或用户定义的字段。如果您想访问用户定义的字段(如“昵称”),可以使用 Outlook 对象模型中的“Outlook::UserProperties
”和“Outlook::UserProperty
” COM 对象来实现。
//UserProperties example ... _bstr_t empty = _T(""); _bstr_t label = _T("Nickname"); CComPtr < Outlook::UserProperties >pUserProperties; CComPtr < Outlook::UserProperty > pUserProperty; CComVariant value(DISP_E_PARAMNOTFOUND, VT_ERROR); if (!pContactItem) return; BSTR curMsgClass; HRESULT hr = pContactItem->get_MessageClass(&curMsgClass); if (FAILED(hr)) return; if (ToStdString(curMsgClass) != "IPM.Contact.Custom") return; hr = pContactItem->get_UserProperties(&pUserProperties); if (FAILED(hr)) return; BSTR nameProp = label.copy(); BSTR bstr_empty=empty.copy(); hr = pUserProperties->Find(nameProp,value,&pUserProperty); if (FAILED(hr)) return; pUserProperty->get_Value(&value);
关注点
您可以以编程方式修改和发布默认 Outlook 自定义表单。这个插件可以通过遵循本文和 背景文章 中显示的步骤来创建。创建插件后,您就可以使用 Outlook 对象模型创建、发布和检索自定义表单中的数据。表单可以从属性页对话框(显示在 Outlook 选项中)加载/卸载,该对话框可以通过单例类(本例中的 FormTemplate
类)与我们的插件通信。然后,您可以在 COM 插件中使用和操作自定义表单数据。
历史
版本 1.0。