Low-level support for ICustomTaskPaneConsumer, IRibbonExtensibility, FormRegionStartup, etc.
I’m mostly interested in the runtime aspects of VSTO, and less interested in the RAD design-time aspects (however wonderful they are), and in this post I want to explore some of the low-level infrastructure that the VSTO runtime provides. As a developer building add-ins using VSTO, you mostly (and rightly) don’t care how the plumbing works, so long as it works, and you won’t ever code against the low-level infrastructure directly. However, there’s a middle level which you could code against if you wanted to – after all, some people do prefer to stay close to the bare metal, and don’t need the hand-holding that visual designers provide.
Back in the mists of time (well, April anyway), I explained how the VSTO support for new Office extensibility interfaces was evolving, and how we focus on the more critical runtime elements before we layer on design-time support. You can read my earlier post here.
At that time, I was talking about early CTP releases of VSTO ‘v3’ (Orcas), although we used the same model in VSTO 2005 SE (Cypress), which shipped at the beginning of Nov-06. Because SE was such an extremely short-cycle release, you can think of it as a snapshot of our design thinking for Orcas – our evolving Orcas support was frozen in time in the SE release. Specifically, SE includes full runtime support for the new extensibility interfaces introduced in Office 2007, and a minimal level of design-time support for these. Of course, we’re aiming to provide more extensive design-time support in Orcas.
In SE, we do have simple wrappers for ICustomTaskPaneConsumer, specifically so that developers can retain the “one line of code” experience that they’re used to from using the VSTO doc-level ActionsPane. So, that’s a degree of design-time support, although not in the sense of a visual designer. (Note that while the VSTO programming model for these two task pane types is very similar, the underlying plumbing is completely different, based as they are on two completely different interfaces, with radically different programming models.) The rationale here is that ICustomTaskPaneConsumer is sufficiently interesting an interface to warrant a degree of design-time support, yet a visual designer is unwarranted because you can simply use the Windows Forms UserControl designer for your task pane controls.
Continuing the pattern, SE has a simple design-time experience for IRibbonExtensibility, including a simple “Add item” Ribbon wizard, which behaves in a “fire and forget” fashion. Finally, SE has zero design-time support for FormRegionStartup.
So, to create a custom task pane using the SE design-time support, you design a custom UserControl, then call the Add method on the VSTO CustomTaskPanes collection object exposed in the add-in class, like so:
UserControl1 uc = new UserControl1();
CustomTaskPane taskPane = CustomTaskPanes.Add(uc, "Contoso");
taskPane.Visible = true;
To create a custom Ribbon, you invoke the SE design-time Ribbon item wizard, which generates a Ribbon XML file and a class that implements IRibbonExtensibility.
[ComVisible(true)]
public class RibbonX : Office.IRibbonExtensibility
{
The Ribbon item wizard also gives you another part of the ThisAddIn partial class, specifically with an override of the RequestService virtual method:
public partial class ThisAddIn
{
private RibbonX ribbon;
protected override object RequestService(Guid serviceGuid)
{
if (serviceGuid == typeof(Office.IRibbonExtensibility).GUID)
{
if (ribbon == null)
{
ribbon = new RibbonX();
}
return ribbon;
}
return base.RequestService(serviceGuid);
}
}
It’s this RequestService method that is your code’s exposure to the underlying VSTO runtime plumbing that will allow your add-in to implement any of the new extensibility interfaces. Right now, VSTO supports three: ICustomTaskPaneConsumer, IRibbonExtensibility and FormRegionStartup, but the point is that later versions of Office could implement any number of additional extensibility interfaces. So long as the Office apps keep querying the add-in for the new interfaces in the same way, the plumbing underlying RequestService should continue to function correctly with these new interfaces, without change. All you have to do in your add-in is to override RequestService to return the appropriate object that implements the interface(s) you want to support. Obviously, as new interfaces are introduced, we will test them thoroughly against this runtime mechanism so that we can support them “officially” as well as “technically”.
Getting back to Ribbon for a moment, because SE has no VSTO wrapper for IRibbonExtensibility, and only a non-re-entrant Ribbon item wizard, any customization you want to make in an SE add-in to the Ribbon controls and their behavior means manually editing the XML and manually adding callbacks to the custom Ribbon class.
At the extreme with zero design-time support, if you want to implement a custom form region in an add-in, you have to create a class manually that implements FormRegionStartup:
[ComVisible(true)]
public class FormRegionX : Outlook.FormRegionStartup
{
…and you’re completely on your own in SE, with no design-time support of any kind.
The main point I want to make in this post is to focus on the low-level plumbing that supports an open-ended number of new extensibility interfaces. So, let’s take all design-time support out of the picture to level the playing field. Suppose you want to build a VSTO add-in that takes advantage of the runtime RequestService support, but you want to implement all the extensibility interfaces yourself without any wizard interference?
For Ribbon, it’s easy, since all the Ribbon item wizard does is to generate for you a class that implements the interface (and some simple XML to get you started with the Ribbon markup), and clearly you could do this entirely manually if you had to. Here’s a simple class that implements IRibbonExtensibility, pretty much as the Ribbon item wizard would generate, and a good starting point if you want to implement the interface manually:
[ComVisible(true)]
public class RibbonX : Office.IRibbonExtensibility
{
private Office.IRibbonUI _ribbon;
public RibbonX()
{
}
public void OnLoad(Office.IRibbonUI ribbonUI)
{
_ribbon = ribbonUI;
}
public string GetCustomUI(string ribbonID)
{
return Properties.Resources.RibbonX;
}
public void OnToggleButton(
Office.IRibbonControl control, bool isPressed)
{
Globals.ThisAddIn._taskPaneX._taskPane.Visible = isPressed;
}
}
Note, that this code assumes that I’ve put the RibbonX.xml into my project resources (so that I get a typed member of the Resources class called RibbonX). I’ve also implemented the OnToggleButton callback to toggle the visibility of a custom task pane – this is a custom field I’m going to add to my ThisAddIn class.
Here’s a similar class that implements ICustomTaskPaneConsumer manually – all we have to do is to cache the task pane factory that Office gives us, and use it at any point thereafter to create custom task panes. To create a custom task pane, we invoke the factory’s CreateCTP method, specifying the ProgId of the control that we want Office to create for us:
public class TaskPaneX : Office.ICustomTaskPaneConsumer
{
private Office.ICTPFactory _taskPaneFactory;
internal Office.CustomTaskPane _taskPane;
public void CTPFactoryAvailable(Office.ICTPFactory CTPFactoryInst)
{
try
{
_taskPaneFactory = CTPFactoryInst;
_taskPane = _taskPaneFactory.CreateCTP(
"ContosoAddin.SimpleControl", "SimpleControl", Type.Missing);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
In the above example, the string “ContosoAddin.SimpleControl” is the ProgId of a custom UserControl in my project – it could just as easily be any ActiveX control or any managed control exposed (and registered) as an ActiveX control. Note that my task pane class doesn’t need to be ComVisible, although of course my UserControl class does. It’s not necessary to make the whole add-in assembly ComVisible, just selected classes. On the other hand, you do need to set the build property to register the add-in for COM interop – this will also register my SimpleControl as an ActiveX control. Here’s the non-designer part of my SimpleControl class:
[ComVisible(true)]
[ProgId("ContosoAddin.SimpleControl")]
[Guid("918E7B30-E23A-4226-85A2-181ABB514C66")]
public partial class SimpleControl : UserControl
{
public SimpleControl()
{
InitializeComponent();
}
}
Finally, here’s the code in my ThisAddIn class to hook everything up: I have a field for both of my classes that implement extensibility interfaces, and I return this field when Office queries for the corresponding interface.
private RibbonX _ribbonX;
internal TaskPaneX _taskPaneX;
protected override object RequestService(Guid serviceGuid)
{
if (serviceGuid == typeof(Office.ICustomTaskPaneConsumer).GUID)
{
if (_taskPaneX == null)
{
_taskPaneX = new TaskPaneX();
}
return _taskPaneX;
}
else if (serviceGuid == typeof(Office.IRibbonExtensibility).GUID)
{
if (_ribbonX == null)
{
_ribbonX = new RibbonX();
}
return _ribbonX;
}
return base.RequestService(serviceGuid);
}
As you can see, all I’m doing is overriding RequestService to test specifically for the two interfaces I want to implement, ICustomTaskPaneConsumer and IRibbonExtensibility. The same would apply for other new extensibility interfaces. Simple, no? Of course, there’s no need to code down at this level if you have higher-level design-time support – including the simple design-time support we shipped with VSTO 2005 SE. In the next release of VSTO, we’ll have even more comprehensive visual designer support. The point is merely to shine a little light on the underlying plumbing.
Comments
- Anonymous
January 18, 2007
The comment has been removed - Anonymous
February 21, 2007
senips, have you checked that you've got all the right attributes on your usercontrol? It must be ComVisible, and have the ProgId that you use in the call to CreateCTP. You also need to ensure this usercontrol is registered, and you can do this by checking the "Register for COM interop' setting in the project properties. - Anonymous
May 30, 2007
Office 2007 introduced a set of new extensibility interfaces. Prior to Office 2007, there were several - Anonymous
March 14, 2008
Hi, using this technique, will my customTaskPane still have the same look (Title at the Top, option dropdown and Close button)? I'm lookong to find a way customize a bit more the look of my task pane.Thank you,Nicolas - Anonymous
April 12, 2008
Will this code work with Office 2003 apps ? - Anonymous
April 12, 2008
Nicolas - this low-level technique is effectively what the higher-level VSTO wrapper does under the covers. So, yes, the behavior will be exactly the same. Regardless of how you implement your custom task pane, the Office behavior restricts the title to the top, with the option dropdown and close button - you cannot change any of these. - Anonymous
April 12, 2008
Tom - no, Office 2003 does not support app-level custom task panes. Word and Excel 2003 can have doc-level custom task panes in VSTO solutions only - but this is a completely different feature. For details of the doc-level task pane, see here: http://msdn2.microsoft.com/en-us/library/7we49he1(VS.80).aspx - Anonymous
May 22, 2008
In an earlier post , I looked at how you could morph a VSTO project for one application into a project - Anonymous
July 21, 2009
I'm trying to build a COM Shim based outlook addin.the wizard created OutlookAddin_COM.csproj as the managed project.For various reasons I want to implement the main logic in a seperate project, and I want the RibbonX callbacks to be specified in one of these satellite assemblies.How can I expose the IRibbonExtensibility interface from a class in different dll?tks - Anonymous
August 12, 2009
yossi - Office will query your add-in object for IRibbonExtensibility. If you don't want to implement this interface directly in your primary add-in class (typically, the Connect class), then you'd have to implement a custom QueryInterface in this class that uses either aggregation or composition to access the IRibbonExtensibility implementation in the object where you have implemented it.Aggregation and Composition are well-understood techniques in COM, but they do add complexity. Is there a compelling reason why you want to do this?