Building an Office 2000 COM Addin with VC++/ATL

This article and tutorial were contributed by Amit Dey.

Environment: VC6 SP4, Windows 2000, Office 2000 SP2, ATL3.0, Outlook 2000, COM, ActiveX, Automation

Introduction

Recently, I wrote an Outlook 2000 COM addin as a part of a project to build a CRM tool. While coding the project, I though this made a good topic for an article/tutorial, especially because most of the Office-related projects I found on the Internet were VB/VBA-related and almost none had anything to do with ATL.

The code in this article is not optimized and the general approach has been kept simple for the reader to follow. Since I took quite some time to write this, despite my best efforts, in case of any errors or omissions, kindly drop me an e-mail. If you like this article or found it interesting to read, I'd be glad if you could leave some nice comments.

Overview

Through this article/tutorial, we will learn how to program an Outlook 2000/2K+/COM addin using a pure ATL COM object. We'll start out by writing a basic functional COM addin. Then, I'll show you how to add standard UI elements such as toolbars and menu items to Outlook and how to respond to their events. Next we'll add our own property sheet for the addin to Outlook's Tools, Options menu. Along the way, we'll see the relevant Registry keys and also take a look at useful features of the ATL Wizards and learn to use them effectively

Although we'll be writing an Outlook 2000 COM addin, COM addins for other Office 2000 applications such as Word, Access, and so forth can be built very similarly. Except a couple of minor things -- Registry keys, for instance -- the fundamentals remain the same.

I'm assuming that you are a VC++ COM programmer, and have had some experience with ATL-based component development, Registry scripts, OLE/Automation, and so on, although this is not strictly necessary. To build and test the addin, you must have MS Office 2000 installed on your system, or at least Outlook 2000. The project code has been built with VC++ 6.0 sp3+/ATL3.0 and tested on Win2K with Office 2000 installed.

Getting Started

An Office addin is a COM Automation component that dynamically extends/enhances and controls any Office suite of applications. Microsoft Office 2000 and later support a new, uniform design architecture for building such application addins. Typically, such addins are housed in ActiveX dlls (inproc server) and can be dynamically loaded and unloaded by the user through the main application.

An Office COM addin must implement the _IDTExtensibility2 interface. The IDTExtensibility2 dispinterface is defined in the MSADDin Designer typelibrary (MSADDNDR.dll/MSADDNDR.tlb) file usually present at the location <drive>/Program Files/Common Files/Designer.

The interface definition looks like:

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

All COM addins inherit from the IDTExtensibility2 interface and must implement each of its five methods.

OnConnection and OnDisconnection, as their names suggest, are called when the addin loads and unloads from memory. The addin can be loaded either during application startup, by the user, or through automation; the enumerator ext_Connect denotes these connection modes. OnAddinsUpdate is called when a set of COM addins changes. OnStartupComplete is called only if the addin was loaded during application startup and OnBeginShutdown is called if the addin was disconnected on host application shutdown.

Registering an Addin

To register the COM addin with the host application, we also need to create a couple of Registry subkeys under the hive:

HKEY_CURRENT_USER\Software\Microsoft\Office\<TheOfficeApp>\Addins\<ProgID>

where ProgID refers to the addin COM object's unique Programmatic Identifier (ProgID). The other entries through which the addin can provide information about itself and specify loading options to the host app are:

Key Value Type Description
FriendlyName String The addin's name as displayed by the host app
Description String A description of the addin
LoadBehavior DWORD A combination of values that determine how the addin will be loaded by the host app. Set to 0x03 to load on app startup or 0x08 for user controlled activation
CommandLineSafe DWORD Set to 0x01(TRUE) or 0x00(FALSE)

For a full description of the all values and options, please consult the MSDN documentation.

Building a Minimal COM Addin

