Export (0) Print
Expand All

Developing an Inspector Wrapper for Outlook 2010

Office 2010

Summary: This article discusses a technique to implement an inspector wrapper. An inspector wrapper handles multiple instances of Microsoft Outlook 2010 inspector windows.

A Microsoft Outlook 2010 inspector wrapper can provide the following solutions:

  • Identify in code the window that the user clicked.

  • Ensure that no events are lost.

  • Handle memory cleanup correctly.

  • Independently handle Inspector objects for different Outlook item types.

The example code in this article demonstrates the preceding solutions. To download the full code sample, in both C# and Visual Basic, see Outlook 2010: Developing an Inspector Wrapper in the MSDN Samples Gallery. The code sample was created for Visual Studio 2010 and Outlook 2010.

Open Visual Studio and create a new Outlook 2010 Add-in project and name it InspectorWrapperExplained.

The add-in is a solution with a ThisAddIn class that has two methods, named Startup and ShutDown. Microsoft Office add-ins can be created by using an extension for Microsoft Office development tools in Microsoft Visual Studio. Office add-ins that are created with Visual Studio 2010 always contain a static property called Globals.

Important note Important

From every point in your add-in, you can access the Globals property and should derive all Outlook objects from the safe Application object specified by the Globals.ThisAddin.Application object. Using this Application object ensures that no security warning will occur when your code sends an email or accesses protected properties, such as email addresses.

One of the common problems with developing Office add-ins is that when you register button and click events, they work only for a small period of time, or until you open another window. Another common problem is that when you access Outlook objects by using managed code, there can be side effects from memory management and garbage collection. The following code examples show how you can use an inspector wrapper to solve these problems.

Now, add a base class called InspectorWrapper, as shown in the following code example. This base class has a globally unique identifier (GUID) property that is required to identify each of the Inspector instances. It also has an Inspector property that provides access to the Inspector instance, and is required for the correct handling of Inspector events.

using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using System;

namespace InspectorWrapperExplained {

    // The base class for all inspector wrappers.
    internal abstract class InspectorWrapper {

        // The unique ID that identifies an inspector window.
        public Guid Id { get; private set; }

        // The Outlook Inspector instance.
        public Outlook.Inspector Inspector { get; private set; }

        // .ctor
        // <param name="inspector">The Outlook Inspector instance that should be handled.</param>
        public InspectorWrapper(Outlook.Inspector inspector) {
            Id = Guid.NewGuid();
            Inspector = inspector;
        }
    }
}

Next, implement a handler to clean up all used resources. This is critical when you develop Office add-ins; otherwise, you can get unexpected side effects. To handle this correctly, define an event notification and register the Close event of the Inspector class.

namespace InspectorWrapperExplained {        
    
    // Eventhandler used to correctly clean up resources.
    // <param name="id">The unique id of the Inspector instance.</param>
    internal delegate void InspectorWrapperClosedEventHandler(Guid id);

    // The base class for all inspector wrappers.
    internal abstract class InspectorWrapper {

        // Event notification for the InspectorWrapper.Closed event.
        // This event is raised when an inspector has been closed.
        public event InspectorWrapperClosedEventHandler Closed;
        
        // The unique ID that identifies an inspector window.
        public Guid Id { get; private set; }

        // The Outlook Inspector instance.
        public Outlook.Inspector Inspector { get; private set; }

        // .ctor
        // <param name="inspector">The Outlook Inspector instance that should be handled.</param>
        public InspectorWrapper(Outlook.Inspector inspector) {
            Id = Guid.NewGuid();
            Inspector = inspector;
            // Register Inspector events here.
            ((Outlook.InspectorEvents_10_Event)Inspector).Close += 
                new Outlook.InspectorEvents_10_CloseEventHandler(Inspector_Close);
        }

        // Event handler 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 now ready for use. However, you must extend the wrapper to handle all available Inspector events and create virtual methods that can be implemented, as shown in the following code example. This encapsulates the event handling and memory cleanup for the user.

namespace InspectorWrapperExplained {        
    
    // Event handler used to correctly clean up resources.
    // <param name="id">The unique id of the Inspector instance.</param>
    internal delegate void InspectorWrapperClosedEventHandler(Guid id);

    // The base class for all inspector wrappers.
    internal abstract class InspectorWrapper {

        // Event notification for the InspectorWrapper.Closed event.
        // This event is raised when an inspector has been closed.
        public event InspectorWrapperClosedEventHandler Closed;
        
        // The unique ID that identifies the inspector window.
        public Guid Id { get; private set; }

        // The Outlook Inspector instance.
        public Outlook.Inspector Inspector { get; private set; }

        // .ctor
        // <param name="inspector">The Outlook Inspector instance that should be handled.</param>
        public InspectorWrapper(Outlook.Inspector inspector) {
            Id = Guid.NewGuid();
            Inspector = inspector;
            // Register 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.
            Initialize();
        }
        // Event handler 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 is called when another page of the inspector has been selected.
        // <param name="ActivePageName">The active page name by reference.</param>
        protected virtual void PageChange(ref string ActivePageName){}

