Welcome to X4U
Minimize 

Hello visitor.

This is the personal homepage of Helmut Obertanner.
I'm a computer freak and electronic enthusiast and I live in Munich in Germany - the hometown of the famous Oktoberfest.
Why the name X4U? In my case it's a synonym for everything is possible.
I'm an experienced full time C# developer and doing so since .Net Framework 1.0.
As technical enthusiast I started Office development using .Net technologies.
As I searched answers and found help in the Internet I decided to share my knowledge in newsgroups and forums too. For participation, helping other developers, writing how-to's and articles and doing consulting and trainings I received the Microsoft MVP Award for the 3rd time.

As Outlook C# specialist I founded the website Outlooksharp.de
Beside software development, I'm building electronic devices for customers.
Prototypes with SMD PCBs and complete electronic devices with USB connections are my typical portfolio.
You got an Idea and have no plan how to put it to work - give me a call and I tell you if I can help.
A complete solution starts from defining the requirements, design the schematic and PCB, build a functional prototype, program the firmware, program the windows software, design and build a housing prototype.
Usually I work with very cheap PIC 18F Microcontrollers, these multifunctional devices are programmed in C language. 

I'm located in Germany with satisfied customers around the globe.
Enjoy the content of my site and feel free to give me your feedback.
You're welcome.
X4U Blog
 
Nov1

Written by:Helmut Obertanner
Sun, 01 Nov 2009 21:54:58 GMT 

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.

 

 

 

 

Tags:

Your name:
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
Enter the code shown above in the box below
Add Comment  Cancel 
X4U Blog
 
Home  |  Hardware  |  Software  |  Services  |  Resources  |  Impressum