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.
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.
- In the visual designer, select AdxRibbonGroup1 (its caption is Make Things From Email).
- Then add a ribbon combo box control (AdxRibbonComboBox) and position it at the bottom.
- For its properties, change the following:
- Name = cboTaskFolder
- Caption = Current Task Folder
- Ribbons = OutlookExplorer
Your custom Outlook ribbon should now look something like this:
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:
- In the visual designer, select the AdxOlExplorerCommandBar1 node.
- Add an ADXCommandBarComboBox to your Outlook commandbar and it position it underneath the cbbCreateTask button (it has the caption, Create Task).
- Select the newly added combo box and change the following properties:
- Name = cbcbTaskFolders
- Caption = in Folder
- OlExplorerItemTypes = Mail
- OlInspectorItemTypes = Unknown (just deselect all types)
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 http://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.
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.
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
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.
Now for a little demo. The video below shows the add-in running in Outlook 2010. It’s pretty cool and useful too!
This sample Outlook plug-in was developed using using Add-in Express for Office and .net:
How to write better Outlook add-ins
- Part 1: Outlook ribbon and command bars in a single add-in
- Part 2: How to create a task, appointment or note from an email
- Part 4: How to add custom items to Outlook main menu and context menus