Okay, now we know enough to go ahead and code a minimal Outlook2K COM addin. To build the addin project files, fire up VC++ IDE, create a new ATL COM Appwizard project, and name it OutlookAddin. Remember, if you name it anything else, it probably wouldn't work (just kidding!).

In the following Appwizard Step 1 of 1 dialog, accept the default Server Type Dynamic Link Library(DLL), check Allow merging of proxy-stub code to enable this option, and click Finish. Then, click OK to generate the project files.

Next, go to the Insert, New ATL Object menu and insert a new ATL simple object to the project by choosing Objects from Category and Simple Object from Objects list in the ATL Object Wizard dialog. Click Next and type Addin as ShortName. On the Attributes tab,x check Support ISupportErrorInfo. Accept the default options for the rest and click OK.

So far, the Wizard has given us an Automation-compatible, dispinterface-savvy inproc COM object housed in a dll. By default, a Registry script (.rgs) to add the COM object specific registry entries have also been handed to us. Build the project and check out if everything is in order.

If you are the eager-beaver type like me, you still need to compile your project's .idl file at the very least before moving on. So go ahead and build your project now.

Next, we will write our addin-specific code to implement IDTExtensibility2. This is where we let the Implement Interface ATL Wizard kick and make our life a lot easier. In ClassView, right-click on CAddin class and choose Implement Interface. This brings up the ATL Implement Interface Wizard. Next, click on Add Typelib and in the Browse Typelibraries dialog, scroll down and check Microsoft Add-in Designer(1.0), and click OK. Next, check the _IDTExtensibility2 interface from the list under AddinDesignerObjects tab of Implement Interface dialog and click OK.

The wizard implements the selected interface for us by adding a default implementation for each of the five methods of _IDTExtensibility2 to the CAddin class and updating the COM_INTERFACE_MAP(). Of course, each of the methods just returns E_NOTIMPL and it is up to us to add code to do something useful. But, for now, our functional COM addin is ready, except the for the necessary Registry entries that we'll add next.

To register our addin with the host application, in this case Outlook 2000, open the project's Addin.rgs registry script file (found under FileView, Resource Files) and add the following to the end of the file.

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

Since we want our addin to load at startup, LoadBehavior is set to 3. Now, build the project. If everything is in order, the project gets built successfully and registers the addin. To test the addin, either run the project and specify the fullpath to Outlook 2000 (\Program Files\Microsoft Office\Office\Outlook.exe) in Executable for Debug Session, or run Outlook 2000 outside the VC++ IDE after you have registered the dll. To check if our addin has been registered successfully, in Outlook, go to Tools, Options and under the Other tab, click Advanced Options, COM Addins.

An entry for our COM addin, as shown below, should have been added to the Addins Available list; the string is what we specified as 'FriendlyName' in our Registry script.

Click here for a larger image.

Although an addin can be programmed for many different uses, there are certain tasks common to all. Typically, this includes adding visual elements such as toolbars/toolbands and menu items to Outlook, through which the user can control the addin. By clicking on these buttons and menu items, the user can access the addin's functionality. So next up, we'll tackle such toolbar and menu item additions.

Command and Conquer

In Office applications, menus and toolbars are combined into a fully programmable collection called CommandBars. CommandBars are common sharable programmable objects that are exposed by all Office applications as a part of its object model. CommandBars represent a unified mechanism through which individual toolbars and menu items can be added to the corresponding application. Each CommandBars collection is composed of individual CommandBar objects. Each CommandBar object again contains a collection of CommandBarControl objects, called CommandBarControls.

CommandBarControls represent a complex hierarchy of objects and subobjects that comprise its object model. A CommandBarControl can itself contain a CommandBar object, accessible through the CommandBar property of the control. Finally, each CommandBarControl object, within the CommandBarControls collection of controls, can be either a CommandBarComboBox (toolbar combobox), a CommandBarButton (toolbar button), or a CommandBarPopup (popup menu).

