Ty Anderson

Developing powerful Outlook add-ins with Visual Studio (VB.NET, C#)

With the recent Beginning Outlook Development series complete, we thought it would be a good idea to do a demo that shows how easy/simple/relatively painless it is to build a powerful Outlook add-in with Visual Studio (VB.NET, C#). Of course, we’ll be using Add-in Express for Office and .NET to get the job done.

In this article (and its accompanying video), I’ll show you to build…

  1. An advanced Outlook form region that shows the selected mail item’s mail header in the explorer window by using the SelectionChange event.
  2. An advanced Outlook form region that displays the current mail item’s folder path in the inspector window. When the user clicks the path, they are shown this folder in the explorer window.
  3. Two custom Outlook ribbons containing toggle buttons that control the display of the forms (items 1 & 2).

Of course, I’ll include the code that ties everything together. I don’t mind saying, the code is so good… you can steal it straight from here and use it today. If you have other logic you want to implement, I think you will still find this code quite useful.

Create a professional Outlook add-in with little coding

Prerequisite – the Visual Studio project created with Add-in Express for Office and .NET.

I don’t think I need to explain how to do this. Just create an ADX COM Add-in project that targets Outlook 2007, 2010 and Outlook 2013. I’m calling my project OutlookDemo… because that is what it is. Let’s get going.

Building the Outlook add-in UI

The UI for this Outlook addin consists of two ribbons and two forms. We’re going to move through them fast so we can move to the code discussion.

Custom Outlook ribbons

We need two ribbons. One for the Outlook explorer window and the other for the Outlook Inspector windows. Both will target Outlook mail items.

To create the ribbons, open the AddinModule in design view. Then complete the following steps:

  1. Add an ADXRibbonTab to the design surface and set the following properties
    • Name = ExplorerRibbon
    • Caption = HEADER OPTIONS
    • Ribbons = OutlookExplorer
  2. Select the ExplorerRibbon and use the visual designer’s toolbar to add an ADXRibbonGroup to the tab. Change the group’s Caption to My Explorer Actions.
  3. Select the ADXRibbonGroup and use the toolbar once again to add an ADXRibbonButton to the group. The button’s properties requiring changes are as follows:
    • Name = RibbonButtonViewHeader
    • Caption = View Mail Header
    • Size = Large
    • ToggleButton = True

This takes care of the ribbon for the explorer window. We have a nice Outlook ribbon with a single toggle button. Let’s create the inspector’s ribbons.

  1. Add another ADXRibbonTab to the AddInModule’s design surface and set the following properties
    • Name = InspectorRibbon
    • Caption = MY ACTIONS
    • Ribbons = OutlookMailRead
  2. Add an ADXRibbonGroup to the tab. Change the group’s Caption to My Inspector Actions.
  3. Select the ADXRibbonGroup and add an ADXRibbonButton to the group. Change button’s properties to:
    • Name = RibbonButtonToGo
    • Caption = Go To Item Folder
    • Size = Large
    • ToggleButton = True

This ribbon will now display anytime an Inspector displays a mail item. Let’s now create the custom Outlook forms that these buttons control.

The Mail Header form

This form consists of a single text box control that fills the entire form real estate. The text box will display a mail item’s mail header at the appropriate time. So, add a new ADX Outlook form to the add-in project and name it MailHeader.

The Mail Header form

Add a TextBox to the design surface and configure it according the image below.

We are done with this custom form for now. We’ll come back to it as we have some code to write.

The Folder Path form

This Outlook form will show the folder path for a mail item’s parent folder. All you need is a Panel and LinkLabel control. Place the link label inside the panel and set the properties as show here:

The Folder Path form

The UI is complete. Art class is over. Let’s code.

Coding the features

The majority of the add-in’s code resides in the AddinModule. But we allow the Outlook form regions to play their part as well.

The AddInModule code

We’ll start with the AddInModule. It contains the following routines:

  • RibbonButtonViewHeader_OnClick
  • GetMailHeader
  • ExplorerSelectionChange
  • RibbonButtonGoTo_OnClick

The first three bullets relate to the logic required for the Explorer window and the MailHeader form. The last one is for the Inspector windows and the FolderPath form.

RibbonButtonViewHeader_OnClick

This is the click even for the button in the ExplorerRibbon control. I recommend that you copy and paste this code into your Outlook add-in. Just open the AddinModule’s code view and paste it!

Private Sub RibbonButtonViewHeader_OnClick(sender As Object, _
    control As IRibbonControl, pressed As Boolean) Handles _
    RibbonButtonViewHeader.OnClick
 
    ' handle all Explorer windows
    For i As Integer = 0 To ExplorerCollectionItem.FormInstanceCount - 1
        If pressed Then
            ' show the form
            ExplorerCollectionItem.FormInstances(i).RegionState = _
                AddinExpress.OL.ADXRegionState.Normal
        Else
            ' hide the form
            ExplorerCollectionItem.FormInstances(i).RegionState = _
                AddinExpress.OL.ADXRegionState.Hidden
        End If
    Next
End Sub

This event loops through all ExplorerCollectionItem instances and either displays or hides the MailHeader form. It all depends on the value of the Pressed parameter.

GetMailHeader

This method returns the Outlook mail header text of the mail item selected in the Outlook Explorer window.

Private Function GetMailHeader() As String
 
    Dim explorer As Outlook._Explorer = Nothing
    Dim selection As Outlook.Selection = Nothing
    Dim mailItem As Outlook._MailItem
    Dim item As Object = Nothing
    Dim headerText As String = String.Empty
 
    Try
        explorer = OutlookApp.ActiveExplorer()
        'Explorer.Selection fires an exception for a top-level folder  
        selection = explorer.Selection
        item = selection.Item(1)
        If TypeOf item Is Outlook._MailItem Then
            mailItem = CType(item, Outlook._MailItem)
 
            If Me.HostMajorVersion >= 12 Then ' Outlook 2007 and higher
                ' use late binding
                Dim propertyAccessor As Object = Nothing
                propertyAccessor = mailItem.GetType().InvokeMember( _
                    "PropertyAccessor", Reflection.BindingFlags.GetProperty, Nothing, mailItem, New Object() {})
                headerText = propertyAccessor.GetType().InvokeMember( _
                    "GetProperty", Reflection.BindingFlags.InvokeMethod, Nothing, propertyAccessor, _
                    New Object() {"http://schemas.microsoft.com/mapi/proptag/0x007D001E"}).ToString()
                Marshal.ReleaseComObject(propertyAccessor)
            Else
                ' use Extended MAPI
                Dim itemPtr As IntPtr = IntPtr.Zero
                Dim retVal As String = String.Empty
                Dim propAddressPtr As IntPtr = IntPtr.Zero
 
                Try
                    itemPtr = Marshal.GetIUnknownForObject(mailItem.MAPIOBJECT)
                    If (MAPI.HrGetOneProp(itemPtr, MAPI.PR_TRANSPORT_MESSAGE_HEADERS_A, propAddressPtr) = MAPI.S_OK) Then
 
                        Dim emails As IntPtr = IntPtr.Zero
                        Dim propValue As SPropValue = CType(Marshal.PtrToStructure(propAddressPtr, GetType(SPropValue)), SPropValue)
                        headerText = Marshal.PtrToStringAnsi(New IntPtr(propValue.Value))
                    End If
                Finally
                    If propAddressPtr <> IntPtr.Zero Then MAPI.MAPIFreeBuffer(propAddressPtr)
                    If itemPtr <> IntPtr.Zero Then Marshal.Release(itemPtr)
                End Try
            End If
        End If
    Catch
    Finally
        If explorer IsNot Nothing Then Marshal.ReleaseComObject(explorer)
        If selection IsNot Nothing Then Marshal.ReleaseComObject(selection)
        If item IsNot Nothing Then Marshal.ReleaseComObject(item)
    End Try
 
    Return headerText
 
    'See this article for more info regarding accessing the mail header
    'http://blogs.msdn.com/b/zainnab/archive/2008/07/01/using-visual-studio-2008-vsto-outlook-to-pull-out-rfc-822-header-data.aspx

End Function

It begins by referencing the ActiveExplorer and using this object to reference the selected email. A Selection object can contain multiple Outlook items of various types. So, we make the assumption we only want the first item. However, we don’t assume we have a MailItem, which is why we check its type before continuing.

If we are working with Outlook 2007 or later, we move on and utilize a PropertyAccessor combined with late binding, reflection, and extended MAPI to retrieve the mail header text.

Note. To learn more about extended MAPI, please see the Add the MAPI methods section at the end of this post. You will need to add these methods to your Outlook addin for the GetMailHeader function to work. They are out-of-scope for today’s discussion but they are well-worth knowing and mastering. I have included links to help you get started. Also, my thanks to Dmitry Kostochko for his help in refining the code of this Outlook plug-in so that it is truly version neutral.

ExplorerSelectionChange

This event occurs when the user selects a different folder item within an Outlook Explorer window (we can also make it happen with code… just FYI). When the user selects a different item, we want to update the mail header text in the MailHeader form.

Private Sub adxOutlookEvents_ExplorerSelectionChange( _
    sender As Object, explorer As Object) Handles _
    adxOutlookEvents.ExplorerSelectionChange
 
    Dim ExplorerForm As MailHeader = Nothing
    ExplorerForm = TryCast( _
        ExplorerCollectionItem.GetCurrentForm( _
        AddinExpress.OL.EmbeddedFormStates.Visible), _
        MailHeader)
 
    If ExplorerForm IsNot Nothing Then
        ExplorerForm.HeaderText.Text = GetMailHeader()
    End If
End Sub

The event references the ExplorerCollectionItem‘s currently visible form. And if it exists, we updated the form’s text box with a call to GetMailHeader. Nothing to it.

RibbonButtonGoTo_OnClick

This event is pretty much the same as RibbonButtonViewHeader_OnClick.

Private Sub RibbonButtonGoTo_OnClick( _
    sender As Object, control As IRibbonControl, pressed As Boolean) _
    Handles RibbonButtonGoTo.OnClick
 
    ' handle all Inspector windows
    For i As Integer = 0 To InspectorCollectionItem.FormInstanceCount - 1
        If pressed Then
            ' show the form
            InspectorCollectionItem.FormInstances(i).RegionState = _
                AddinExpress.OL.ADXRegionState.Normal
        Else
            ' hide the form
            InspectorCollectionItem.FormInstances(i).RegionState = _
                AddinExpress.OL.ADXRegionState.Hidden
        End If
    Next
 
End Sub

The only difference is the use of the InspectorCollectionItem instead of the ExplorerCollectionItem.

Mail Header form

We’ll complete this Outlook form first because it doesn’t have much code when compared to FolderPath. All this form needs to do is respond to the ADXBeforeFormShow event.

ADXBeforeFormShow

This event fires just before the form displays. It’s the place to check the status of the ribbon’s toggle button and show or hide the form accordingly.

Private Sub MailHeader_ADXBeforeFormShow() Handles MyBase.ADXBeforeFormShow
    If OutlookDemo.AddinModule.CurrentInstance.RibbonButtonViewHeader.Pressed Then
        Me.RegionState = ADXRegionState.Normal
    Else
        Me.RegionState = ADXRegionState.Hidden
    End If
End Sub

This strategy is easy to accomplish because we can get to the ribbon and its controls via the AddinModule class.

Folder Path form

This custom Outlook form displays within an Inspector window for Outlook mail items only. It shows the mail items folder path and allows the user to navigate to the folder with a simple click.

ADXBeforeFormShow

This works exactly the same as with the MailHeader form.

Private Sub FolderPath_ADXBeforeFormShow() Handles MyBase.ADXBeforeFormShow
    If OutlookDemo.AddinModule.CurrentInstance.RibbonButtonGoTo.Pressed Then
        Me.RegionState = ADXRegionState.Normal
    Else
        Me.RegionState = ADXRegionState.Hidden
    End If
End Sub

Load event

This event is just what we need to retrieve the mail item’s folder path and display it in the LabelLink control. First, we need to create a private class member called FolderEntryID. This member is stores the folder’s EntryID property for later use.

Private FolderEntryID As String
 
Private Sub FolderPath_Load(sender As Object, e As EventArgs) _
    Handles MyBase.Load
 
    Dim objFolder As Outlook.MAPIFolder = Nothing
    Dim objItem As Object = Nothing
 
    FolderEntryID = String.Empty
 
    Me.FolderPathLink.Text = String.Empty
 
    Try
        objItem = Me.InspectorObj.CurrentItem
        If objItem IsNot Nothing Then
            Try
                objFolder = objItem.Parent
                If objFolder IsNot Nothing Then
                    FolderEntryID = objFolder.EntryID
                    Me.FolderPathLink.Text = GetFolderPath(objFolder)
                End If
            Finally
                Marshal.ReleaseComObject(objItem)
            End Try
        End If
    Catch
    End Try
End Sub

The method references the CurrentItem of the form’s InspectorObj property. The CurrentItem should be a MailItem but we don’t really need to attempt a cast. We only need to get the item’s Parent property. The Parent is the item’s folder object. When we have the folder object, we store its EntryID for later and call GetFolderPath to display the path in the form’s LabelLink control.

GetFolderPath method

This method returns the folder path of the passed folder object… nothing more, nothing less.

Function GetFolderPath(ByVal objFolder As Outlook.MAPIFolder) As String
    Dim strFolderPath As String
    Dim objChild As Outlook.MAPIFolder
    Dim objParent As Outlook.MAPIFolder
 
    strFolderPath = "\" + objFolder.Name
    objChild = objFolder
 
    While objChild IsNot Nothing
        Try
            objParent = objChild.Parent
        Catch
            objParent = Nothing
        Finally
            If objChild IsNot Nothing Then
                Marshal.ReleaseComObject(objChild)
            End If
        End Try
        If objParent Is Nothing Then Exit While
        strFolderPath = "\" + objParent.Name + strFolderPath
        objChild = objParent
    End While
 
    Return "\" + strFolderPath
End Function

The method starts with the passed folder and attempts to move up the “parent” parent chain to build the Outlook folder path. As long as the method continues to find a folder in the Parent property, it continues… otherwise it exists the While loop and returns the path.

FolderPathLink_LinkClicked event

This event navigates the user to the item’s parent folder in Outlook Explorer. It then activates the Explorer windows so that it moves to the foreground.

Private Sub FolderPathLink_LinkClicked( _
    sender As Object, e As Windows.Forms.LinkLabelLinkClickedEventArgs) _
    Handles FolderPathLink.LinkClicked
 
    Dim olApp As Outlook._Application = Me.OutlookAppObj
    Dim parentFolder As Outlook.MAPIFolder = Nothing
    Dim ns As Outlook._NameSpace = Nothing
    Dim activeExplorer As Outlook._Explorer = Nothing
 
    Try
        ns = olApp.GetNamespace("MAPI")
        parentFolder = ns.GetFolderFromID(FolderEntryID, Type.Missing)
        activeExplorer = olApp.ActiveExplorer()
        activeExplorer.CurrentFolder = parentFolder
        activeExplorer.Activate()
    Catch
    Finally
        If ns IsNot Nothing Then Marshal.ReleaseComObject(ns)
        If activeExplorer IsNot Nothing Then Marshal.ReleaseComObject(activeExplorer)
        If parentFolder IsNot Nothing Then Marshal.ReleaseComObject(parentFolder)
    End Try
End Sub

To make this function work, we reference the MAPI namespace and call its GetFolderFromID method. Lucky for us, we stored it early in the private class member (FolderEntryID). After we retrieve the folder, we set the CurrentFolder of the ActiveExplorer to the folder and activate it. I love it when a plan comes together.

Add the MAPI methods

The following Extended MAPI routines are required for this Outlook add-in to work correctly. They are out of the scope of today’s discussion but I recommend that you investigate the following links to learn more:

Don’t forget to paste this code into your solution!

#Region "Extended MAPI routines"
 
<StructLayout(LayoutKind.Sequential)>
Friend Structure SPropValue
    Dim ulPropTag As UInteger
    Dim dwAlignPad As UInteger
    Dim Value As Long
End Structure
 
Class MAPI
    Friend Shared S_OK As Integer = 0
    Friend Shared PR_TRANSPORT_MESSAGE_HEADERS_A As UInteger = &H7D001E
 
    <DllImport("MAPI32.DLL", CharSet:=CharSet.Ansi, _
        EntryPoint:="HrGetOneProp@12")>
    Private Shared Function HrGetOneProp32(pmp As IntPtr, _
        ulPropTag As UInteger, <Out> ByRef ppProp As IntPtr) As Integer
    End Function
 
    <DllImport("MAPI32.DLL", CharSet:=CharSet.Ansi, _
        EntryPoint:="MAPIFreeBuffer@4")>
    Private Shared Sub MAPIFreeBuffer32(lpBuffer As IntPtr)
    End Sub
 
    <DllImport("MAPI32.DLL", CharSet:=CharSet.Ansi, _
        EntryPoint:="HrGetOneProp")>
    Private Shared Function HrGetOneProp64(pmp As IntPtr, _
        ulPropTag As UInteger, <Out> ByRef ppProp As IntPtr) As Integer
    End Function
 
    <DllImport("MAPI32.DLL", CharSet:=CharSet.Ansi, _
        EntryPoint:="MAPIFreeBuffer")>
    Private Shared Sub MAPIFreeBuffer64(lpBuffer As IntPtr)
    End Sub
 
    Friend Shared Function HrGetOneProp(pmp As IntPtr, _
        ulPropTag As UInteger, <Out> ByRef ppProp As IntPtr) As Integer
        If (IntPtr.Size = 8) Then
            Return HrGetOneProp64(pmp, ulPropTag, ppProp)
        Else
            Return HrGetOneProp32(pmp, ulPropTag, ppProp)
        End If
    End Function
 
    Friend Shared Sub MAPIFreeBuffer(lpBuffer As IntPtr)
        If (IntPtr.Size = 8) Then
            MAPIFreeBuffer64(lpBuffer)
        Else
            MAPIFreeBuffer32(lpBuffer)
        End If
    End Sub
 
End Class
 
#End Region

***

Believe it or not, this is a minor amount of code that generates some major features. I bet you could put these to use today just as a simple, loyal, Outlook user. But the benefits in this sample go beyond the extended Outlook features it provides. The real benefit are in the strategies presented in how to access the custom UI elements and the Outlook items.

Available downloads:

This sample Outlook add-in was developed using Add-in Express for Office and .net:

VB.NET sample Outlook add-in

You may also be interested in:

Post a comment

Have any questions? Ask us right now!