Introduction
This article is intended to assist VS2008 programmers inspired to enhance Microsoft Outlook by offering an easily implemented class radically simplifying the otherwise rather tedious (and error-prone) implementation of buttons and popups in Outlook's Menu Bars.
Background
Microsoft Outlook is a rich developer platform offering ample opportunities to improve on the built-in functionality of this mainstream information management program. There are three ways to pursue this opportunity:
- Developing add-ins using VSTO
- Writing VBA code
- Developing automation executables using Visual Studio 2008
The relative merits of these three strategies are nicely discussed by E.Carter & E.Lippert [1] however with emphasis on add-in development with VSTO. The development of add-ins seems notoriously difficult: debugging is non-trivial and proper registration on the target machine tends to be fickle. Using VBA is rather limiting as it does not currently offer full access to the .NET platform. Developing automation executables with VS2008 seems to be the best of all worlds: it is a development environment we are comfortable with and provides access to the full range of debugging techniques offered by the VS2008 Integrated Development Environment. In addition, if the end-product must be an add-in, the resulting code can be relatively easily ported once it is fully debugged.
Using the Code
The code I am presenting here is intended to provide you with the framework needed to develop your own automation products. You need to add the following references to your VS2008 project:
- Microsoft Office 11.0 (or 12.0) Object Library
- Microsoft Outlook 11.0 (or 12.0) Object Library
The code is organized as a single class (OutlookMenuExtensions_Class_src.cs) which accomplishes the following essential steps:
- It checks whether the user has provided non-empty captions for a single custom menu button and/or a custom popup menu item and the associated pull-down menu items.
- It either marshals to an already running instance of Outlook or creates a new instance of Outlook (there can only be one running at a time).
- It ensures that the requested menu items are added to the "Main Menu" commandbars of the Outlook application (for both explorer and inspector instances). The associated
CommandBarButton
objects are stored in a List of type Office.CommandBarButton
.
The code instantiating the OutlookMenuExtensions
class needs to define the captions of the single menu button and/or of the popup menu and the associated menu items for both Outlook explorers and inspectors. The event wiring is best accomplished by wiring all similar buttons to a single click-event handler which then needs to differentiate between the buttons firing the event by using their Tag
property (see more details below).
I have included an implementation example (ImplementationExample_src.cs). Here is the main part of the code (I will explain the reason for the reference to the API method SetForegroundWindow
, the details behind the WaitForEvents()
method, and the commented lines referencing the LicenseGenie
class later).
public YourApplication()
{
oL.NewExplorerMenuItem_Caption = "My Explorer Button";
oL.NewExplorerMenuItem_Tag = "My Explorer Button";
oL.NewExplorerMenuPopUp_Caption = "My Explorer Popup";
oL.NewExplorerPopupMenuItem_Captions = new string[3]
{ "Explorer Item 1", "Explorer Item 2", "Explorer Item 3" };
oL.NewExplorerPopupMenuItem_Tags = new string[3]
{ "Explorer Item 1", "Explorer Item 2", "Explorer Item 3" };
oL.NewInspectorMenuItem_Caption = "My Inspector Button";
oL.NewInspectorMenuItem_Tag = "My Inspector Button";
oL.NewInspectorMenuPopUp_Caption = "My Inspector Popup";
oL.NewInspectorPopupMenuItem_Captions = new string[3]
{ "Inspector Item 1", "Inspector Item 2", "Inspector Item 3" };
oL.NewInspectorPopupMenuItem_Tags = new string[3]
{ "Inspector Item 1", "Inspector Item 2", "Inspector Item 3" };
oL.SetupMenus();
if (!oL.LaunchSuccessful) return;
oL.c_NewExplorerMenuItem[0].Click +=
new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
(Explorer_Menu_Click);
for (int i=0; i < oL.NewExplorerPopupMenuItem_Captions.Length;i++)
oL.c_NewExplorerPopUpMenuItem[i].Click +=
new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
(Explorer_Menu_Click);
oL.c_NewInspectorMenuItem[0].Click +=
new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
(Inspector_Menu_Click);
for (int i = 0; i < oL.NewInspectorPopupMenuItem_Captions.Length; i++)
oL.c_NewInspectorPopUpMenuItem[i].Click +=
new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
(Inspector_Menu_Click);
c_Outlook = oL.c_App;
WaitForEvents();
}
Your custom code is easily implemented in the click handlers. I recommend to use a separate handler for the explorer and the inspector menu controls. It is left up to you to determine whether the custom method associated with a particular menu button is appropriate for the current context. I recommend you use c_Outlook.ActiveExplorer().Caption
or c_Outlook.ActiveInspector().Caption
, respectively, to make that determination.
In my implementation example (Program.cs) I used generic click handlers which simply display a form indicating which button the user pressed. For reasons I was unable to determine, it turned out that this could NOT be properly accomplished with a call to:
MessageBox.Show("User clicked " + Ctrl.Tag)
The MessageBox
would not reliably bubble up to the top of the windows stack the first time a button was pressed after the launch of the application (it would work just fine on subsequent clicks. I remedied this by wiring the Activated
event of the custom form (named 'alert
') to the following event handler which ensured, by making use of the API function SetForegroundWindow
that the alert form always bubbles up to the top.
void alert_Activated(object sender, EventArgs e)
{
SetForegroundWindow(((Form)sender).Handle.ToInt32());
}
The code for the WaitForEvents()
method is straightforward. It simply is intended to ensure that your application listens to the events fired by the Outlook application AND that your application closes when the user closed the Outlook application (which I assumed to be the case when the Outlook application no longer shows any explorer or inspector windows; in reality Outlook can persist when e.g. if the user did not respond to all reminders).
private void WaitForEvents()
{
do
{
Application.DoEvents();
if (c_Outlook.Explorers.Count == 0 & c_Outlook.Inspectors.Count == 0)
{
MessageBox.Show("User just terminated the last Outlook session\
nand hence this Outlook Sidekick is going to close as well.");
((Microsoft.Office.Interop.Outlook._Application)c_Outlook).Quit();
break;
}
Thread.Sleep(500);
} while (true);
}
Finally, as promised, a few words about the commented lines referring to a class I am calling LicenseGenie
. Implementing this class as shown would incorporate a complete user license management framework into your application without having to write any additional code! If you find this intriguing, I invite you to find out more about LicenseGenie on SoarentComputing's web site.
An application implementing the OutlookMenuExtensions
class can be found on SoarentComputing's web site. It is called OutlookSidekick2009 and offers two helpful additions to Outlook:
- It eliminates the multi-step cut-and-paste process normally required to create calendar entries from e-mail messages containing meeting invitations from clients that don't use the meeting request features built into Outlook, and
- It intercepts outgoing e-mail messages when the user forgets to include attachments promised in the e-mail.
Points of Interest
Since I have made the code of the OutlookMenuExtensions
class available for anyone interested in studying it in more detail, I will include here just a few remarks.
Of all applications in the Microsoft Office Suite, Outlook is arguably the most complex in regard to its commandbar architecture (while Excel is arguably the least complex). Each of Outlook's explorers (e.g. Inbox, Calendar, Contacts, etc.) has its own command bars. The same holds for Outlook's inspectors (i.e. the detailed views for any of the items stored in Outlook (e.g. mail items, calendar items, contact items, etc.). The user can open a seemingly unlimited number of Outlook explorers and inspectors.
When starting on this project, I had been under the impression that there might be one master command bar for both explorers and inspectors one could extend to include custom buttons and popup menus, but it turned out that each explorer and inspector uses by default the original explorer and inspector Menu Bar which then needed to be extended for each new instance. This scenario can easily become a nightmare for the developer who is trying to extend the associated commandbars with menu buttons that remain consistently accessible. The number of CommandBarButton
controls need to scale with the number of opened explorers and inspectors. This was however readily accomplished by using Lists of type Office.CommandBarButton
for the custom menu controls. To simplify matters, I decided not to bother to delete any CommandBarButtons
that were no longer used when the user closed the explorer or inspector whose Menu Bar those buttons were associated with. This clearly is a potential memory hog, but I figured that under normal Outlook usage, this should not become an issue.
Inspite of this rather large number of CommandBarButtons
only the first single custom menu button and only the first set of the custom popup menu items need to be wired up explicitly, because the Tag
property is identical for the repeated items. In fact trying to wire up more than the first set of buttons would result in multiple firing.
I already mentioned the surprise I encountered when a MessageBox
shown by the application would not bubble up to the top of the window stack only the first time it was activated and described how I solved this nuisance. I was however unable to resolve another windows nuisance. Whenever a new inspector is launched from an open Outlook explorer, the subsequent clicking of one of the custom CommandBarButtons
has an undesired effect: once the click-event handler completes its task, Outlook returns to explorer the new inspector was launched from, but only the first time! It would have been nice to prevent this, but I eventually gave up, and am now hoping that somebody reading this might be able to suggest a solution.
I need to make one more point regarding the threading apartment of this code. It turns out that in spite of my efforts to ensure that the process instantiating OutlookMenuExtensions
uses the single-threaded apartment (STA) model by using various methods including the inserting of [STAThreadAttribute]
in front of the Main
method, the thread representing the Outlook process was using the multi-threaded apartment (MTA) model. Therefore the methods handling Outlook events could not include any methods that required the STA model (e.g. any methods of the Clipboard
class).
References
[1] "Visual Studio Tools for Office 2007: VSTO for Excel, Word and Outlook", E.Carter & E.Lippert, Addison-Wesley, 2009
[2] "Programming Applications for Microsoft Office Outlook 2007", R. Byrne & R. Gregg, Microsoft Press, 2007
History
- First revision published in June 2009