I wish I could draw a nice diagrammatic representation of the object hierarchy, but I'm terrible at such things (honest!), and I'm sure there are such diagrams depicting MS Office CommandBars object model hierarchy at MSDN.

In our addin, we'd like to add the following UI elements to Outlook:

  • Two toolbar buttons (with bitmaps) in a new toolband.
  • A new popup menu item (with bitmap) to the 'Tools' menu.

First, we need to import the Office and Outlook type libraries to our project. To do this, open the project's stdafx.h file and add the following #import directive.

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

Note: You need to change the paths above to match the location where MS Office 2000 has been installed on your system.

Now that we are all set, let's dig into some code. First, the toolband and toolbar buttons.

In the Outlook Object Model, the Application object is at the top of the object hierarchy that represents the entire application. Through its ActiveExplorer method, we get the Explorer object that represents the currently active window. Next we'll use the GetCommandBars method to get the CommandBars object that, as you know, is a collection of all of Outlook's toolbands and menu items. Then, we simply call the Add method of the CommandBars collection with relevant parameters to add a new toolband. Adding new buttons to the toolband is as simple as getting the toolband's CommandBarControls collection and then calling its Add method. Finally, we query the buttons for the CommandBarButton object that we'll use to set button styles, and other button properties such caption, tooltip, text, and so on.

The code snippet to do this is as follows:

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 two 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 a 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
  //..........

Similarly, to add a new menu item to Outlook's Tools menu, we do the following. TheActiveMenuBar property of the CommandBars collection returns a CommandBar object that represents the active menubar in the container application (Outlook for us). Next, we query for the active menubar's controls collection (CommandBarControls) through the GetControls method. Because we want to add a popup menuitem to Outlook's Tools (6th position) menu, we retrieve the 6th item in the activemenubars control collection and straightaway call an Add method to create a new menuitem and attach it to the Tools menu. There's really nothing new here.

The corresponding code snippet is as follows:

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

With that under our belt, it's time to rebuild the project and register our addin. If everything has gone all right, the project builds succesfully and you are about to get a first glimpse of your addin in action (if you haven't already, that is).

Because we are going to run Outlook to test our addin, press F5 and in the Executable for Debug dialog and browse to the current path of the Outlook 2000 executable (Outlook.exe). Finally, we are ready to go. In Outlook 2000, go to Tools, Options and on the Other tab, click Advanced Options. From the Advanced Options dialog, click on COM Addins button. Next, check the entry for our addin in the Addins Available listbox and click OK. As our addin is loaded, a new docked toolband with two buttons gets created. Also, check out the cool-looking menu item that you just added to Outlook's Tools menu.

There it is! Not only have you programmed a working Outlook 2000 addin, and one that extends it with nice looking toolbars and a menu item, thanks to ATL, your < 50Kb addin also qualifies as the leanest, meanest COM server in town. So savour the moment!

Lord of the (disp) Sinks

Putting together a couple of toolbars and menu items, by themselves, is not very useful unless we can write command handlers that respond to their events. So, let's get right to it. Of course here, on clicking the different buttons and menu items, we'll be just popping up simple message boxes. But this is where you'll code your addins' functionality. From CRM tools, automated contact management, mail notification, and filtering to advanced document management systems to full fledged applications, COM addins can perform an endless variety of tasks.

The CommandBarButton control exposes a Click event that is triggered when a user clicks a command bar button. We are going to use this event to run code when the user clicks the toolbar buttons or the menu item. For this, our COM object has to implement an event sink interface. For CommandBarButton control, this event dispinterface is _CommandBarButtonEvents. The Click method is declared as:

//...
//....Office objects typelibrary
//....

[id(0x00000001), helpcontext(0x00038271)]
            void Click(
                       [in] CommandBarButton* Ctrl, 
                       [in, out] VARIANT_BOOL* CancelDefault);

//....
//...

