The Evolution of VSTO ‘v3’

We have two main reasons for releasing early builds of VSTO v3. The first is to unblock developers so that they can get started building solutions against Office 2007, ready for when it releases. The second is to get feedback on our designs and the early implementation of those designs. A case in point is the support we have for the three new UI extensibility features in Office 2007: custom task panes, custom ribbons, and Outlook custom form regions.

 

Backing up a second here, it should be clear that we’re providing the runtime support ahead of the design-time support. This is as it should be: we make sure everything is actually going to work correctly at runtime; and only then do we layer on design-time features that end up with the rich developer experience that people expect from VS.

 

The three new UI extensibility features have some things in common, and some differences. In common, they are all implementable via COM add-ins (managed or unmanaged). Each one is exposed as a new interface which your add-in can choose to implement. That’s about where the commonalities stop. The hookup behavior starts off the same, and then diverges. Consider the high-level sequence (and please bear in mind that all of this is potentially subject to change). The following describes the raw sequence – it does not describe the VSTO implementation that streamlines most of these steps:

 

Custom task pane:

- Office loads your add-in.

- Office queries your add-in to see if you implement ICustomTaskPaneConsumer (this interface has only one method).

- Your add-in returns to Office the object that implements this interface.

- Later, Office calls back (CTPFactoryAvailable) on that interface on that object to hand your add-in a CTPFactory object.

- Your add-in uses the CTPFactory object at any time thereafter to create new custom task panes.

- When you create a task pane, what you actually do is specify an (unmanaged) ActiveX control or a (managed) UserControl which you want to be placed in the task pane.

- Office will really create the task pane, and put your control in it.

 

Custom ribbon:

- Office loads your add-in

- Office queries your add-in to see if you implement IRibbonExtensibility (this interface has only one method).

- Your add-in returns to Office the object that implements this interface.

- Later, Office calls back (GetCustomUI) on that interface on that object to fetch your XML for your custom ribbon.

- Office parses the XML and creates tabs, groups, controls according to the markup you supplied.

- These controls are all unmanaged and belong to Office – you have no direct access to them at all.

- When the user interacts with these controls, Office will call back on the IRibbonExtensibility object you provided earlier. Note that IRibbonExtensibility defines only one method (GetCustomUI), but you can implement as many additional arbitrary callback methods as you like, relying on the fact that IRibbonExtensibility is a dispatch interface.

- So, when Office calls back to, say your custom OnMyToggleButtonMethod callback on your object, it is doing so using IDispatch::Invoke

 

Custom form region:

- Outlook loads your add-in

- Outlook queries your add-in to see if you implement FormRegionStartup (this interface defines two methods).

- Your add-in returns to Outlook the object that implements this interface.

- Later, Outlook calls back (GetFormRegionStorage) on that interface on that object to fetch your OFS for your custom form region.

- Outlook reads the OFS and creates the corresponding controls.

- Later, Outlook calls back (BeforeFormRegionShow) on that interface, just before showing your form region.

- In this call Outlook passes you a FormRegion object which encapsulates all the controls on the form region, and this is your opportunity to hookup the form controls with your code behind.

                                                                            

As you can see, the hookup between your add-in’s implementation of the interface and Office starts out pretty much the same for all three interfaces, but then diverges according to the specific requirements of each feature. For unmanaged add-ins, Office queries your add-in for the interface via the standard COM QueryInterface call. For managed add-ins, QueryService is the managed equivalent. For VSTO add-ins, this is represented as a ServiceRequest event property. The idea here is that the initial hookup is essentially the same, regardless of the interface.

 

VSTO support for the three new UI extensibility features is at three different stages of completion. Let me emphasize again that all of this is potentially subject to change. We’re deliberately being very transparent here in showing people our early work, but this should not be taken as a commitment to release the final product with this implementation.

 

