Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A VC++ Outlook COM Add-in that publicizes a custom form

0.00/5 (No votes)
6 Apr 2005 2  
This article explains how to publicize and retrieve data from custom Outlook forms programmatically.

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:

// 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;

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</*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()

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);

    // 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;
}}

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; 
    // 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;
}

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 : //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)

The FormTemplate class is a Singleton class that implements some methods to load/unload custom forms:

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

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)
//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;;}

The complementary method LoadDefaultForm() reconverts all your existing contact items to default contact form.

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;
}

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:

// 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;
}

In OutlookAddin class, you must add property page:

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");
}

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(); //you can add UserProperties bottom code here

    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.

    //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);

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here