        // Method is called before the inspector is resized.
        // <param name="Cancel">To prevent resizing, set Cancel to true.</param>
        protected virtual void BeforeSize(ref bool Cancel){}

        // Method is called before the inspector is moved around.
        // <param name="Cancel">To prevent moving, set Cancel to true.</param>
        protected virtual void BeforeMove(ref bool Cancel){}

        // Method is called before the inspector is minimized.
        // <param name="Cancel">To prevent minimizing, set Cancel to true.</param>
        protected virtual void BeforeMinimize(ref bool Cancel){}

        // Method is called before the inspector is maximized.
        // <param name="Cancel">To prevent maximizing, set Cancel to true.</param>
        protected virtual void BeforeMaximize(ref bool Cancel){}

        // Method is called when the inspector is deactivated.
        protected virtual void Deactivate(){}

        // Method is called when the inspector is activated.
        protected virtual void Activate(){}

        // Derived classes can do a cleanup by overriding this method.
        protected virtual void Close(){}
    }
}

In Outlook, the different item types are identified by a property called MessageClass. For more information about different message classes and their corresponding forms and items, see Item Types and Message Classes.

This section demonstrates how to implement wrapper classes that handle different Outlook item types, such as appointments, contacts, and messages. Each of the specialized classes is derived from the abstract InspectorWrapper class that you created before. The code examples also show how to override the Initialize method and register events that are specific to contact items. The Open and Write event notifications of the item are handled, and the Close method is overwritten and must be used to clean up the memory and COM references that you acquired in code.

In the following code example, a ContactItemWrapper class is implemented with the Item property defined as ContactItem. This makes it easy to implement functionality that is specific to contacts only.

namespace InspectorWrapperExplained {
    
    internal class ContactItemWrapper : InspectorWrapper {

        // .ctor
        // <param name="inspector">The Outlook Inspector instance that is to be handled.</param>
        public ContactItemWrapper(Outlook.Inspector inspector)
            : base(inspector) {
        }

        // The Object instance behind the Inspector, which is the current item.
        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.
        // <param name="Cancel">When set to true, the save operation is cancelled.</param>
        void Item_Write(ref bool Cancel) {
            //TODO: Implement something 
        }

        // This method is called when the item is visible and the UI is initialized.
        // <param name="Cancel">When you set this property to true, the Inspector is closed.</param>
        void Item_Open(ref bool Cancel) {
            //TODO: Implement something 
        }


        // The Close method is called when the inspector has been closed.
        // The UI is gone, cannot 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);

            // Set item to null to keep a reference in memory of the garbage collector.
            Item = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
}

The class is derived from the InspectorWrapper class and implements an Item property, which represents the concrete ContactItem instance behind the inspector. You need to override the constructor and pass the instance of Inspector to the constructor of the base class. When the base class has finished initializing, the virtual method Initialize is called. This method can be overwritten and used by the derived class to set up the Item and register the Item events. This must be done for other items, as well. The code for the specific item wrapper looks very similar, but it is specific to the type of the Outlook item. The type of an Outlook item corresponds to its message class. Each Outlook item type has different methods, events, and properties.

In the following code example, an AppointmentItemWrapper is implemented with the Item property defined as AppointmentItem.

namespace InspectorWrapperExplained {

    // Derive a wrapper for each message class/item type.
    internal class AppointmentItemWrapper : InspectorWrapper  {

        // The Object instance behind the Inspector, which is the current item.
        public Outlook.AppointmentItem Item { get; private set; }

        // .ctor
        // <param name="inspector">The Outlook Inspector instance that should be handled.</param>
        public AppointmentItemWrapper(Outlook.Inspector inspector)
            : base(inspector) {
        }

