Extending Outlook 2000

Part II: Building a COM Add-in

By Thomas Rizzo

Last month, we discussed the architecture behind the Office 2000 COM Add-in technology. This month, we'll put the theory into practice by building a COM Add-in for Outlook 2000. Besides COM Add-ins, Outlook 2000 features a number of other developer enhancements, such as VBA support, a greatly enhanced object model, Folder Homepages, and Activity Tracking. These features are beyond the scope this article, however, so please refer to the Outlook 2000 online help to learn more.

The Add-in we're going to build will provide Outlook with two of the capabilities developers request most often: 1) extending menus and toolbars at the application level; and 2) extending property pages for folders, and for the main Options page in Outlook. While you could extend toolbars and menus in Outlook 98, your extensions were probably at the item-level since Outlook 98 didn't provide a way (beyond Exchange Client Extensions), to fire an event when your button or menu item was selected within application scope. In contrast, your COM Add-in will be loaded by Outlook, therefore allowing Outlook to call your Add-in to respond when users click your custom button or menu item.

Building Your COM Add-in

The first step when building a COM Add-in is to fire up your favorite COM development tool. We're going to use Visual Basic 6.0 (VB6) in this article, but you can instead use VBA, Visual C++, etc. In VB6, you need to select an ActiveX DLL as your project type (see FIGURE 1).

FIGURE 1: The VB6 New Project dialog box.

Once you've done that, you will need to add a reference to the Microsoft Add-in Designer (msaddndr.dll) as shown in FIGURE 2. This DLL contains the IDTExtensibility2 interface, which our Add-in must support. We'll also need to add a reference to the Outlook 2000 and Office 2000 object models, since we're going to use them to extend the Outlook environment.

FIGURE 2: The VB6 References dialog box.

Now the coding fun begins. To make sure we tell VB6 that we're going to implement the IDTExtensibility2 interface in our application, we need to add the following statement to our ActiveX DLL code:

Implements IDTExtensibility2

VB6 will then add the methods we need to implement to the drop-down list in the VB6 interface. With the methods for IDTExtensibility2 in place, we need to add our code to those methods to implement our functionality. All the routines - their names begin with "IDTExtensibility2" - are shown in Listing One, where they have been fully implemented.

Stepping through the Code

We added code in the OnConnection method, so we can store the Outlook Application object in one of our global variables for use throughout the DLL. The OnConnection method passes an Application variable as an object to the procedure. This Application object will be the Application object for whichever host Office application your add-in is loaded by. Because we're being loaded by Outlook, the variable that is passed is the Outlook Application object. If you want to validate that your Add-in is being loaded by the correct host Office application, you can check the variable or properties on the variable. For example, Outlook's Application object has a ProductCode property which returns a GUID that uniquely identifies Outlook. You can create a constant that contains this GUID and then compare it to the ProductCode property to see if the host application loading your Add-in is Outlook.

Once we've captured the Application object in a variable, we need to use the Office CommandBars collection to modify the menu items and toolbars of the Outlook client. Because the CommandBars collection is used across all the Office products, all the code that you see in Listing One could be used to modify menu items and toolbars in other Office products.

To modify the menu bar in Outlook, we need to first retrieve the CommandBars collection from an Outlook Explorer object. Once we have the CommandBars collection, we want to add a new menu bar for our custom application. To do this, we'll use the Add method on the CommandBars collection. In this method, True is passed to the MenuBar property, which will cause the new CommandBar to act like a menu bar, and replace the default Outlook menu bar.

After we create the new menu bar, we need to add buttons to it. To do this, the code uses the Add method of the Controls collection for the new CommandBar we created. First, the code adds a popup control for the first menu item on the menu bar. Then, we use the Add method on this new menu item's Controls collection to add new menu options to its drop-down list. When the code is finished executing, you'll see a menu structure as shown in FIGURE 3.

FIGURE 3: The example custom menu structure.

Now that we've created the items for new menu, we need to be able to handle when a user clicks on one of those items. To do this, the code declares the variables for the menu items using the WithEvents keyword. This keyword allows your code to receive events fired by the application and write code to handle the events. To handle the Reset Menu command, the code fires on the Click event for that menu item, and then deletes the new menu bar that we created. Outlook automatically reverts to its original menu bar when you delete the custom one.

Custom Property Pages

With that code snippet finished, let's turn our attention to the custom property page. Outlook 2000 provides a great deal of extensibility to its shell through the use of custom property pages. Not only can you extend Outlook's standard Options dialog box, but you can also extend the property pages for individual folders. FIGURES 4 and 5 show examples of both types of customization. Although the figures show the same custom property page, you can provide different property pages for both types of property page environments. In fact, you can provide unique custom property pages for every folder in your Outlook client.

