Plugin Aware Ribbon in C# Using MEF

With the release of .NET 4.5 came a built-in WPF ribbon control to be used by developers everywhere. I couldn’t manage to find any solid articles on how to allow plugin authors to dynamically add items to the ribbon, so I thought I’d chime in with my solution.

In this article, we will use the Managed Extensibility Framework to do the grunt work of finding our plugins and extend the System.Windows.Controls.Ribbon.Ribbon object to handle those plugins.

Expectations

As this is not a tutorial on MEF, I’ll expect that you have at least some knowledge of this technology. There are many excellent articles online that already cover the subject, so it didn’t seem necessary to touch on it’s intricacies here.

It is also helpful to have a farmiliarity with Linq as it’s used quite often in the code examples.

Overview

It is important to understand how a Ribbon object works before we go messing around with extending it. The ribbon is essentially a system of nested ItemsControl objects:

  • Ribbon
    • ApplicationMenu
    • QuickAccessToolBar
    • RibbonTab
      • RibbonGroup
        • RibbonButton

Knowing this, we can deduce that one adds the ribbon controls themselves to the RibbonGroup object’s Items collection, which is then placed into RibbonTab.Items, and finally all of the tabs are added to the Ribbon.Items collection.

The ribbon also contains two more collections, ApplicationMenu and QuickAccessToolBar, which is where we add our other buttons.

MEF Helper Class

To save some time, i’ve created a quick singleton to handle the MEF container:

using System.ComponentModel.Composition.Hosting;
using System.Reflection;

public class MEFHelper
{
    private static MEFHelper instance;
    public static MEFHelper Instance
    {
        get
        {
            if (instance == null) {
                instance = new MEFHelper();
            }

            return instance;
        }
    }

    private AggregateCatalog catalog = new AggregateCatalog();
    public AggregateCatalog Catalog
    {
        get
        {
            return this.catalog;
        }
    }

    public CompositionContainer Container
    {
        get;
        private set;
    }

    private MEFHelper()
    {
        this.Catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
        this.Container = new CompositionContainer(this.Catalog);
    }
}

Empty Interfaces?

The approach we will be using is to create marker interfaces (or contracts as they are known in the decoupled world) that will allow MEF to see which objects it should be importing into our custom ribbon. These interfaces don’t do anything other than act pretty:

using System;
using System.ComponentModel.Composition;

namespace MEFRibbon.Common {
    [InheritedExport(typeof(IRibbonItem))]
    public interface IRibbonItem { }

    [InheritedExport(typeof(IRibbonMenuItem))]
    public interface IRibbonMenuItem { }

    [InheritedExport(typeof(IRibbonQuickItem))]
    public interface IRibbonQuickItem { }
}

As you can see, each interface has a special MEF attribute called InheritedExport which tells MEF that every object which implements this one is exported.

I should point out that it’s always a good idea to place each class/interface/enum/whatever in it’s own file. It makes things easier to find and cleaner in general. Since this is just an example, i’ve chosen to compact things.

Dynamic Tabs and Groups

We could certainly take the route of adding more contracts to the pile, but wouldn’t it be nice if IRibbonItem objects could just tell us which tab and group they belong to?

To do this, we will create a custom ExportMetadataAttribute that will allow our ribbon to dynamically create tabs and groups as needed:

using System;
using System.ComponentModel.Composition;

namespace MEFRibbon.Common
{
    public interface IRibbonLocationAttribute
    {
        string TabName { get; }
        string GroupName { get; }
    }

    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple=false, Inherited=false)]
    public sealed class RibbonLocation : ExportAttribute, IRibbonLocationAttribute
    {
        public string TabName
        {
            get;
            private set;
        }

        public string GroupName
        {
            get;
            private set;
        }

        public RibbonLocation(string tabName, string groupName)
            : base(typeof(IRibbonItem))
        {
            this.TabName   = tabName;
            this.GroupName = groupName;
        }
    }
}

As you can see, the class is pretty straightforward. The only things I can think to point out is the base(typeof(IRibbonItem)) line and the AttributeUsage attribute. The first one is important because it tells MEF the type of export this attribute should be tied to, and AttributeUsage defines how the ExportAttribute should function and what may actually use it (Class in our case).

Finally, the Ribbon!

Now that the groundwork has been laid, we have everything we need to build our awesome pluggable ribbon. The code is surprisingly simple too, but I’ll break it down to try and explain things a bit better:

using MEFRibbon.Common;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Windows.Controls.Ribbon;
using System.Linq;
using System.Linq.Expressions;
using System.Windows;
using System;

namespace MEFRibbon.Controls
{
    public class MEFRibbon : Ribbon
    {
        [ImportMany]
        private IEnumerable<Lazy<IRibbonItem, IRibbonLocationAttribute>> ribbonItems = null;

        [ImportMany]
        private IEnumerable<IRibbonMenuItem> ribbonMenuItems = null;

        [ImportMany]
        private IEnumerable<IRibbonQuickItem> ribbonQuickItems = null;

        public MEFRibbon() : base()
        {
            MEFHelper.Instance.Container.ComposeParts(this);
            this.Loaded += MEFRibbon_Loaded;
        }
    }
}

