Pieter van der Westhuizen

How to merge several Office add-in projects into one binary

You know, I love working for Add-in Express! I think there are few companies that cover so many interesting things to write about. Take this article for example, Eugene asked me to write an article on how the Add-in Express team merged a number of add-ins into one assembly when they build the AbleBits Excel Add-ins Collection.

When I first heard about this topic, I had no idea how to even tackle this problem. Fortunately, one of the perks of writing this blog for Add-in Express is access to the best MS Office developers in the world, and Aleksey send me a sample project. Phew!

The scenario

Before we have a look at the sample project, let’s first examine the scenario for when you might want to consider using this approach. In the case of the Ultimate Suite for Excel, loading was a crucial factor, by following this approach the team effectively reduced the loading time by nearly 60%, because only one assembly needs to be loaded instead of 20+ separate ones.

Now if you ask me, a 60% speed increase is definitely worth-it! In this example, we’ll have two separate Microsoft Excel add-ins. One will have a custom Ribbon Tab including a Ribbon group with a few buttons and a custom Excel Task pane.

The Ribbon Tab and Excel Task pane components of the first add-in

The other add-in will have a custom Ribbon Tab, custom Task Pane as well as some keyboard shortcuts and an Excel App Events component.

The Ribbon Tab, Task Pane, Keyboard Shortcuts and Excel Events components of the second add-in

Before we can merge our Excel add-ins we need to create another plug-in which we’ll use to combine all the elements of the other add-ins.

Start by adding a new ADX COM Add-in project to the solution. Because the other two add-ins only support Excel, you need to select Excel as the supported application. Once the project has been created create two folders in the project called Addin1 and Addin2

Right-click on the Addin1 folder and select Add > Existing Item… Browse to the Addin1 project folder and select AddinModule.cs and the task pane class, in this example it is called ADXExcelTaskPane1.cs, and click the arrow on the right hand side of the Add button and select Add As Link.

The Ribbon Tab, Task Pane, Keyboard Shortcuts and Excel Events components of the second add-in

Repeat the same process for the second plugin, only this time links the files in the Addin2 folder. Once done, the Visual Studio Solution Explorer should look similar to the following image:

The structure of Visual Studio solution

Merging the add-in user interfaces

Since we’re merging our Excel add-ins we have to combine things like Ribbons tabs into a single Ribbon Tab and command bars into a single command bar. To do this, you need to add an empty ADXRibbonTab and ADXCommandBar control to the MergedAddin project’s AddinModule designer surface.

Once the required components are in place, switch to the AddinModule‘s code view and add a new List<T> object to the class:

private List<ADXAddinModule> _modules = new List<ADXAddinModule>();

We’ll add all the modules we need to merge to this collection and loop through each module’s components and add it to the MergedAddin project’s components.

public AddinModule()
{
  Application.EnableVisualStyles();
  InitializeComponent();
 
  if (Process.GetCurrentProcess().ProcessName.ToLower() == "devenv")
      return;
 
  _modules.Add(new Addin1.AddinModule());
  _modules.Add(new Addin2.AddinModule());
 
  for (int i = 0; i < _modules.Count; i++)
  {
    System.ComponentModel.IContainer moduleComponents = _modules[i].GetContainer();
    for (int j = moduleComponents.Components.Count - 1; j >= 0; j--)
    {
      // Keyboard Shortcuts
      if (moduleComponents.Components[j] is AddinExpress.MSO.ADXKeyboardShortcut)
      {
        this.components.Add(moduleComponents.Components[j]);
        continue;
      }
 
      // Excel Task Panes
      if (moduleComponents.Components[j] is AddinExpress.XL.ADXExcelTaskPanesCollectionItem)
      {
        this.adxExcelTaskPanesManager1.Items.Add(moduleComponents.Components[j] as
			AddinExpress.XL.ADXExcelTaskPanesCollectionItem);
        this.components.Add(moduleComponents.Components[j]);
        continue;
      }
 
      // Ribbon controls
      if (moduleComponents.Components[j] is IADXRibbonComponent)
      {
        if (moduleComponents.Components[j] is ADXRibbonTab)
          continue;
        if (moduleComponents.Components[j] is ADXRibbonGroup)
          this.adxRibbonTab1.Controls.Add(moduleComponents.Components[j] as ADXRibbonGroup);
 
        this.components.Add(moduleComponents.Components[j]);
        continue;
      }
 
      // Excel events
      if (moduleComponents.Components[j] is ADXExcelAppEvents)
      {
        BindEvents(moduleComponents.Components[j], this.adxExcelEvents);
        continue;
      }
 
      // Task Panes
      if (moduleComponents.Components[j] is AddinExpress.XL.ADXExcelTaskPanesManager)
      {
        BindEvents(moduleComponents.Components[j], this.adxExcelTaskPanesManager1);
        continue;
      }
    }
    // Module events
    BindEvents(_modules[i], this);
  }
}

The BindEvents method does exactly as its name suggests and will bind the events of the source component to the destinations component in the MergedAddin project.

private void BindEvents(object source, object destination)
{
  System.Reflection.EventInfo[] events = source.GetType().GetEvents();
  if (events == null) return;
  for (int i = 0; i < events.Length; i++)
  {
    System.Reflection.EventInfo eInfo = events[i];
    System.Reflection.FieldInfo fieldSource = 
		source.GetType().GetField(eInfo.Name,
			System.Reflection.BindingFlags.NonPublic |
			System.Reflection.BindingFlags.Instance);
    if (fieldSource == null) // addin module (!!!)
    {
      fieldSource = source.GetType().BaseType.GetField(eInfo.Name,
          System.Reflection.BindingFlags.NonPublic |
          System.Reflection.BindingFlags.Instance);
    }
    if (fieldSource != null)
    {
      Delegate delegateSource = fieldSource.GetValue(source) as Delegate;
      if (delegateSource != null)
      {
        System.Reflection.FieldInfo fieldDestination = 
			destination.GetType().GetField(eInfo.Name, 
				System.Reflection.BindingFlags.NonPublic | 
				System.Reflection.BindingFlags.Instance);
        if (fieldDestination == null)
        {
          fieldDestination = destination.GetType().BaseType.GetField(eInfo.Name,
              System.Reflection.BindingFlags.NonPublic |
              System.Reflection.BindingFlags.Instance);
        }
        if (fieldDestination != null)
        {
			System.Reflection.EventInfo eInfoDestination = 
				destination.GetType().GetEvent(eInfo.Name);
			if (eInfoDestination != null)
			{
				eInfoDestination.AddEventHandler(destination, delegateSource);
			}
        }
      }
    }
  }
}

If you build, register and run the MergedAddin project, you might see some strange behaviour as some of the components are added twice. To resolve this, you need to open the AddinModules of both Addin1 and Addin2 projects and add the following above the class name:

[ComVisible(false)]

Now, when you run your project you should see the UI elements of the two Excel add-ins combined into one.

Merged add-ins in Excel 2010:

The merged UI elements of both add-ins in Excel 2010

There you have it! With a little bit of the Add-in Express teams’ know-how and some innovation you do not need to rewrite your add-ins to combine them into a single binary.

Thank you for reading. Until next time, keep coding!

Available downloads:

This sample Excel Add-in was developed using Add-in Express for Office and .net:

C# sample project for Excel 2007-2013.

Post a comment

Have any questions? Ask us right now!