FIGURE 4: The customized Inbox Properties dialog box.

FIGURE 5: The customized Options dialog box.

Creating custom property pages is straightforward in Outlook 2000. The first step is to create a new ActiveX control in VB6. The ActiveX control you create will be the interface and functionality for your custom property page. Listing Two shows the code for the sample property page for this article.

The next step is to implement the Outlook PropertyPage interfaces by placing this statement in your code:

Implements Outlook.PropertyPage

and then implementing the resulting three methods: Apply, Dirty and GetPageInfo.

The Apply method should implement the required functionality for your application when the user changes a value in your control, and then hits the Apply button to make those changes permanent. The sample property page just resets an internal variable that keeps track of whether the page is dirty. However, in your applications you could write to the registry, or save information into a database, when this method is called.

The Dirty property is used by Outlook to check whether the value of some item on your property page has changed. This property is used with the OnStatusChange method on the Outlook PropertyPageSite object. When an item gets dirty on your property page, you need to tell Outlook the status has changed for your page. To do this, call the OnStatusChange method on the PropertyPageSite object. Outlook will then check the Dirty property to see if the page has truly become dirty. If you return True for the Dirty property, Outlook will enable the Apply button at the bottom of your property page.

The GetPageInfo method allows you to specify a help file and help index for your custom property page. Because we don't have any help for our example, the code specifies a bogus file and index.

One Hitch

Pretty easy right? Well, there's one "gotcha" you should know about. To get the name of your page to display in the tabs with all the other property pages, you need to create a special property in your code. To do this, just create a new property. I named this new property Name in my code. Then, go to the Tools menu and select Procedure Attributes. Make sure your property is selected in the Name drop-down list, then click the Advanced button. For the Procedure ID drop-down, make sure to select Caption. You need to set this unique ID for this property so Outlook knows what name to use for the tab for your property page. FIGURE 6 shows these steps in VB6.

FIGURE 6: The VB6 Procedure Attributes dialog box.

Now that we have the OCX set for our property page, we need to have Outlook add our new property page to the standard property pages. To do this, we use the new OptionsPageAdd event in the Outlook 2000 object model. Both the Namespace and Application objects in Outlook fire this event when someone goes to launch a set of property pages.

For the Application object, this event is fired when the user selects Tools | Options. For the Namespace object, this event is fired whenever a user goes to the properties for a folder. The only difference between the two events is that the Namespace version gets passed a MAPIFolder object named Folder that contains the folder of which the user is trying to get the properties. You can use this object to check which custom property page you should create and add to the standard property pages for the folder.

Both events get a PropertyPages collection object named Pages. Using the Add method of this collection, you can add your new custom property page to the collection. To do this, you'll first need to instantiate your ActiveX control using CreateObject, then you'll need to pass the variable containing your new control to the Add method.

That's it! You now have a new custom property page in Outlook 2000.

Conclusion

While this series has helped you get started with the world of COM Add-ins in Office 2000, there is a lot more you can do with this technology to extend your Office applications. To learn more about COM Add-ins, be sure to pick up a copy of Microsoft Office 2000 Developer Edition when it's released. Enjoy!

Thomas Rizzo works as a Product Manager in the Microsoft Exchange Server Product Unit in Redmond, WA. He specializes in evangelizing development features in both Exchange and Outlook. You can reach Tom at thomriz@microsoft.com. Tom is also the author of Programming Microsoft Outlook and Exchange Server [Microsoft Press, due in March, 1999].

Begin Listing One - IDTExtensibility2
Implements IDTExtensibility2

Dim WithEvents oApp As Outlook.Application
Dim oCB As Office.CommandBarButton
Dim oCBs As Office.CommandBars
Dim oMenuBar As Office.CommandBar
Dim WithEvents oNS As Outlook.NameSpace
Dim WithEvents oMyCB As Office.CommandBarButton
Dim WithEvents oResetCB As Office.CommandBarButton
Dim oFolder As Outlook.MAPIFolder

Private Sub IDTExtensibility2_OnAddInsUpdate( _
  custom() As Variant)

  ' Use this subroutine when Add-ins are updated.
  MsgBox "OnAddInsUpdate called"
End Sub

' Use this subroutine when the host app is shutting down.
' You should persist or destroy your objects in this
' subroutine.
Private Sub IDTExtensibility2_OnBeginShutdown( _
  custom() As Variant)

  MsgBox "OnBeginShutdown called"
  On Error Resume Next

  Set oApp = Nothing
  Set oCBs = Nothing
  Set oMenuBar = Nothing
  Set oMyCB = Nothing
  Set oNS = Nothing
  Set oCB = Nothing
  Set oResetCB = Nothing
  Set oFolder = Nothing