Here, we’ve got the foundation of our ribbon; three IEnumerable fields and a constructor with some fancy looking ImportMany attributes. In the constructor, we see our MEFHelper object in action and an event hook called MEFRibbon_Loaded. Once ComposeParts is called, the IEnumberable fields will be filled with any plugins found by MEF.

Note that ribbonItems has a different type than the rest: IEnumberable<Lazy<IRibbonItem, IRibbonLocationAttribute>>. While I didn’t necessarilly want to lazy load the menu items here, it’s the only way I know of to fetch the custom attribute information (that second parameter within the Lazy<> object.

Moving on, we will take a look at where the magic happens:

void MEFRibbon_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
    ribbonMenuItems.ToList()
                   .ForEach(x => this.ApplicationMenu.Items.Add(x));
                   
    ribbonQuickItems.ToList()
                    .ForEach(x => this.QuickAccessToolBar.Items.Add(x));

    ribbonItems.ToList().ForEach(item => {
        RibbonTab oTab = this.FetchOrCreateTab(item.Metadata.TabName);
        RibbonGroup oGroup = this.FetchOrCreateGroup(oTab, item.Metadata.GroupName);
        oGroup.Items.Add(item.Value);
    });
}

Once the XAML has been parsed and everything is ready, the Loaded event fires. We then use Linq to loop through each plugin found, adding it to the respective sections of our object. The names are fairly self-explainatory here, so i’ll spare you the details, but let’s touch on ribbonsItems a bit more.

The ribbonItems field contains our buttons, checkboxes, and any other type of control we want to go on the tool strip itself. Since we are using dynamically created tabs and groups, it was necessary to create some methods to deal with that:

private RibbonTab FetchOrCreateTab(string name)
{
    // Fetch the RibbonTab object with the correct header
    IEnumerable<RibbonTab> oTabs = this.Items
        .Cast<RibbonTab>()
        .Where(tab => tab.Header.ToString() == name);

    if (oTabs.Count() > 0) {
        return oTabs.First();
    }

    // We need a new tab.
    RibbonTab oTab = new RibbonTab();
    oTab.Header = name;
    this.Items.Add(oTab);

    return oTab;
}

private RibbonGroup FetchOrCreateGroup(RibbonTab oTab, string name)
{
    IEnumerable<RibbonGroup> oGroups = oTab.Items
        .Cast<RibbonGroup>()
        .Where(group => group.Header.ToString() == name);

    if (oGroups.Count() > 0) {
        return oGroups.First();
    }

    // We need a new group
    RibbonGroup oGroup = new RibbonGroup();
    oGroup.Header      = name;

    oTab.Items.Add(oGroup);
    return oGroup;
}

Some more Linq magic was used to loop through the list of tabs and groups to see if one already exists. The Header property is what we’ll use as the basis for a match.

If one doesn’t exist, we simply make one with the TabName and/or GroupName specified in the IRibbonLocationAttribute.

Examples

Below are a few examples of how the code for each type of ribbon control will look.

Buttons

Copy.xaml:

<RibbonButton
    x:Class="MEFRibbon.RibbonItems.Buttons.Copy"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    
    Label="Copy"
    Click="Copy_Click" />

Copy.xaml.cs:

using MEFRibbon.Common;
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Ribbon;

namespace MEFRibbon.RibbonItems.Buttons
{
    [RibbonLocation("Main", "Text Manipulation")]
    public partial class Copy : RibbonButton, IRibbonItem
    {
        public Copy()
        {
            InitializeComponent();
        }

        private void Copy_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Nothing was actually copied!");
        }
    }
}

Note that we provided a RibbonLocation attribute at the top of the class.

Quick Launch

Undo.xaml:

<RibbonButton x:Class="MEFRibbon.RibbonItems.QuickLaunch.Undo"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          
          Label="Undo"
          Click="Undo_Click" />

Undo.xaml.cs:

using MEFRibbon.Common;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Ribbon;

namespace MEFRibbon.RibbonItems.QuickLaunch
{
    public partial class Undo : RibbonButton, IRibbonQuickItem
    {
        public Undo()
        {
            InitializeComponent();
        }

        private void Undo_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Nothing was undone!");
        }
    }
}

Help.xaml:

<RibbonApplicationMenuItem
    x:Class="MEFRibbon.RibbonItems.ApplicationMenu.HelpApplicationMenuItem"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    
    Header="_Help"
    Click="Help_Click" />

Help.xaml.cs:

using MEFRibbon.Common;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Ribbon;

namespace MEFRibbon.RibbonItems.ApplicationMenu
{
    public partial class HelpApplicationMenuItem : RibbonApplicationMenuItem, IRibbonMenuItem
    {
        public HelpApplicationMenuItem()
        {
            InitializeComponent();
        }

        private void Help_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("You've been helped!");
        }
    }
}

Summary

All in all, it isn’t that difficult to implement a custom ribbon that is plugin aware. .NET has a ton of great tools to accomplish this task in many different ways. This seemed like a solid solution because it emphasizes the use of XAML instead of a bunch of potentially messy code.

You can get a fully commented copy of the code I talked about in this article here. I hope it helped you!