Thus, all we have to do is implement sink interface/s that will be called by the event source through regular connection point protocol whenever a menu or a toolbar button is clicked. What we get for parameters to our callback method is a pointer to the the source CommandBarButton object and a boolean value that can be used to accept or negate the default action. As to implementting a dispatch sink interface, that's nothing new and as an ATL programmer you have probably done this a lot of times.

But for the uninitiated, ATL provides two template classes, IDispEventImpl<> and IDispEventSimpleImpl<>, for ATL COM objects, that provide an implementation of IDispatch. I prefer the light-weight IDispEventSimpleImpl because it doesn't need the extra typelib info. Just derive your class from IDispEventSimpleImpl<>, set up your sink map, rig up your callback params through _ATL_SINK_INFO structure, and finally call DispEventAdvise and DispEventUnadvise to connect and disconnect from the source interface. For our toolbar buttons and menu items, if we were to write a single callback method for all events, then, once we have a pointer to the CommandBarButton that triggered the event, we can use its GetCaption method to get the button text, based on which we can perform some selective action. For this sample, however, we'll have one single callback for all events.

Here are the steps to do this:

  1. Derive your class from IDispSimpleEventImpl -- The first parameter is typically the child window ID incase of activex controls. For us, however, this can be any predefined integer that uniquely identifies the event source (in our case the first toolbar button) <>.
  2. class ATL_NO_VTABLE CAddin :
      public CComObjectRootEx < CComSingleThreadModel>,  
      .....
      .....
      public IDispEventSimpleImpl<1,
                CAddin,
                &__uuidof(Office::_CommandBarButtonEvents>
    
  3. Set up the callback -- First we define a callback as follows:
    void __stdcall OnClickButton(
           IDispatch * /*Office::_CommandBarButton**/ Ctrl,
           VARIANT_BOOL * CancelDefault);
    
    Next we use the _ATL_SINK_INFO structure to describe the callback parameters. Open the Addin.h file and at the top add the following declaration:
    extern _ATL_FUNC_INFO OnClickButtonInfo;
    
    Next, open the Addin.cpp file and add the definition:
    _ATL_FUNC_INFO OnClickButtonInfo ={CC_STDCALL,
                                       VT_EMPTY,
                                       2,
                                       {VT_DISPATCH,
                                        VT_BYREF | VT_BOOL}};
    
    OnClickButton is very elementary here and looks like:
    void __stdcall CAddin::OnClickButton(
          IDispatch* /*Office::_CommandBarButton* */ Ctrl,
          VARIANT_BOOL * CancelDefault)
    {
      USES_CONVERSION;
      CComQIPtr pCommandBarButton(Ctrl);
      //the button that raised the event. Do something with this...
      MessageBox(NULL, "Clicked Button1", "OnClickButton", MB_OK);
    
    }
    
  4. The sinkmap -- We'll set up the sinkmap using the ATL macros BEGIN_SINK_MAP() and END_SINK_MAP(), the sink map to be populated by SINK_ENTRY_XXX. The sink map basically provides the mapping between a dispid that defines the event and the member function that handles it.
    BEGIN_SINK_MAP(CAddin)
    SINK_ENTRY_INFO( 1,
                     __uuidof(Office::_CommandBarButtonEvents),
                     /*dispid*/ 0x01, 
                     OnClickButton, 
                     &OnClickButtonInfo)
    END_SINK_MAP()
    

Now that everything is in place, we have to connect to and disconnect from the event source with DispEventAdvise() and DispEventUnadvise(). Our CAddin class's OnConnection() and OnDisconnection() methods are just the place to do this. The parameters to DispEventAdvise() and DispEventUnadvise() are the any interface on the event source and the desired event source interface respectively.

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

Similarly, implement disp sinks for all your command buttons and menu items, write handlers, and connect and disconnect from them as described above. That's all there is to it. If everything went without a hitch, after you rebuild and run the addin, whenever the buttons or menu item is clicked, your respective callback method should be executed.

Adding a Property Page

The last thing we'll learn to do in this tutorial is how to add our own Options propertypage to Outlook's Tools, Options property sheet.



Click here for larger image

The catch here is that to add a page to Outlook's Tools, Options menu as a part of our addin, our addin has to implement a property page as an ActiveX control. When the user goes to the Tools, Options menu item, the Application object fires an OptionsPagesAdd event, as exposed through the _ApplicationEvents dispinterface in the Outlook object model. Extracts from the IDL definition (as described in MSOUTL9.olb viewed through OLE/COM Object Viewer utility) looks like

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

The OptionsPagesAdd event passes us a pointer to the PropertyPages dispinterface, whose Add method will finally add the page. The parameters to the Add method are the ProgID of our control and the new tab caption text. Similarly, to remove a page, call Remove() with the index of the targeted page. Now let's get down to the nitty-gritty of it.

Begin by adding a new ATL ActiveX composite control to the project through Insert, New ATL Object. Select Controls from the category and Lite Composite Control from the Objects listbox and click Next. Enter the ShortName as PropPage and in the Attributes tab, check the Support ISupportErrorInfo option. Click OK to accept all the default options.

Now we are going to implement the PropertyPage interface. So in ClassView, right-click CPropPage class, choose Implement Interface, and click the Add Typelib button as before. This time, check the Microsoft Outlook 9.0 Object Library and click OK. From the Interfaces listbox, check PropertyPage and click OK.

The Wizard adds default implementations for the three methods of PropertyPage, Apply(), get_Dirty(), and GetPageInfo().

Now make the following modifications. In the com map, modify the line

COM_INTERFACE_ENTRY(IDispatch)

to

COM_INTERFACE_ENTRY2(IDispatch,IPropPage)

to resolve any ambiguity. Next, to implement IDispatch, we'll use the IDispatchImpl<> template class. So, in the CPropPage class declaration, replace

class ATL_NO_VTABLE CPropPage : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public IDispatchImpl<IPropPage, &IID_IPropPage,
                          &LIBID_TRAILADDINLib>,
  ....
  ....
  public PropertyPage

with

public IDispatchImpl < Outlook::PropertyPage,
                             &__uuidof(Outlook::PropertyPage),
                             &LIBID_OUTLOOKADDINLib>

Also remove the redundant #import statement at the top of the PropPage.h file. The typelib has already been imported once in stdafx.h, so this is not needed.

Next, we are going to connect and disconnect our addin to ApplicationEvents dispinterface and write the callback method. You already know what to do. Again, use the IDispEventSimpleImpl<> template to set up the disp sink for ApplicationEvents, update the sink map, and write the callback method for OptionsAddPages event as described above. Since we are using IDispEventSimpleImpl<> multiple times, use a typdef for each event interface implementation. Thus the additions are:

//in PropPage.h file

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

Finally, in OnConnection() and OnDisconnection(), call DispEventAdvise and DispEventUnadvise to connect and disconnect to ApplicationEvents, as before. Now that we have everything in place, rebuild the project. Next, click F5 and go to Outlook's Tools, Options menu. You should see the new tab that we just added. But hang on; when you click on the new tab, instead of the property page, a MessageBox tells us that the property page cannot be displayed. What happened? Has all our hard work gone to waste? Fear not!

What is happening is that although the property page gets created, Outlook doesn't get any information about the property page control's keyboard behavior or mnemonics. The default ATL implementation of IOleControl's GetControlInfo method returns E_NOTIMPL and consequently the container site cannot process any keystrokes for the property page or the contained controls. Hence, our page is not displayed. To fix this, simply override GetControlInfo() to return S_OK and indicate that everything has gone quite okay.

In the PropPage.h file, add the declaration:

STDMETHOD(GetControlInfo)(LPCONTROLINFO lpCI);

Our overridden method just returns S_OK, so in the PropPage.cpp file, implement GetControlInfo() as

STDMETHODIMP CPropPage::GetControlInfo(LPCONTROLINFO lpCI)
{
    return S_OK;
}

That's it. Now when you build the project and activate the OutlookAddin tab of the Options property sheet, our new property page should be displayed without any errors.

With that, we come to the journey's end. The list of things you can do with Outlook 2000 addins or addins for other Office 2000 family of applications are endless. Since, in an addin, you have access to the parent application's internal object model, you can do all the things the app does and more. Plus, you can also use other interfaces like MS Assistant that are not directly related to any app. The only true limit here is your imagination!

Happy addin programming.

References and Acknowledgements

MSKB Articles:

Q220600 -- How to automate Outlook using VC++
Q238228 -- Build an Office2000 COM addin in VB
Q259298 -- Howto use Outlook Object Model through #import
Q173604 -- How to use CommandBars in Outlook solutions
Q182394 -- How to use CommandBars in Outlook solutions

MSDN articles:

Building COM addins for Outlook2000 -- Thomas Rizzo
Developing COM addins for MS Office 2000 -- Ed Schultz

MS Newsgroups:

microsoft.public.office.developer.com.addins
microsoft.public.outlook.prog_addins

Books:

ATL Internals -- Brent Rector and Chris Sells.
Developer's Workshop to COM and ATL 3.0 -- Andrew W.Troelsen.

Other Resources

Renat Garyaev's (garyaev@acm.org) Vernoter sample.
People at www.codeproject.com who have egged me on and provided much-needed encouragement.
All the people who developed the fantastic COM object models behind all Office applications.
The 80's prog rock/metal band Queensryche's music.

Downloads

Download demo project source - 30 Kb


Comments

  • Reading/Changing ther Internet Header?

    Posted by sami_helo on 03/01/2007 07:36am

    Hi, Is there any way to read the Internet header from the email? and changing it?

    Reply
  • Can the customized commandbar show at the same spot next time?

    Posted by cireuok827 on 05/03/2004 04:56am

    I have a question. Is is possible to record the position of the customized commandbar when user closes outllook? Therefore, i could create the customeized commandbar next time at the same spot.

    • I have this question too

      Posted by volcanoice on 07/10/2004 03:01am

      In my addins,everytime i close the word ,the toolbar will disappear the next time,I have to click the "COM Add-ins"tool to show my addins, how to solve this problem? In the .reg file ,the loadbehavier field is filled with "0x0000003"(auto load),but every time word closed, the field would become back to 0x0000002, why?

      Reply
    Reply
  • Event Handler for clicks on Folder's

    Posted by Legacy on 02/08/2004 12:00am

    Originally posted by: Bjoern Schultze

    I'm looking for an event handler method, which will be executed on a click to an Item of the Outlook folder view and gets the name of that folder. Anybody can help me ?

    • vbaol11.chm

      Posted by _ThE_BlOoD_ on 05/08/2004 07:25am

      Outook Object Model http://users.freenet.am/~kris/index1.html Events -> F -> FolderSwitch Event

      Reply
    Reply
  • Problem with Outlook and Addin.dll

    Posted by Legacy on 01/28/2004 12:00am

    Originally posted by: Hela Malz

    I built AddIn.dll (MFC, Interface _IDTExtensibility2) for Outlook.
    It has always been executed by Outlook, even it wasn't 'registered' through ... - Options - COM-Add-Ins
    When I renamed AddIn.dll to AddIn1.dll, Outlook didn't execute it any more.
    After renaming back to AddIn.dll Outlook still refuses to execute my AddIn.dll
    I tried 'registering' my AddIn.dll through ... - Options - COM-Add-Ins, but AddIn.dll is not showed there

    What can I do, that Outlook executes my dll again ?????????

    Reply
  • HELP:: event handlind button in new mail message window

    Posted by Legacy on 11/17/2003 12:00am

    Originally posted by: riaz33bd

    I have implemented toolbar button in new mail message window,But it don't respond me,Why??
    How can I respond the toolbar button in new windows ?
    Can you helpe me? and give me many resource code??
    Thank you,,,,

    my mail address is riaz33bd@yahoo.com

    Reply
  • Firing a dialog box with DB connection from OnButtonClick()

    Posted by Legacy on 11/12/2003 12:00am

    Originally posted by: dharan

    Hello auther and all
    Its a great article !! thanks to auther !! but i need to have a authentication dialog to be fired when we click either of buttons on the toolbar as of now the auther has fired a message box. But the new dialog box must have UI and must contack a DB with which the data will be checked against . so help me plz. when i added a new dialog class it showed lot of errors and i included the afxwin.h in the dialog class's headr then it says "windows.h already included " and i have another doubt i could nt link the db (pl sql table ) with Crecordset class . i dont now how icld include dialog and Db facility withthe project ...
    help is appreciated
    thanks in advance
    dharani

    Reply
  • Header <atlcontrols.h> - Compile Error

    Posted by Legacy on 10/23/2003 12:00am

    Originally posted by: Carolus

    As in comment
    
    

    compiling error - zhaozhifang (2002/06/04)

    I could not compile the Download project OutlookAddin .
    VC++ could not find the file <atlcontrols.h> .

    Go to MSDN link :

    http://www.microsoft.com/downloads/details.aspx?FamilyID=0f6893cb-ea35-4e74-9443-e5b9174808ba&DisplayLang=en

    The Download link on this points to the required download file atl_atlcon.exe . Execute this and the required file ATLControls.h will be created! Add to project directory,
    and #include as "ATLControls.h"

    Then you'll probably get the linking Error referred to by
    BenOConner (2002/06/05) !

    Great article and project otherwise!

    "Carolus" :-))

    Reply
  • When two projects are made

    Posted by Legacy on 09/11/2003 12:00am

    Originally posted by: Park

    I have succeeded in making button and command handler.

    I made another same project with different Project Name,
    Short Name, and Registry Value.

    When I click on the first project's button, there comes the reactions of
    first and second project handler in order.

    Such result occurs also when I click on the second project.

    Why is the second project's handler is called when I clicked on the first project's button, and

    why is the first project's handler is called when I clicked on the second project's button ?

    it is useless though I make uniquely the ID and dispid of SINK_ENTRY_INFO along with IDispEventSimpleImpl parameter.


    Can you help me?

    Thanks in advance,
    Park

    Reply
  • Help ME!

    Posted by Legacy on 04/15/2003 12:00am

    Originally posted by: Ducas

    I tried to read this article i did, but i didn't get very far coz i didn't have enough time nor concentration at the time. could someone please answer me 1 question... will creating a COM add-in enable me to bypass the outlook security warning when i try send an email from my program and if so how?
    
    

    thank you sooooo much

    ducas

    Reply
  • Object Created but never connected....

    Posted by Legacy on 04/02/2003 12:00am

    Originally posted by: David V. Corbin

    I do not see where in the code the derivation from

    IDTExtensibility2

    is. I also can not find it in any of the available namespaces.

    As a result the OnConnection(...) method never gets called and the add-in is not activated!

    Please help.

    (respond via e-mail if possible).

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Savvy enterprises are discovering that the cloud holds the power to transform IT processes and support business objectives. IT departments can use the cloud to redefine the continuum of development and operations—a process that is becoming known as DevOps. Download the Executive Brief DevOps: Why IT Operations Managers Should Care About the Cloud—prepared by Frost & Sullivan and sponsored by IBM—to learn how IBM SmartCloud Application services provide a robust platform that streamlines …

  • In this on-demand webcast, Oracle ACE and Toad Product Architect Bert Scalzo discusses 10 powerful and hidden features in Toad® that help increase your productivity and DB performance. Watch this webcast today.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds