Ty Anderson

How to develop Outlook 2010, 2007, 2003 add-in: Outlook objects and events

We’re making our way through this short series covering Outlook add-in development. If you missed the previous parts (Part 1: Customize Outlook Ribbon & command bars in a single add-in and Part 2: How to work with Outlook Items), I highly recommend them, not just because I wrote. No no… because they are essential to understanding this edition, Part 3. If you try to read today’s edition without the context of the previous two, you’ll be entertained but you will not get everything out of it that I intend for you.

I have your best interests at heart. I really do. Okay, from here on out, I’ll assume you are up-to-speed and I’ll just get on with it. I’ve got some really cool stuff for you today.

A new requirement – ability to select the task folder to create tasks

I like how an idea can evolve. An idea almost never remains static as it is developed and transformed from a “back of the napkin” concept into a tried and true, useful plugin for Outlook 2010, 2007, 2003 and lower versions. The Ty Actions Outlook add-in is no exception. Not long after completing Part 2, I started thinking of how I use Outlook to manage tasks.

I will not bore you with the details… just know that I use a lot of task folders in my Outlook. Important projects have their own folder. Sometimes I make a folder to handle a category of tasks I don’t want to do. My Admin tasks folder is a good example. I can store a task there and successfully hide it from view where I can forget about it. But I digress…

Today we have new requirement:

  • Add the ability to select the task folder to create tasks.

This feature will allow the user to select the destination tasks folder for any Task item created using our custom toolbar. This feature does not apply to Outlook appointment and note items but it is easy enough to add if you choose to do it on your own.

Sketch: ability to select the task folder to create tasks

The sketch above shows how we will implement this new requirement. We’ll add a combo box control to the add-in’s custom ribbon and custom commandbar. This combo box contains the names of all Outlook tasks folders. So, we will have to add the code that keeps the combo box filled with folder names. This means we will need to track task folders and respond to any changes to them (e.g. adding, deleting, and renaming task folders).

Let’s get to work.

Modifying Outlook toolbars (ribbon and commandbar)

We’ll begin by modifying the custom ribbon and commandbar. I’ll assume you have your TyActions project open in Visual Studio (you can download it at the end of Part 2). Open the AddInModule in design view then select the AdxRibbonTab1 object and complete the following steps.

  1. In the visual designer, select AdxRibbonGroup1 (its caption is Make Things From Email).
  2. Then add a ribbon combo box control (AdxRibbonComboBox) and position it at the bottom.
  3. For its properties, change the following:
    1. Name = cboTaskFolder
    2. Caption = Current Task Folder
    3. Ribbons = OutlookExplorer

Your custom Outlook ribbon should now look something like this:
Custom Outlook ribbon design

That’s it for the Ribbon. Now let’s tackle the add-in’s command bar.

On the AddInModule (in design view), select the AdxOlExplorerCommandBar1 object and complete the following actions:

  1. In the visual designer, select the AdxOlExplorerCommandBar1 node.
  2. Add an ADXCommandBarComboBox to your Outlook commandbar and it position it underneath the cbbCreateTask button (it has the caption, Create Task).
  3. Select the newly added combo box and change the following properties:
    1. Name = cbcbTaskFolders
    2. Caption = in Folder
    3. OlExplorerItemTypes = Mail
    4. OlInspectorItemTypes = Unknown (just deselect all types)

Custom Outlook command bar design

When done, your custom Outlook command bar should look just like the one above. If you deviated a little, yours should at least resemble. Hopefully, you at least used the same names I provided because the code depends on it.

Coding the business logic

I don’t believe this will take too long. The main thing we need to do is write the code for saving a task to the specified folder. The “specified folder” being the one the user selects in the combo box we just created. Also, we need to track changes to the task folders residing under the Outlook Task folder.

Moving items to a specified Outlook folder

This is the main method which allows us to save a task in the folder of the user’s preference. The method requires a TaskItem and the destination folder name as parameters. There is a third parameter that allows us to specify whether or not to display the task when all is said and done.

Add the following code to the AddInModule:

Private Sub MoveItemToFolder(ByRef outlookItem As Outlook.TaskItem, _
	ByVal folderName As String, ByVal displayAfterMove As Boolean)
 
    Dim targetFolder As Outlook.Folder
 
    'Check that the default Task folder is NOT selected before attempting a move
    Dim defaultTasks As String = GetDefaultTasksFolderName()
 
    'Find the Root Folder's path value
    Dim ns As Outlook.NameSpace = OutlookApp.Session
    Dim inboxFolder As Outlook.Folder = ns.GetDefaultFolder( _
		Outlook.OlDefaultFolders.olFolderInbox)
    Dim rootFolder As Outlook.Folder = inboxFolder.Parent
    'Set the Path to the root + the Tasks folder path
    Dim rootPath As String = rootFolder.FolderPath & "\" & defaultTasks & "\"
 
    Marshal.ReleaseComObject(ns)
    Marshal.ReleaseComObject(inboxFolder)
    Marshal.ReleaseComObject(rootFolder)
 
    If folderName <> defaultTasks Then
      targetFolder = GetFolder(rootPath & folderName)
      Dim movedItem As Outlook.TaskItem = outlookItem.Move(targetFolder)
      movedItem.Save()
      If displayAfterMove Then movedItem.Display()
      Marshal.ReleaseComObject(targetFolder)
      Marshal.ReleaseComObject(movedItem)
    End If
End Sub

The method begins by finding the root folder name of the user’s Outlook data store as well as the name of the default tasks folder (via a call to GetDefaultTasksFolderName which is explained later). You might or might not know this, but the root folder name varies depending on the version of Outlook. We can’t rely on it to be the same. Plus, hard coding is bad.

After we know the name of the root folder, we can build a path to passed folderName; combining it with the root folder, and the default tasks folder. If the folder name is not the default tasks folder, we know we need to take some action to save the newly created task to a different folder. Pay attention because there is a trick here.

Outlook does not provide a “Save As” method to save items into Outlook folders. No, no, no! That would be too easy. Instead, what we must do is use the Move method. The trick is to save the task into the default task folder, then move the task to the desired folder and save it there. After moving and saving, the method checks if we want to display the task (displayAfterMove).

Getting the default tasks folder’s name

The default Tasks folder is always named “Tasks”… as long as you use an English version of Outlook. Given there are over 1 billion people using Office, it is a fair bet that you will have user running Outlook in other languages. Therefore, we need to retrieve the name of the default tasks folder using the following code:

Private Function GetDefaultTasksFolderName() As String
    Dim ns As Outlook.NameSpace = OutlookApp.Session
    Dim defaultTaskFolder As Outlook.Folder =   
      ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderTasks)
    Dim defaultTaskFolderName As String = defaultTaskFolder.Name
    Marshal.ReleaseComObject(ns)
    Marshal.ReleaseComObject(defaultTaskFolder)
    Return defaultTaskFolderName
End Function

This method uses the Outlook Session object to retrieve the default Tasks folder. Once retrieved, it returns the folder name as the function’s value.

Getting an Outlook folder by its full path

This is a handy method I use all the time. It allows us to specify a folder path and receive the folder as an Outlook Folder object. I have no idea why this method is not a standard feature of the Outlook API. Despite my ponderings, it isn’t. Go ahead and add the following code to the AddInModule.

Private Function GetFolder(ByVal folderPath As String) As Outlook.Folder
    ''From https://msdn.microsoft.com/en-us/library/bb756875(v=office.14).aspx
 
    Dim returnFolder As Outlook.Folder = Nothing
    Try
      ' Remove leading "\" characters.
      folderPath = folderPath.TrimStart("\".ToCharArray())
 
      ' Split the folder path into individual folder names.
      Dim folders As String() = folderPath.Split("\".ToCharArray())
 
      ' Retrieve a reference to the root folder.
      Dim ns As Outlook.NameSpace = OutlookApp.Session
      Dim olFolders As Outlook.Folders = ns.Folders
      returnFolder = TryCast(olFolders(folders(0)), Outlook.Folder)
      Marshal.ReleaseComObject(ns)
      Marshal.ReleaseComObject(olFolders)
 
      ' If the root folder exists, look in subfolders.
      If returnFolder IsNot Nothing Then
        Dim subFolders As Outlook.Folders = Nothing
        Dim folderName As String
 
        ' Look through folder names, skipping the first folder, 
        ' which you already retrieved.
        For i As Integer = 1 To folders.Length - 1
          folderName = folders(i)
          subFolders = returnFolder.Folders
          returnFolder = TryCast(subFolders(folderName), Outlook.Folder)
          Marshal.ReleaseComObject(subFolders)
        Next
      End If
    Catch ex As Exception
      ' Do nothing at all -- just return a null reference.
      returnFolder = Nothing
    End Try
    Return returnFolder
End Function

I didn’t write it. I confess I lifted it straight off MSDN. Which, you probably already noticed as I said as much in the code comments. I’m not going explain it further as there is plenty of explanation on MSDN if you are interested.

Adding custom items to the Ribbon combo box

At various times we need to create items in the Ribbon’s combo box. We’ll get to the times we need to do this. For now, we’ll focus on the method that does it. This method requires a folder name and folder path to be passed as strings. The method uses these values to create ADXRibbonItems, which we then add to the cboTaskFolder combo box residing on our Outlook ribbon.

Private Sub CreateRibbonComboItem(ByVal folderName As String, _
	ByVal folderPath As String)
 
  Dim adxRibbonItem As AddinExpress.MSO.ADXRibbonItem = _
	New AddinExpress.MSO.ADXRibbonItem()
  adxRibbonItem.Caption = folderName
  adxRibbonItem.Id = folderPath
  adxRibbonItem.ImageTransparentColor = System.Drawing.Color.Transparent
  cboTaskFolder.Items.Add(adxRibbonItem)
End Sub

The caption is the folder name as you might expect. I’m also storing the folder path in the Id property.

Adding task folders’ names to the Ribbon dropdown

This method’s job is to find the default task folder, loop through its folder collection, and call CreateRibbonComboItem for each folder found.

Friend Sub LoadTaskFoldersToRibbonDropDown()
    ' Clear the items collection
    cboTaskFolder.Items.Clear()
 
    Dim ns As Outlook.NameSpace = OutlookApp.Session
    If ns IsNot Nothing Then
 
      Dim fldr As Outlook.Folder = ns.GetDefaultFolder( _
		Outlook.OlDefaultFolders.olFolderTasks)
      If fldr IsNot Nothing Then
 
        'Add Default Folder to combo
        CreateRibbonComboItem(fldr.Name, fldr.FolderPath)
 
        Dim fldrs As Outlook.Folders = fldr.Folders
        If fldrs IsNot Nothing Then
 
          'Now add all sub folders...we'll assume they are task folders for the demo.
          For i = 1 To fldrs.Count
            Dim subFolder As Outlook.Folder = fldrs(i)
            CreateRibbonComboItem(subFolder.Name, subFolder.FolderPath)
            Marshal.ReleaseComObject(subFolder)
          Next
 
          Marshal.ReleaseComObject(fldrs)
        End If
 
        Marshal.ReleaseComObject(fldr)
      End If
 
      Marshal.ReleaseComObject(ns)
    End If
End Sub

It begins by clearing the ribbon combo box. Then the method finds the default task folder and calls CreateRibbonComboItem and passes the tasks folder name and path. This action ensures the default tasks folder is the first item in the combo box.

The method then loops through the tasks folder collection and finishes by cleaning up after itself.

Adding task folders’ names to the commandbar combo box

This method works like LoadTaskFoldersToRibbonDropDown except it calls the CreateCommandBarComboItem to add items to the command bar’s combo box. Other than this, there isn’t much difference between the two.

Friend Sub LoadTaskFoldersToComboBox()
    ' Clear the items collection
    cbcbTaskFolders.Items.Clear()
 
    Dim ns As Outlook.NameSpace = OutlookApp.Session
    If ns IsNot Nothing Then
 
      Dim fldr As Outlook.Folder = ns.GetDefaultFolder( _
		Outlook.OlDefaultFolders.olFolderTasks)
      If fldr IsNot Nothing Then
 
        'Add Default Folder to combo
        CreateCommandBarComboItem(fldr.Name)
 
        Dim fldrs As Outlook.Folders = fldr.Folders
        If fldrs IsNot Nothing Then
 
          'Now add all sub folders...we'll assume they are task folders for the demo.
          For i = 1 To fldrs.Count
            Dim subFolder As Outlook.Folder = fldrs(i)
            CreateCommandBarComboItem(subFolder.Name)
            Marshal.ReleaseComObject(subFolder)
          Next
 
          Marshal.ReleaseComObject(fldrs)
        End If
 
        Marshal.ReleaseComObject(fldr)
      End If
 
      Marshal.ReleaseComObject(ns)
    End If
End Sub

Adding a new item to the Outlook command bar’s combo box

This method is super simple. It uses the passed string to add a new item to the command bar’s combo box.

Private Sub CreateCommandBarComboItem(ByVal folderName As String)
  cbcbTaskFolders.Items.Add(folderName)
End Sub

I think you could make a case for combining CreateCommandBarComboItem with CreateRibbonComboItem. Same for LoadFoldersToRibbonDropDown and LoadTaskFoldersToComboBox. I left them separate because I think it is easy to follow the differences in how the objects work.

Updating CreateLinkedItem

Due to the new requirement, we need to change CreateLinkedItem to utilize the user’s preferred task folder of the moment. I have included the updated CreateLinkedItem below. You can see the required changes in the highlighted portion of the method.

Private Sub CreateLinkedItem(ByVal itemType As Outlook.OlItemType)
    Dim explorer As Outlook.Explorer = DirectCast(OutlookApp.ActiveExplorer, _
		Outlook.Explorer)
    Dim sel As Outlook.Selection = Nothing
    Try
      sel = explorer.Selection
    Catch
    End Try
 
    If sel IsNot Nothing Then
      If sel.Count > 0 Then
        Dim email As Outlook.MailItem = TryCast(sel.Item(1), Outlook.MailItem)
        If email IsNot Nothing Then
          Select Case itemType
            Case Outlook.OlItemType.olAppointmentItem
              Dim olItem As Outlook.AppointmentItem = TryCast( _
				OutlookApp.CreateItem(itemType), Outlook.AppointmentItem)
              olItem.Subject = "Appt: " & email.Subject
              olItem.Body = email.Body
              AttachSelections(olItem, sel)
              olItem.Display()
              Marshal.ReleaseComObject(olItem)
 
            Case Outlook.OlItemType.olTaskItem
              Dim olItem As Outlook.TaskItem = TryCast( _
				OutlookApp.CreateItem(itemType), Outlook.TaskItem)
              olItem.Subject = "Task: " & email.Subject
              olItem.Body = email.Body
              olItem.ContactNames = email.ReceivedByName
              AttachSelections(olItem, sel)
 
              '==========PART 3=======:::::
              olItem.Save()
              'Get name of default folder
              Dim defaultTaskFolderName As String = GetDefaultTasksFolderName()
              If Me.HostMajorVersion >= 14 Then
                If cboTaskFolder.Text = "" Or _
					cboTaskFolder.Text = defaultTaskFolderName Then
                  olItem.Display()
                Else
                  'Don't want to attempt a move if the user wants it saved to 
				  'the default folder
                  MoveItemToFolder(olItem, cboTaskFolder.Text, True)
                End If
              Else
                If cbcbTaskFolders.Text = "" Or _
					cbcbTaskFolders.Text = defaultTaskFolderName Then
                  olItem.Display()
                Else
                  'ditto...see above.
                  MoveItemToFolder(olItem, cbcbTaskFolders.Text, True)
                End If
              End If
 
              '/===========PART 3=======:::::
              Marshal.ReleaseComObject(olItem)
 
            Case Outlook.OlItemType.olNoteItem
              Dim olItem As Outlook.NoteItem = TryCast( _
				OutlookApp.CreateItem(itemType), Outlook.NoteItem)
              olItem.Body = email.Subject & vbCrLf & email.Body
              olItem.Display()
              Marshal.ReleaseComObject(olItem)
 
          End Select
          Marshal.ReleaseComObject(email)
        End If
      End If
      Marshal.ReleaseComObject(sel)
    End If
    Marshal.ReleaseComObject(explorer)
End Sub

For a task item, the first thing we do is save it. This places the item in the default tasks folder. We then check the Outlook version number to determine if we should check the value of the ribbon’s combo box or that of the command bar’s combo box. Either way, we check the combo box value to determine if the user wants to save the task to the default folder. If so, we simply display it and stop. If a non-default folder is selected, we call the MoveToFolder method.

We have completed our business logic. We can wrap this up by adding code to some Outlook item events.

Responding to Outlook folder events

Within, the Add-in Express for Office and .net framework, the best practice for responding to folder events is to use our Outlook Folders Events Class. This class is a special Visual Studio item template that allows you to easily handle events for an Outlook folders collection.

Adding an Outlook Folders Events Class

Go ahead and add one of these special classes to your TyActions project via the Project > Add New Item… Visual Studio menu option. Within the Add New Item dialog, select the Outlook Folders Events class. Specify the TaskFoldersEventsClass.vb as the file name and click the Add button.

Adding an Outlook Folders Events Class

The FolderAdd Event

This event calls the AddinModule’s RefreshFolderLists method and passes a Folder object to it. The Folder will be the one the one the user just added.

Public Overrides Sub ProcessFolderAdd(ByVal Folder As Object)
    AddinModule.CurrentInstance.RefreshFolderLists(Folder)
End Sub

The FolderChange Event

The FolderChange event calls the RefreshFolderLists method and passes the changed Folder object to it.

Public Overrides Sub ProcessFolderChange(ByVal Folder As Object)
    AddinModule.CurrentInstance.RefreshFolderLists(Folder)
End Sub

The Folder Remove Event

This FolderRemove event does not call the RefreshFolderLists method. Instead it checks the Outlook version number and calls either LoadTaskFoldersToRibbonDropDown or LoadTaskFoldersToComboBox to refresh the folder list.

Public Overrides Sub ProcessFolderRemove()
    If AddinModule.CurrentInstance.HostMajorVersion >= 14 Then
      AddinModule.CurrentInstance.LoadTaskFoldersToRibbonDropDown()
    Else
      AddinModule.CurrentInstance.LoadTaskFoldersToComboBox()
    End If
End Sub

Adding RefreshFolderLists

This method requires a folder object as a parameter. This object is the item that has been added or changed in some manner. Open the AddInModule in code view and add the following code:

Friend Sub RefreshFolderLists(ByVal Folder As Outlook.MAPIFolder)
    Dim fldrtype As Outlook.OlItemType = Folder.DefaultItemType
 
    If fldrtype = Outlook.OlItemType.olTaskItem Then
      If Me.HostMajorVersion >= 14 Then
        LoadTaskFoldersToRibbonDropDown()
      Else
        LoadTaskFoldersToComboBox()
      End If
    End If
End Sub

If the folder is a task folder, we know we need to add it to a combo box. We check the Outlook version to determine which one.

Capture folder events

With AddinModule still open in code view, let’s add the code for the events of our default tasks folders collection. We’ll create a member variable, named fldrs that is a TaskFoldersEventsClass object. We will use this variable to store a reference to the default task folder’s folder collection.

Add this line (preferably at the top of the class).

Dim fldrs As TaskFoldersEventsClass

There is one other step related to the fldrs and we will cover it next.

Connecting methods to Outlook events

We’re almost done. We need only to edit the AddInInitialize event and to add the OnRibbonBeforeLoad event. These events will ensure our Outlook plugin is ready to go when the user sees it.

Editing the AddInInitialize event

Due to all our changes, this event needs to store the default tasks folder’s folder collection in to the fldrs variable. We also need to load items into our command bar combo box by calling LoadTaskFoldersToComboBox and setting its default value to the default task folder name. In this case, hard-coding is okay. The default task folder’s name is always the same.

Private Sub AddinModule_AddinInitialize(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles MyBase.AddinInitialize
 
    ''NEW FOR PART 3
    'Grab Task folders so we can monitor changes
    Dim ns As Outlook.NameSpace = OutlookApp.Session
    Dim tasksFolder As Outlook.Folder = ns.GetDefaultFolder( _
		Outlook.OlDefaultFolders.olFolderTasks)
    fldrs = New TaskFoldersEventsClass(Me)
    fldrs.ConnectTo(tasksFolder, True)
    Marshal.ReleaseComObject(ns)
    '''
 
    If Me.HostMajorVersion >= 14 Then
      AdxOlExplorerCommandBar1.UseForRibbon = False
    Else
      ''NEW FOR PART 3
      LoadTaskFoldersToComboBox()
      cbcbTaskFolders.Text = GetDefaultTasksFolderName()
    End If
 
End Sub

The main action is found in the call to GetDefaultFolder where we retrieve the default tasks folders. We then create a new TasksFoldersEventClass and pass the task folder as a parameter. This action creates a new TasksFoldersEventClass that is attached to folder collection of the default tasks folder.

Adding the OnRibbonBeforeLoad event

This event is the place to add items to the ribbon combo box by calling LoadTaskFoldersToRibbonDropDown. It is also the place to set the combo box’s text to the default tasks folder name.

Private Sub AddinModule_OnRibbonBeforeLoad(ByVal sender As Object, _
	ByVal e As AddinExpress.MSO.ADXRibbonBeforeLoadEventArgs) _
	Handles Me.OnRibbonBeforeLoad
 
    LoadTaskFoldersToRibbonDropDown()
    cboTaskFolder.Text = GetDefaultTasksFolderName()
 
End Sub

Adding the AddInFinalize event

When the add-in shuts down, we need to remove our reference to the folder collection. This is a trivial task as you see below.

Private Sub AddinModule_AddinFinalize(sender As System.Object, _
	e As System.EventArgs) Handles MyBase.AddinFinalize
 
	fldrs.Dispose()
End Sub

Just add this code to the AddinModule and we’re done.

Demo time

Now for a little demo. The video below shows the add-in running in Outlook 2010. It’s pretty cool and useful too!

Available downloads:

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

VB.NET – TyActions Outlook Add-in

How to write better Outlook add-ins

You may also be interested in:

2 Comments

Post a comment

Have any questions? Ask us right now!