        // Method is called when the wrapper has been initialized.
        protected override void Initialize() {
            // Get the item in the current Inspector.
            Item = (Outlook.AppointmentItem)Inspector.CurrentItem; 
            
            // Register 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.
        // <param name="Cancel">When you set this property to true, the Inspector is closed.</param>
        void Item_Open(ref bool Cancel) {
            // TODO: Implement something 
        }

        // This method is called when the item is saved.
        // <param name="Cancel">When set to true, the save operation is cancelled.</param>
        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, cannot 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;

            // Set item to null to keep a reference in memory of the garbage collector.
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

    }
}

You can create different inspector wrapper classes, one for each Outlook item type that you need to handle independently. Figure 1 shows the list of predefined wrapper classes that are included in the full code sample.

NoteNote

When you create a solution by using Office development tools in Visual Studio, a temporary key is created on your computer, and the solution is signed by using this key. The file is called InspectorWrapperExplained_TemporaryKey.pfx. If you want to compile it on your computer, you need to create your own temporary key, or use a valid developer certificate.

Figure 1: Wrapper classes for different Outlook item types

Wrapper classes for different Outlook item types

In Figure 2, you can see a class diagram that gives you a graphical overview of how the classes are related to each other. Starting from the top is the abstract InspectorWrapper base class. The wrappers for different Outlook item types—such as ContactItem, MailItem, TaskItem, and JournalItem—are derived from the InspectorWrapper class.

Figure 2: Class diagram of the abstract InspectorWrapper class and derived wrapper classes

InspectorWrapper class diagram

To determine the correct wrapper for an item, you can use a factory method. Implement an extra class for the Outlook item, or add it to the InspectorWrapper class. The Outlook item behind an Inspector object is accessible by using the CurrentItem property. The factory method just checks the MessageClass property of the item and returns the corresponding wrapper. The factory method returns null if the item is not handled.

// This factory method returns a specific InspectorWrapper or null if not handled.
// <param name=”inspector”>The Outlook Inspector instance.</param>
// Returns the specific wrapper or null.
public static InspectorWrapper GetWrapperFor(Outlook.Inspector inspector) {

    // Retrieve the message class by using late binding.
    string messageClass = inspector.CurrentItem.GetType().InvokeMember("MessageClass", BindingFlags.GetProperty, null, inspector.CurrentItem, null);

    // Depending on the message class, you can instantiate a
    // different wrapper explicitly for a given message class by
    // using a switch statement.
    switch (messageClass) {
        case "IPM.Contact":
            return new ContactItemWrapper(inspector);
        case "IPM.Journal":
            return new ContactItemWrapper(inspector);
        case "IPM.Note":
            return new MailItemWrapper(inspector);
        case "IPM.Post":
            return new PostItemWrapper(inspector);
        case "IPM.Task":
            return new TaskItemWrapper(inspector);
    }

    // Or, check if the message class begins with a specific fragment.
    if (messageClass.StartsWith ("IPM.Contact.X4U")){
        return new X4UContactItemWrapper(inspector);
    }

    // Or, check the interface type of the item.
    if (inspector.CurrentItem is Outlook.AppointmentItem) {
        return new AppointmentItemWrapper(inspector);
    }

    // No wrapper is found.
    return null;
}

In the Outlook object model, inspectors correspond to Outlook forms. You must create the infrastructure to handle newly created and already opened inspectors. Declare a property in the ThisAddIn class that holds a reference to the Application.Inspectors collection. This property is set in the ThisAddin_StartUp method, where you also register the InspectorsEvents_NewInspectorEventHandler event notification. Whenever the NewInspector event occurs, you wrap the Inspector by defining a method called WrapInspector that takes an Inspector object as an argument, and then connecting the NewInspector event directly with that method.

When Outlook starts, an inspector may already open automatically (for example, if you start Outlook by double-clicking an Outlook message file (.msg) in Windows Explorer). You can handle these inspectors by looping through the Inspectors collection in the ThisAddin_StartUp method and then calling the WrapInspector method for those inspectors. After WrapInspector successfully creates a wrapper, it registers the Close event of the inspector, and stores the instance in a dictionary. This ensures that you never lose events for items, command bars, and other objects. When the InspectorWrapper handles the Closed event, the Outlook form has been closed and you can remove the inspector from memory.

The following code example demonstrates how to implement 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 Outlook loads the add-in. 
        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);

            // Also handle existing Inspectors
            // (for example, double-clicking a .msg file).
            foreach (Outlook.Inspector inspector in _inspectors) {
                WrapInspector(inspector);
            }
        }

        // Wrap an Inspector, if required, and store it in memory to get events of the wrapped Inspector.
        // <param name="inspector">The Outlook Inspector instance.</param>
        void WrapInspector(Outlook.Inspector inspector) {
            InspectorWrapper wrapper = InspectorWrapper.GetWrapperFor(inspector);
            if (wrapper != null) {
                // Register 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.
        // <param name="id">The unique id of the closed inspector</param>
        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)
        {
            // Clean up.
            _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
    }
}

The inspector wrapper technique is very useful in enforcing business logic that requires certain fields (for example, a contact form that requires a business address and phone number before the form can be saved). See the article Enforcing Business Rules in Outlook 2010 for a sample application that validates two fields in a contact form, and displays a message box to the user if the requirements are not met.

The InspectorWrapper template presents a technique to manage multiple inspector windows in an Outlook add-in. This technique supports different Outlook item types independently, ensures that events are handled correctly, and prevents memory leaks and unexpected behavior with inspector objects and item objects.

Helmut Obertanner is a Microsoft Most Valuable Professional and has expertise in Microsoft Outlook and Microsoft Office development tools in Microsoft Visual Studio. Helmut maintains a professional site at www.outlooksharp.de.

Show:
© 2014 Microsoft