End Sub

Private Sub IDTExtensibility2_OnConnection( _
  ByVal Application As Object, ByVal ConnectMode As _
    AddInDesignerObjects.ext_ConnectMode, _
  ByVal AddInInst As Object, custom() As Variant)

  ' This subroutine is called when your Add-in is connected
  ' to by the host application.
  MsgBox "OnConnection called"
  ' Get the Application object for Outlook.
  Set oApp = Application
  ' Get the Namespace.
  Set oNS = oApp.GetNamespace("MAPI")
  ' Get a Folder to extend with the PropPage extension.
  ' Let the user pick the folder.
  Set oFolder = oNS.PickFolder()
  ' Customize the Outlook Menu structure and toolbar.
  Set oCBs = oApp.ActiveExplorer.CommandBars
  Set oMenuBar = oCBs.Add("CustomMenu", , True, True)
  oMenuBar.Visible = True
  Set oMyControl = _
    oMenuBar.Controls.Add(msoControlPopup, , , , True)
  oMyControl.Caption = "&Menu Item"
  Set oResetCB = oMyControl.Controls.Add( _
    Type:=msoControlButton, Temporary:=True, Before:=1)
  oResetCB.Caption = "&Reset Menu"
  oResetCB.Enabled = True
  Set oMyCB = oMyControl.Controls.Add( _
    Type:=msoControlButton, Temporary:=True, Before:=1)
  oMyCB.Caption = "&Test Menu Item"
  oMyCB.Enabled = True
End Sub

Private Sub IDTExtensibility2_OnDisconnection( _
  ByVal RemoveMode As _
    AddInDesignerObjects.ext_DisconnectMode, _
  custom() As Variant)

  ' This Sub is called when your add-in is
  ' disconnected from the host.
  MsgBox "OnDisconnection called"
End Sub

Private Sub IDTExtensibility2_OnStartupComplete( _
  custom() As Variant)

  ' This Sub is called when the host application has
  ' completed its startup routines.
  MsgBox "OnStartupComplete called"
End Sub

Private Sub oMyCB_Click( _
  ByVal Ctrl As Office.CommandBarButton, _
  CancelDefault As Boolean)

  MsgBox "You clicked me!"
End Sub

Private Sub oNS_OptionsPagesAdd( _
  ByVal Pages As Outlook.PropertyPages, _
  ByVal Folder As Outlook.MAPIFolder)

  If Folder.Name = oFolder.Name Then
    ' Add in the Options page to the folder.
    Set oNewPage = CreateObject("TestPropPage.PropPage")
    Pages.Add oNewPage
  End If
End Sub

Private Sub oApp_OptionsPagesAdd( _
  ByVal Pages As Outlook.PropertyPages)

  ' Add a new Prop Page to the Tools/Options prop page.
  Set oNewPage = CreateObject("TestPropPage.PropPage")
  Pages.Add oNewPage
End Sub

Private Sub oResetCB_Click( _
  ByVal Ctrl As Office.CommandBarButton, _
  CancelDefault As Boolean)

  oMenuBar.Delete
End Sub
End Listing One

Begin Listing Two - The PropertyPage

Implements Outlook.PropertyPage

Private oSite As Outlook.PropertyPageSite
Private boolInitializing As Boolean
Dim m_fDirty As Boolean
Dim m_AdminDLL As Object

Private Sub SetDirty()
  If Not oSite Is Nothing Then
    m_fDirty = True
    oSite.OnStatusChange
  End If
End Sub

Private Sub PropertyPage_Apply()
  On Error GoTo PropertyPageApply_Err
  m_fDirty = False
  Exit Sub

PropertyPageApply_Err:
  MsgBox "Error in PropertyPage_Apply.  Err# " & _
    Err.Number & " and Err Description: " & Err.Description
End Sub

Private Property Get PropertyPage_Dirty() As Boolean
  PropertyPage_Dirty = m_fDirty
End Property

Private Sub PropertyPage_GetPageInfo(HelpFile As String, _
  HelpContext As Long)

  HelpFile = "nothing.hlp"
  HelpContext = 102
End Sub

Private Sub Check1_Click()
  MsgBox "You clicked me!"
  SetDirty
End Sub

Private Sub Command1_Click()
  MsgBox "You clicked me!!"
  SetDirty
End Sub

Private Sub UserControl_EnterFocus()
  boolInitializing = False
End Sub

Private Sub UserControl_Initialize()
  m_fDirty = False
  boolInitializing = True
End Sub

Private Sub UserControl_InitProperties()
  On Error Resume Next
  Set oSite = Parent
End Sub

Public Property Get Name() As Variant
  ' Set the caption for the proppage.
  Name = "Test Prop Page"
End Property
End Listing Two