Introduction
When you create an Outlook COM/ATL add-in for Microsoft Outlook, like a toolbar or a new popup menu, sometimes you would publicize programmatically a custom form and retrieve its data. I here suppose that you know how to create an Outlook form template in a oft file and how to create a COM/ATL add-in for MS Outlook. If you have never created a custom form or an Outlook COM add-in, I suggest to you to read the Background articles, before you continue to read this one. In this sample code, we'll load an Outlook toolbar with a push button that displays the selected contact item and a Property Page that can switch between the default Outlook contact form and our custom contact form. The push-button retrieves user data of the selected contact and displays its relative value.
Background
It's necessary that you first read some articles about Outlook COM add-ins, Outlook Object Model and Outlook form customization. You can read some of these articles here:
Using the code
First of all, we must create an Outlook form template. Create a new contact form and enter in custom contact form designing mode. Insert a new custom field named "Nickname", rename tab page from "(p.2)" to "Custom", and save this in an Outlook form template (oft) file named "Custom.oft". You should create a custom form like this:
We now suppose that you saved this custom form as "C:\Custom.oft", and you have not yet publicized it.
With .NET, you can create a COM add-in selecting new ATL COM from Wizard and then selecting DLL Project. Now after you add an ATL Object through Wizard, go to Implement Interface -> Add Type Lib -> Microsoft Add-in Designer -> _IDTExtensibility2. If you have Microsoft Outlook 2003, add:
#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;
to your StdAfx.h. If you have Outlook XP or 2000, look Background links for more details.
Create your ATL/COM add-in: add a _CommandBarButton
in a toolbar and its _CommandBarButtonEvents
in sink map.
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< 1,OutlookAddin,
&__uuidof(Office::_CommandBarButtonEvents)> CommandButton2Events;
typedef IDispEventSimpleImpl< 2,OutlookAddin,
&__uuidof(Outlook::ApplicationEvents)> AppEvents;
public:
protected:
BEGIN_COM_MAP(OutlookAddin)
END_COM_MAP()
BEGIN_SINK_MAP(OutlookAddin)
SINK_ENTRY_INFO(1, __uuidof(Office::_CommandBarButtonEvents),
0x01, OnClickButton, &OnClickButtonInfo)
SINK_ENTRY_INFO(2,__uuidof(Outlook::ApplicationEvents),
0xf005,OnOptionsAddPages,&OnOptionsAddPagesInfo)
END_SINK_MAP()
Change OnConnection()
and OnDisconnection()
methods in this way:
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);
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);
CommandButton2Events::DispEventAdvise((IDispatch*)m_spButton);
AppEvents::DispEventAdvise((IDispatch*)m_spApp);
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;
}}
The AddIconButton()
method adds a generic custom icon button to your toolbar:
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;
spNewBar = pBarControls->Add(vToolBarType,
vEmpty, vEmpty, vEmpty, vShow);
ATLASSERT(spNewBar);
spNewBar->put_BeginGroup(vBeginGroup);
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);
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
::CloseClipboard();
::DeleteObject(hBmp);
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;
}
Now add FormTemplate
and PropPage
classes. The first class manages Custom Contact Form template, and the second class visualizes a new option page in Outlook:
class ATL_NO_VTABLE PropPage :
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();
}
protected:
LRESULT OnBnClickedButtonLoad(WORD ,
WORD , HWND , BOOL& );
LRESULT OnBnClickedRadioCustom(WORD ,
WORD , HWND , BOOL& );
LRESULT OnBnClickedRadioDefault(WORD ,
WORD , HWND , BOOL& );
private:
FormTemplate* m_pForm;
bool m_bCustomForm;
};
OBJECT_ENTRY_AUTO(__uuidof(PropPage), PropPage)
The FormTemplate
class is a Singleton class that implements some methods to load/unload custom forms:
class FormTemplate
{
public:
static FormTemplate* GetInstance(CComPtr<Outlook::_Application>
spApp = 0);
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
Now you are ready to publicize programmatically your new contact form. I here suppose that you would publicize in contact folder itself.
Furthermore, if you want to convert all your existent items to your new custom contact form, you need to change existent items' Message
class too. In this way, you say to Outlook what form must it use when it opens a contact item.
int FormTemplate::LoadCustomForm(void)
{
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);
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);
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;
}
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;
}
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;;}
The complementary method LoadDefaultForm()
reconverts all your existing contact items to default contact form.
int FormTemplate::LoadDefaultForm(void)
{
_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;
}
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;
}
Now, add a property page to your project with two radio buttons and a push-button: this allows you to switch between default and custom forms.
To do this, you should add a dialog box (IDD_PROPPAGE
) to your project, something like this:
See Background articles for more details on how to create a Property Page for Outlook, or see demo project linked in this article.
PropPage
implementation catch button events:
#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 ,
WORD , HWND , BOOL& ){
if (m_bCustomForm)
m_pForm->LoadCustomForm();
else
m_pForm->LoadDefaultForm();
return 0;
}
LRESULT PropPage::OnBnClickedRadioCustom(WORD ,
WORD , HWND hWndCtl, BOOL& ){
m_bCustomForm = true;
return 0;
}
LRESULT PropPage::OnBnClickedRadioDefault(WORD ,
WORD , HWND , BOOL& ){
m_bCustomForm = false;
return 0;
}
In OutlookAddin
class, you must add property page:
void __stdcall OutlookAddin::OnOptionsAddPages(IDispatch* Ctrl)
{
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");
}
Now you can create an OutlookAddin.idl in your project that creates the appropriate GUID in the Windows registry.
#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;
};
};
At last, we write push-button event code. When user pushes the toolbar button, the add-in opens the custom form for the selected custom contact.
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();
m_spApp->ActiveInspector(&pInspector);
BSTR pageName;
_bstr_t pageStr ("Custom");
pageName = pageStr.copy();
pInspector->SetCurrentFormPage(pageName);
}
Now your add-in can manage your custom contact forms and retrieve default or user defined fields. If you want access to user defined fields like "Nickname", you can do this using "Outlook::UserProperties
" and "Outlook::UserProperty
" COM objects in the Outlook Object Model.
_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);
Points of Interest
You can programmatically modify and publish default Outlook custom forms. This add-in can be created following the steps shown in this article and in other Background articles. After you have created the add-in, you can then create, publicize and retrieve data from your custom forms using the Outlook Object Model. The forms can be loaded/unloaded from a PropertyPage dialog (displayed in Outlook options) that can communicate with our add-in by a Singleton class (FormTemplate
class, in this example). You can then use and manipulate custom form data, inside your COM add-in.
History
Version 1.0.