In this how-to let me explain one of the various Inspector Wrappers techniques.
An Inspector Wrapper is used to handle multiple instances of Outlook Inspector Windows.
This sample is created using Visual Studio 2010(Beta 2) and Outlook 2007.
First we open Visual Studio and create a new Outlook 2007 add-in project and name it "InspectorWrapperExplained".

Picture 1: Create an Outlook Add-in using Visual Studio 2010
What you get is a solution with a class "ThisAddIn" that has two methods named "ThisAddInStartup" and "ThisAddinShutDown". Such an add-in is created using an extension that is called VSTO (Visual Studio Tools for Office). In a VSTO-solution you will always have a static property called "Globals". Form every point in your application you can access this property and use the "safe" application object. This ensures that no security warning pops up when e.g. accessing secure properties like email addresses or automatically send out emails from the add-in. Just remember that. One of the common problems developing office add-ins is that you register for button events and the click event are working just a small amount of time – or until you open another window.
What is an Inspector Wrapper good for?
It can handle a bunch of problems:
- identify in code what window the user has clicked and interact correctly
- ensures that no event get lost
- handle memory cleanup correctly
- handle different kind of Inspectors independently
How to implement?
First add a base class called "InspectorWrapper". The base class becomes a global unique identifier property that is required to identify each of the Inspector instances. It also becomes an Inspector property that provides access to the Inspector instance and it's required for correctly handling the Inspector events.
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using System;
namespace InspectorWrapperExplained {
///
/// The base class for all InspectorWrappers
///
internal abstract class InspectorWrapper {
///
/// The unique Id the identifies an Inspector Window
///
public Guid Id { get; private set; }
///
/// The Outlook Inspector Instance
///
public Outlook.Inspector Inspector { get; private set; }
///
/// .ctor
///
/// The Outlook Inspector instance that should be handled
public InspectorWrapper(Outlook.Inspector inspector) {
Id = Guid.NewGuid();
Inspector = inspector;
}
}
}
Next we need to implement a correct handling to cleanup all used resources. When developing Office add-ins this is critical, otherwise you can get unexpected side effects. To handle this correctly define an event notification and register for the close event of the Inspector instance.
namespace InspectorWrapperExplained {
///
/// Eventhandler used to correctly clean up resources
///
/// The unique id of the Inspector instance
internal delegate void InspectorWrapperClosedEventHandler(Guid id);
///
/// The base class for all InspectorWrappers
///
internal abstract class InspectorWrapper {
///
/// Event notifier for the InspectorWrapper.Closed event.
/// Is raised when an Inspector has been closed.
///
public event InspectorWrapperClosedEventHandler Closed;
///
/// The unique Id the identifies an Inspector Window
///
public Guid Id { get; private set; }
///
/// The Outlook Inspector Instance
///
public Outlook.Inspector Inspector { get; private set; }
///
/// .ctor
///
/// The Outlook Inspector instance that should be handled
public InspectorWrapper(Outlook.Inspector inspector) {
Id = Guid.NewGuid();
Inspector = inspector;
// register for Inspector events here
((Outlook.InspectorEvents_10_Event)Inspector).Close += new Outlook.InspectorEvents_10_CloseEventHandler(Inspector_Close);
}
///
/// Eventhandler for the Inspector close event
///
private void Inspector_Close() {
// call the Close Method - the derived classes can implement cleanup code
// by overriding the Close method
Close();
// unregister Inspector events
((Outlook.InspectorEvents_10_Event)Inspector).Close -= new Outlook.InspectorEvents_10_CloseEventHandler(Inspector_Close);
// clean up resources and do a GC.Collect();
Inspector = null;
GC.Collect();
GC.WaitForPendingFinalizers();
// raise the Close event.
if (Closed != null) Closed(Id);
}
///
/// Derived classes can do a cleanup by overriding this method.
///
protected virtual void Close();
}
}
Basically the Wrapper is ready to use now. We can extend the wrapper to handle all available Inspector events and create virtual methods that can be implemented for them. This encapsulates the event handling and cleanup for the user.
namespace InspectorWrapperExplained {
///
/// Eventhandler used to correctly clean up resources
///
/// The unique id of the Inspector instance
internal delegate void InspectorWrapperClosedEventHandler(Guid id);
///
/// The base class for all InspectorWrappers
///
internal abstract class InspectorWrapper {
///
/// Event notifier for the InspectorWrapper.Closed event.
/// Is raised when an Inspector has been closed.
///
public event InspectorWrapperClosedEventHandler Closed;
///
/// The unique Id the identifies the Inspector Window
///
public Guid Id { get; private set; }
///
/// The Outlook Inspector Instance
///
public Outlook.Inspector Inspector { get; private set; }
///
/// .ctor
///
/// The Outlook Inspector instance that should be handled
public InspectorWrapper(Outlook.Inspector inspector) {
Id = Guid.NewGuid();
Inspector = inspector;
// register for Inspector events here
((Outlook.InspectorEvents_10_Event)Inspector).Close += new Outlook.InspectorEvents_10_CloseEventHandler(Inspector_Close);
((Outlook.InspectorEvents_10_Event)Inspector).Activate += new Outlook.InspectorEvents_10_ActivateEventHandler(Activate);
((Outlook.InspectorEvents_10_Event)Inspector).Deactivate += new Outlook.InspectorEvents_10_DeactivateEventHandler(Deactivate);
((Outlook.InspectorEvents_10_Event)Inspector).BeforeMaximize += new Outlook.InspectorEvents_10_BeforeMaximizeEventHandler(BeforeMaximize);
((Outlook.InspectorEvents_10_Event)Inspector).BeforeMinimize += new Outlook.InspectorEvents_10_BeforeMinimizeEventHandler(BeforeMinimize);
((Outlook.InspectorEvents_10_Event)Inspector).BeforeMove += new Outlook.InspectorEvents_10_BeforeMoveEventHandler(BeforeMove);
((Outlook.InspectorEvents_10_Event)Inspector).BeforeSize += new Outlook.InspectorEvents_10_BeforeSizeEventHandler(BeforeSize);
((Outlook.InspectorEvents_10_Event)Inspector).PageChange += new Outlook.InspectorEvents_10_PageChangeEventHandler(PageChange);
// Initialize is called to give the derived Wrappers a chance to do initialization
Initialize();
}
///
/// Eventhandler for the Inspector close event
///
private void Inspector_Close() {
// call the Close Method - the derived classes can implement cleanup code
// by overriding the Close method
Close();
// unregister Inspector events
((Outlook.InspectorEvents_10_Event)Inspector).Close -= new Outlook.InspectorEvents_10_CloseEventHandler(Inspector_Close);
((Outlook.InspectorEvents_10_Event)Inspector).Activate -= new Outlook.InspectorEvents_10_ActivateEventHandler(Activate);
((Outlook.InspectorEvents_10_Event)Inspector).Deactivate -= new Outlook.InspectorEvents_10_DeactivateEventHandler(Deactivate);
((Outlook.InspectorEvents_10_Event)Inspector).BeforeMaximize -= new Outlook.InspectorEvents_10_BeforeMaximizeEventHandler(BeforeMaximize);
((Outlook.InspectorEvents_10_Event)Inspector).BeforeMinimize -= new Outlook.InspectorEvents_10_BeforeMinimizeEventHandler(BeforeMinimize);
((Outlook.InspectorEvents_10_Event)Inspector).BeforeMove -= new Outlook.InspectorEvents_10_BeforeMoveEventHandler(BeforeMove);
((Outlook.InspectorEvents_10_Event)Inspector).BeforeSize -= new Outlook.InspectorEvents_10_BeforeSizeEventHandler(BeforeSize);
((Outlook.InspectorEvents_10_Event)Inspector).PageChange -= new Outlook.InspectorEvents_10_PageChangeEventHandler(PageChange);
// clean up resources and do a GC.Collect();
Inspector = null;
GC.Collect();
GC.WaitForPendingFinalizers();
// raise the Close event.
if (Closed != null) Closed(Id);
}
protected virtual void Initialize(){}
///
/// Method gets called when another Page of the Inspector has been selected
///
/// The active page name by reference
protected virtual void PageChange(ref string ActivePageName){}
///
/// Method gets called before the Inspetor is resized
///
/// To prevent resizing set Cancel to true
protected virtual void BeforeSize(ref bool Cancel){}
///
/// Method gets called before the Inspetor is moved around
///
/// To prevent moving set Cancel to true
protected virtual void BeforeMove(ref bool Cancel){}
///
/// Method gets called before the Inspetor is minimized
///
/// To prevent minimizing set Cancel to true
protected virtual void BeforeMinimize(ref bool Cancel){}
///
/// Method gets called before the Inspetor is maximized
///
/// To prevent maximizing set Cancel to true
protected virtual void BeforeMaximize(ref bool Cancel){}
///
/// Method gets called when the Inspector is deactivated
///
protected virtual void Deactivate(){}
///
/// Method gets called when the Inspector is activated
///
protected virtual void Activate(){}
///
/// Derived classes can do a cleanup by overriding this method.
///
protected virtual void Close(){}
}
}
Creating wrappers for different message classes
To demonstrate how to handle different Item types, we implement a wrapper that handles Appointments and one for Contact Items. Start with a ContactItemWrapper class.
namespace InspectorWrapperExplained {
internal class ContactItemWrapper : InspectorWrapper {
///
/// .ctor
///
/// The Outlook Inspector instance that should be handled
public ContactItemWrapper(Outlook.Inspector inspector)
: base(inspector) {
}
///
/// The Object instance behind the Inspector (CurrentItem)
///
public Outlook.ContactItem Item { get; private set; }
///
/// Method is called when the Wrapper has been initialized
///
protected override void Initialize() {
// Get the Item of the current Inspector
Item = (Outlook.ContactItem)Inspector.CurrentItem;
// Register for the Item events
Item.Open += new Outlook.ItemEvents_10_OpenEventHandler(Item_Open);
Item.Write += new Outlook.ItemEvents_10_WriteEventHandler(Item_Write);
}
///
/// This Method is called when the Item is saved.
///
/// When set to true, the save operation is cancelled
void Item_Write(ref bool Cancel) {
//TODO: Implement something
}
///
/// This Method is called when the Item is visible and the UI is initialized.
///
/// When you set this property to true, the Inspector is closed.
void Item_Open(ref bool Cancel) {
//TODO: Implement something
}
///
/// The Close Method is called when the Inspector has been closed.
/// Do your cleanup tasks here.
/// The UI is gone, can't access it here.
///
protected override void Close() {
// unregister events
Item.Write -= new Outlook.ItemEvents_10_WriteEventHandler(Item_Write);
Item.Open -= new Outlook.ItemEvents_10_OpenEventHandler(Item_Open);
// required, just stting to NULL may keep a reference in memory of the Garbage Collector.
Item = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
The Wrapper derives from "InspectorWrapper" and implements a ContactItem property which represents the concrete Contact Item instance. You need to verride the constructor and pass the inspector instance to the constructor of the base class. When the base class has finished initializing the virtual method Initialize is called. This method is overwritten and used by the derived class to setup the Item and register for the Item events. The same is done for Appointments.
namespace InspectorWrapperExplained {
///
/// We derive a Wrapper for each MessageClass / ItemType
///
internal class AppointmentItemWrapper : InspectorWrapper {
///
/// The Object instance behind the Inspector (CurrentItem)
///
public Outlook.AppointmentItem Item { get; private set; }
///
/// .ctor
///
/// The Outlook Inspector instance that should be handled
public AppointmentItemWrapper(Outlook.Inspector inspector)
: base(inspector) {
}
///
/// Method is called when the Wrapper has been initialized
///
protected override void Initialize() {
// Get the Item of the current Inspector
Item = (Outlook.AppointmentItem)Inspector.CurrentItem;
// Register for the Item events
Item.Open += new Outlook.ItemEvents_10_OpenEventHandler(Item_Open);
Item.Write += new Outlook.ItemEvents_10_WriteEventHandler(Item_Write);
}
///
/// This Method is called when the Item is visible and the UI is initialized.
///
/// When you set this property to true, the Inspector is closed.
void Item_Open(ref bool Cancel) {
//TODO: Implement something
}
///
/// This Method is called when the Item is saved.
///
/// When set to true, the save operation is cancelled
void Item_Write(ref bool Cancel) {
//TODO: Implement something
}
///
/// The Close Method is called when the Inspector has been closed.
/// Do your cleanup tasks here.
/// The UI is gone, can't access it here.
///
protected override void Close() {
// unregister events
Item.Write -= new Outlook.ItemEvents_10_WriteEventHandler(Item_Write);
Item.Open -= new Outlook.ItemEvents_10_OpenEventHandler(Item_Open);
// Release references to COM objects
Item = null;
// required, just stting to NULL may keep a reference in memory of the Garbage Collector.
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
Here is the related class diagram:

Picture 2: Class Diagramm of the InspectorWrapper
To determine the correct InspectorWrapper for a specific MessageClass a factory method needs to be created.
You can put it in an extra class or add it to the InspectoWrapper class. This class just checks the message class property of the inspector.CurrentItem instance and returns a specific Wrapper or null if the message class isn't handled.
///
/// This Fabric method returns a specific InspectorWrapper or null if not handled.
///
/// The Outlook Inspector instance
/// Returns the specific Wrapper or null
public static InspectorWrapper GetWrapperFor(Outlook.Inspector inspector) {
// retrieve the message class using late binding
string messageClass = inspector.CurrentItem.GetType().InvokeMember("MessageClass", BindingFlags.GetProperty, null, inspector.CurrentItem, null);
switch (messageClass) {
case "IPM.Contact":
return new ContactItemWrapper(inspector);
case "IPM.Appintment":
return new AppointmentItemWrapper(inspector);
}
return null;
}
How to use the Wrapper?
First we need to create the infrastructure to handle the new and already opened Inspectors. In the "ThisAddIn" class, we declare a property that holds a reference to the "Application.Inspectors" collection. We set this property in the "ThisAddin_StartUp" method and register for the "NewInspector" event. When the event occurs we want to wrap this Inspector – so we define a Method "WrapInspector" that takes an Inspector as argument. We can connect the "NewInspector" event directly with that method. We also need to take care of already visible Inspectors after Outlook has been started. When the "NewInspector" event is fired we try to create a wrapper for the Inspector. When we created a Wrapper successfully we need to register for the close event of the Inspector and remember it in memory. When the inspector has been closed we remove it from memory.
Here is the implementation of the "ThisAddin" class:
using System;
using System.Collections.Generic;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
namespace InspectorWrapperExplained
{
public partial class ThisAddIn
{
///
/// Holds a reference to the Application.Inspectors collection
/// Required to get notifications for NewInspector events.
///
private Outlook.Inspectors _inspectors;
///
/// A dictionary that holds a reference to the Inspectors handled by the add-in
///
private Dictionary<Guid, InspectorWrapper> _wrappedInspectors;
///
/// Startup method is called when the add-in is loaded by Outlook
///
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
_wrappedInspectors = new Dictionary<Guid, InspectorWrapper>();
_inspectors = Globals.ThisAddIn.Application.Inspectors;
_inspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(WrapInspector);
// Handle also already existing Inspectors
// (e.g. Double clicking a .msg file)
foreach (Outlook.Inspector inspector in _inspectors) {
WrapInspector(inspector);
}
}
///
/// Wraps an Inspector if required and remember it in memory to get events of the wrapped Inspector
///
/// The Outlook Inspector instance
void WrapInspector(Outlook.Inspector inspector) {
InspectorWrapper wrapper = InspectorWrapper.GetWrapperFor(inspector);
if (wrapper != null) {
// register for the closed event
wrapper.Closed += new InspectorWrapperClosedEventHandler(wrapper_Closed);
// remember the inspector in memory
_wrappedInspectors[wrapper.Id] = wrapper;
}
}
///
/// Method is called when an inspector has been closed
/// Removes reference from memory
///
/// The unique id of the closed inspector
void wrapper_Closed(Guid id) {
_wrappedInspectors.Remove(id);
}
///
/// Shutdown method is called when Outlook is unloading the add-in
///
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
// do the homework and cleanup
_wrappedInspectors.Clear();
_inspectors.NewInspector -=new Outlook.InspectorsEvents_NewInspectorEventHandler(WrapInspector);
_inspectors = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
#region VSTO generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}
Conclusion
The InspectorWrapper template makes it easy to handle multiple windows in an Outlook add-in.
With minimal effort you can handle different Outlook Items types independently. It ensures that events are handled correctly and it can help you prevent memory leaks with Inspector objects.