Writing the Outlook add-in described in my previous post was composed of the following steps:
1) Creating the project
Ah, the wonders of VSTO. Saving us all the unnecessary hassle of adding COM references, implementing interfaces and working too hard. After installing VSTO 2005 SE we can create a new Outlook 2007 Add-in project that creates both the add-in project and the MSI Setup project that can be used to install it.
2) Building the Context Menu
To add my own menu items to each item's Context Menu, I use the new ItemContextMenuDisplay event that is exposed by the Outlook 2007 object model. In the pre-supplied Startup method I simply add the following event registration:
this.Application.ItemContextMenuDisplay += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemContextMenuDisplayEventHandler(Application_ItemContextMenuDisplay);
In my event handler method I start by fetching the current Outlook folder:
MAPIFolder currentFolder = Application.ActiveExplorer().CurrentFolder;
and creating the root of the popup menu tree:
// Create a new button in the commandBar. The command bar itself is received as a parameter to the event
// handler method.
CommandBarPopup rootButton = (CommandBarPopup)commandBar.Controls.Add
(Office.MsoControlType.msoControlPopup, Type.Missing, "MoveToFolder", commandBar.Controls.Count + 1, Type.Missing);
rootButton.BeginGroup = true;
rootButton.Tag = "MoveToSubFolder";
rootButton.Caption = "Move To...";
rootButton.Visible = true;
Now I recursively go over the current folder and its subfolders, adding new buttons under the rootButton as I go along.
Things to note:
- I create a different menu control depending on whether I'm creating a leaf node or a node that has children - one is a Button, the other a Popup. For some reason I couldn't create a PopupButton, maybe it's a control that's reserved for a toolbar and can't be used in a ContextMenu. This means that a node with children cannot be clicked, and we can't copy items to it. There may be a workaround to this using the OnAction property rather than the Click event, but I haven't gotten around to checking it out. I'd appreciate feedback.
- I'm saving the FullFolderPath in each node's Tag object. This is so I can access it later when I'm moving the items. I originally wanted to save the EntryID + Store's EntryID in the Tag for a totally unique identifier, but for some reason Outlook crashed whenever I tried to save that in it. I have no idea why.
- Note the recursion - each folder is addings its leaf-nodes as buttons, and sending each non-leaf children to another call to AddFolder.
foreach (MAPIFolder subFolder in folder.Folders)
MsoControlType linkType = (subFolder.Folders.Count > 0) ?
MsoControlType.msoControlPopup : MsoControlType.msoControlButton;
CommandBarControl folderLink = menu.Controls.Add
(linkType, Type.Missing, subFolder.Name, 1, Type.Missing);
folderLink.Tag = subFolder.FullFolderPath;
folderLink.Caption = subFolder.Name;
if (linkType == MsoControlType.msoControlPopup)
AddFolder (subFolder, folderLink as CommandBarPopup);
(folderLink as CommandBarButton).Click += new
3) Moving the items
As we could see above, each leaf node's Click event is handled by the MoveToFolder method:
- The FullFolderPath we saved earlier is translated to a MAPIFolder object using the GetFolderFromPath function, a glaring omission in the Outlook object model that we have to rectify by hand. I'll post the full (and simply) code for this in a different post. It's included in the attached project.
- Note that we're casting the items in the current selection to MailItem objects, meaning standard outlook email messages. We're ignoring non-MailItems in this code. Since the COM object model doesn't give us any shared base-classes or interfaces for other entities, we need to write some ugly code to handle PostItems, AppointmentItems and so forth.
void MoveToFolder(CommandBarButton Ctrl, ref bool CancelDefault)
MAPIFolder destFolder = GetFolderFromPath(Ctrl.Tag);
foreach (object item in Application.ActiveExplorer().Selection)
MailItem mi = item as MailItem;
if (mi != null)
// Can only copy MailItems;
Well, maybe not. But this is the entire extent of the code needed to implement this add-in. I've attached the whole project (including the MSI project) so you can install it at home, or use the code as a base for whatever add-in you feel like writing. The pre-built MSI is in the MoveToFolderAddInSetup\Debug folder. Enjoy!