The most complex of the three interfaces is ICustomTaskPaneConsumer. You might think, “this is just a custom control, what’s so complicated about that?”. Well, for a start, consider how you would ensure that this control is positioned correctly and parented correctly to the task pane window that Office creates. Crucially for managed add-ins, consider which appdomain this control is going to live in, and how you would get it there. (Bear in mind that the raw behavior of ICustomTaskPaneConsumer – without using VSTO – is that you would provide Office with the COM-visible ProgId of the control you want Office to create on your behalf).

 

Because of these issues, we tackled this one first. For custom task panes, we do a lot of work behind the scenes in the VSTO runtime and add-in base classes to make sure that your custom UserControl is instantiated in the same appdomain as the add-in itself, separate from any other add-ins or managed customizations. Plus, we make sure the window parenting is done correctly and seamlessly. If you want to build a custom task pane without using VSTO, you’d have to do all this work yourself. Bearing in mind that by default non-VSTO managed add-ins all live in DefaultDomain, you can start to see the problems. If you provide a custom shim for your non-VSTO add-in, you get to create your own appdomain, but then you’d have to make sure your task pane control lives in that appdomain.

 

So, VSTO add-ins get all this work for free, and the net result is a very simple “one-line of code” developer experience to create custom task panes.

 

The second interface we looked at is IRibbonExtensibility. In the first CTP release we provide a very simple project item wizard (right-click, add new item, Ribbon item). There’s no visual designer yet, but the ribbon item wizard does generate some standard code. This code includes an XML file and a class that implements IRibbonExtensibility. Again, the developer experience is effectively one line of code – all you have to do is create an object of this custom ribbon class and you’re good to go.

 

The interesting thing here is that this ribbon class exposes out some of the same initial hookup code that we implement for custom task pane. If you look at the class, you’ll see the ServiceRequest hookup and how it tests for IRibbonExtensibility.

 

public RibbonHelper()

{

    Globals.ThisApplication.ServiceRequest +=

        new EventHandler<ServiceRequestEventArgs>(ThisApplication_ServiceRequest);

}

void ThisApplication_ServiceRequest(object sender, ServiceRequestEventArgs e)

{

    if (e.Guid == typeof(Office.IRibbonExtensibility).GUID)

    {

        if (e.Service == null)

        {

            e.Service = this as Office.IRibbonExtensibility;

        }

    }

}

 

By the time we get our designer story finished, I anticipate that this code will no longer be exposed to the developer and will just be a part of the base classes.

 

That said, the fact that it is exposed in this first CTP release is interesting for two reasons. One, you can see how we do the hookup, and get an idea of the some of the things our runtime does for you. Two, you can understand how it would be applicable to the other two UI extensibility interfaces.

 

Which brings us to FormRegionStartup. The first VSTO v3 CTP has zero additional support for custom form regions, beyond all the basic add-in support. If you want to implement custom form regions in a VSTO add-in right now, you have to manually add a class to implement the FormRegionStartup interface. In this class you have to provide the hookup – almost exactly the same code that we expose in the hookup for a custom ribbon class.

 

public FormRegionHelper()

{

    Globals.ThisApplication.ServiceRequest +=

        new EventHandler<ServiceRequestEventArgs>(ThisApplication_ServiceRequest);

}

void ThisApplication_ServiceRequest(object sender, ServiceRequestEventArgs e)

{

    if (e.Guid == typeof(Outlook.FormRegionStartup).GUID)

    {

        if (e.Service == null)

        {

            e.Service = this as Outlook.FormRegionStartup;

        }

    }

}

 

Again, by the time we get our designer story finished, I anticipate that this code will no longer be exposed to the developer and will just be a part of the base class.

 

So, two things to take away from this post:

  1. There is a convergence going on here in the design of new Office extensibility interfaces. All the new interfaces are implementable via add-ins, and all are hooked up in the same way.
  2. VSTO provides a very RAD design-time experience, doing a lot of the routine (and often complex) work for you behind the scenes, leaving the developer with a streamlined experience.