Exposing Events from Managed Add-in Objects

Exposing Events from Managed Add-in Objects

Following on from my recent posts on exposing add-in objects, here and here, it occurred to me that its sometimes useful to be able to expose events from these objects. Recall that you can expose your add-in through the COMAddIn.Object property in the Office OM, either directly (in a non-VSTO add-in) or through VSTO’s RequestComAddInAutomationService mechanism (documented here).

For example, let’s suppose you’re exposing an object from your add-in that provides a DoSomething method, like this:

namespace ComServiceOleMarshal

{

[ComVisible(true)]

[InterfaceType(ComInterfaceType.InterfaceIsDual)]

publicinterfaceIAddInUtilities

{

void DoSomething();

}

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.None)]

publicclassAddInUtilities :

StandardOleMarshalObject,

IAddInUtilities

{

publicvoid DoSomething()

{

Globals.ThisAddIn.CreateNewTaskPane();

}

}

}

(Let’s assume the CreateNewTaskPane method in the ThisAddIn class does in fact create a new custom taskpane.) Then, in a document/workbook opened in the host application, you write some VBA macro code to consume this object, like this:

Private Sub CommandButton1_Click()

Dim addin As Office.COMAddIn

Dim addInUtils As ComServiceOleMarshal.AddinUtilities

Set addin = Application.COMAddIns("ComServiceOleMarshal")

Set addInUtils = addin.Object

addInUtils.DoSomething

End Sub

So far, so good. Now, suppose you want to be able to fire events from your exposed add-in object. For example, say the custom taskpane has a button, and when the user clicks the button, you want to propagate the event out through the exposed add-in object. To achieve this, you would write an event interface which defines the events you want to expose. Then, specify that the add-in object class implements this event interface, using the ComSourceInterfaces attribute, documented here:

// The delegate type for our custom event.

[ComVisible(false)]

publicdelegatevoidSomeEventHandler(object sender, EventArgs e);

// Outgoing (source/event) interface.

[ComVisible(true)]

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

publicinterfaceIAddInEvents

{

[DispId(1)]

void SomeEvent(object sender, EventArgs e);

}

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.None)]

[ComSourceInterfaces(typeof(IAddInEvents))]

publicclassAddInUtilities :

StandardOleMarshalObject,

IAddInUtilities

{

// Event field. This is what a COM client will hook up

// their sink to.

publiceventSomeEventHandler SomeEvent;

publicvoid DoSomething()

{

Globals.ThisAddIn.CreateNewTaskPane();

}

// We expose a method to allow the add-in class to

// cause the event to fire.

inernalvoid FireEvent(object sender, EventArgs e)

{

if (SomeEvent != null)

{

SomeEvent(sender, e);

}

}

}

The ThisAddIn class is enhanced to sink the Click event on the button, and re-fire it out through the exposed add-in object:

publicpartialclassThisAddIn

{

privateAddInUtilities addInUtilities;

protectedoverrideobject RequestComAddInAutomationService()

{

if (addInUtilities == null)

{

addInUtilities = newAddInUtilities();

}

return addInUtilities;

}

internalvoid CreateNewTaskPane()

{

UserControl uc = newUserControl();

Button b = newButton();

b.Text = "Click Me";

b.Click += newEventHandler(b_Click);

uc.Controls.Add(b);

Microsoft.Office.Tools.CustomTaskPane taskPane =

this.CustomTaskPanes.Add(uc, "New TaskPane");

taskPane.Visible = true;

}

// When the user clicks the button on the taskpane,

// we sink the Click event here, and fire the custom

// event exposed from our IAddInUtilities object.

internal void b_Click(object sender, EventArgs e)

{

addInUtilities.FireEvent(sender, e);

}

}

Finally, the VBA client code is enhanced to use the WithEvents keyword when defining the add-in object, so that it can sink the propagated event:

Note: for this to work, in the VBE you need to add a reference to the add-in’s typelib, and to the typelib for mscorlib, which is typically in a path like this: C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.tlb.

As an aside, note that Richard Cook, a developer in my team, has started a cool series of blog posts that contrast .NET delegate-based event handling with classic COM IConnectionPoint eventing in managed code.