Sharing Code Between Add-ins

Suppose you want to build a Ribbon customization, or a custom task pane, and you want to use this customization in multiple add-ins? For example, you might want to use the same customization in say an Excel add-in and a PowerPoint add-in. It's pretty simple to achieve this: all you need do is abstract common functionality out to a separate class library, which can then be shared amongst multiple add-in projects. As you do this, you'll encounter a few simple decision points: such as, where to draw the boundary between functionality that we expose publicly from the library, and functionality we keep internal to the library; or, where to handle events? For example, if your shared class library offers a Ribbon customization with a button, should you handle the user's click event in the class library itself, or in the consuming add-in? The answer depends on how generic is the behavior you want to implement: if its generic enough, you can handle it in the library; if it needs to be app-specific or add-in-specific, then you need to handle it in the add-in.

For example, consider a simple class library project (let's call it SharedTools) that contains Ribbon customization and a custom task pane. The ribbon offers 2 buttons. When the user clicks the Calendar button, we handle this event entirely within the SharedTools library code: by toggling the visibility of the custom task pane.

 

public void OnToggleCalendar(Office.IRibbonControl control, bool isPressed)

{

    try

    {

        taskPane.Visible = isPressed;

    }

    catch (Exception ex)

    {

        MessageBox.Show(ex.ToString());

    }

}

For the second button, labeled “Message”, we expect the consuming add-in to respond. We ensure this by handling the event internally in the SharedTools library code, and then immediately refiring an event to any consumer that is listening.

 

public event EventHandler DoSomething;

private void DoSomethingEvent()

{

    if (DoSomething != null)

    {

        DoSomething(this, new EventArgs());

    }

}

public void OnButtonMessage(Office.IRibbonControl control)

{

    DoSomethingEvent();

}

 

The idea here is to show that when you abstract functionality to a common shared library, there may be behavior that you can handle in a client-agnostic manner, and there will also be behavior that needs to be handled specifically by each add-in. Any client-agnostic functionality can also be host application-agnostic. This is the case with the task pane toggle button – we handle it internally in the class library, and each add-in gets the same behavior regardless of the specific Office application that they’re running in.

 

On the other hand, the Message button represents functionality that needs to be handled specifically by each add-in, so we do the minimum necessary in the SharedTools library to refire the event to the consuming add-in.

 

The SharedTools library also contains a custom UserControl, which contains a standard MonthCalendar control. This custom UserControl can be used by the consuming add-in to create a custom task pane. For simplicity, we expose the private MonthCalendar control as a public property so that the consuming add-in can consume its events directly.

 

public partial class TaskPaneControl : UserControl

{

    public MonthCalendar Calender

    {

        get { return monthCalendar; }

    }

}

 

The code below illustrates a simple Excel 2007 add-in which consumes the SharedTools DLL. The add-in declares a field of the RibbonX class type from the SharedTools library, which implements the Ribbon customization. It also declares an instance of the custom UserControl from the SharedTools library. The add-in uses the low-level VSTO runtime support for extensibility features: it overrides the RequestService method to set up the RibbonX object. At the same time, the add-in sinks the event fired when the user clicks the Message button on the Ribbon – and handles this event by displaying a simple test message box:

 

public partial class ThisAddIn

{

    private SharedTools.RibbonX ribbon;

    private SharedTools.TaskPaneControl taskPaneControl;

    protected override object RequestService(Guid serviceGuid)

    {

        if (serviceGuid == typeof(Office.IRibbonExtensibility).GUID)

        {

            if (ribbon == null)

            {

                ribbon = new SharedTools.RibbonX();

                ribbon.DoSomething += new EventHandler(ribbon_DoSomething);

            }

            return ribbon;

        }

        return base.RequestService(serviceGuid);

    }

    void ribbon_DoSomething(object sender, EventArgs e)

    {

        MessageBox.Show("Test");

    }

At startup, the add-in instantiates the custom UserControl and uses it to set up a custom task pane. It assigns this task pane to the TaskPane property on the RibbonX object. It also handles the click events on the MonthCalendar control directly:

 

    private void ThisAddIn_Startup(object sender, System.EventArgs e)

    {

        taskPaneControl = new SharedTools.TaskPaneControl();

        this.ribbon.TaskPane = this.CustomTaskPanes.Add(

taskPaneControl, "Contoso");

        taskPaneControl.Calender.DateChanged +=

new DateRangeEventHandler(Calender_DateChanged);

    }

    void Calender_DateChanged(object sender, DateRangeEventArgs e)

    {

        MessageBox.Show(e.Start.ToShortDateString());

    }

}

 

This code model will work equally well with VSTO 2005 SE and with VS 2008.