Ribbon and Task Pane in Access Add-ins

In an earlier post, I looked at how you could morph a VSTO project for one application into a project for another application – specifically, how you could build a VSTO add-in for Access. Note that this is explicitly not supported. However, although this was only intended as an investigative exercise, I notice that some people have added comments to the post asking for further information about things like custom Ribbons. So, to continue the investigative exercise, I’ll look today at how you could build on a VSTO Access add-in to implement custom task panes and Ribbons. The following discussion builds on the Access add-in I discussed in the earlier post.

Recall that you have two options for building custom task panes and Ribbons: you can use the low-level approach of implementing the interfaces directly; or you can use the VSTO wrapper classes and designer support. Let’s look at both options. Many moons ago, I introduced the VSTO low-level support in a blog post, and the updated documentation for the technique is here. Documentation on the VSTO CustomTaskPane wrappers and Ribbon designer are here and here.

To build a custom task pane (regardless of whether or not I use the VSTO wrappers), I must first set up a custom UserControl of some kind (which I’ll call SimpleControl). Then, for the low-level approach, I need to write a class that implements ICustomTaskPaneConsumer. In the CTPFactoryAvailable method, I’ll simply cache the ICTPFactory object for later use, eg:

public class TaskPaneHelper : ICustomTaskPaneConsumer

{

    internal ICTPFactory taskPaneFactory;

    public void CTPFactoryAvailable(ICTPFactory CTPFactoryInst)

    {

        taskPaneFactory = CTPFactoryInst;

    }

}

In my ThisAddIn class, I declare a couple of fields: one for this helper class, and one for the Office CustomTaskPane PIA object:

private TaskPaneHelper taskPaneHelper;

internal Office.CustomTaskPane taskPane;

Then, I must override the RequestService virtual method to return an instance of my helper class:

protected override object RequestService(Guid serviceGuid)

{

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

    {

        if (taskPaneHelper == null)

        {

            taskPaneHelper = new TaskPaneHelper();

        }

        return taskPaneHelper;

    }

    return base.RequestService(serviceGuid);

}

Finally, add code to my ThisAddIn_Startup method to instantiate the task pane:

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

{

    if (taskPaneHelper.taskPaneFactory != null)

    {

        taskPane = taskPaneHelper.taskPaneFactory.CreateCTP(

            "MyAccessAddIn.SimpleControl", "Contoso", Type.Missing);

    }

}

Next, the Ribbon. In the Solution Explorer, I can right-click on the project and select Add New Item. From the New Item dialog, I select a Ribbon XML item (not the Ribbon Visual Designer item), and name the new class RibbonX. This generates some code in RibbonX.cs and some Ribbon XML. I follow the generated comments to move the override of CreateRibbonExtensibilityObject into the ThisAddIn class. Then, I’ll modify the RibbonX.xml to give me one ToggleButton:

<?xml version="1.0" encoding="UTF-8"?>

<customUI xmlns="https://schemas.microsoft.com/office/2006/01/customui">

    <ribbon>

        <tabs>

            <tab idMso="TabAddIns">

                <group id="vstoTab" label="VSTO">

                    <toggleButton id="toggleTaskPane"

                            onAction="toggleTaskPane_OnAction"

                            label="TaskPane"

                            imageMso="ControlLayoutStacked"

                            size="large" />

                </group>

            </tab>

        </tabs>

    </ribbon>

</customUI>

Note: I used the ImageMso RibbonIDs VSTO Power Tool to get the imageMso value.

In my RibbonX class, I implement the button onAction handler to toggle the visibility of the custom task pane:

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

{

    Globals.ThisAddIn.taskPane.Visible = isPressed;

}

That’s it. I can now press F5 to test-run my prototype.

Now that I’ve looked at how to do this using the low-level support, I’ll look at the alternative approach of using the VSTO wrappers and designers. First the custom task pane. If I’m using the VSTO wrappers, I can delete the TaskPaneHelper class altogether, including the TaskPaneHelper field in the ThisAddIn class, the override for RequestService and the code I previously added to ThisAddIn_Startup. I also need to change the type of my taskPane field from the Office PIA type to the wrapped VSTO type:

//internal Office.CustomTaskPane taskPane;

internal Microsoft.Office.Tools.CustomTaskPane taskPane;

The only other thing I need to do is to use the VSTO CustomTaskPanes collection object in my ThisAddIn_Startup method:

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

{

    //if (taskPaneHelper.taskPaneFactory != null)

    //{

    // taskPane = taskPaneHelper.taskPaneFactory.CreateCTP(

    // "Word2AccessAddIn.SimpleControl", "Contoso", Type.Missing);

    //}

    taskPane = this.CustomTaskPanes.Add(new SimpleControl(), "Contoso");

}

F5 again, and this should behave exactly as before.

Next, the Visual Ribbon designer. I can delete the previously-generated RibbonX.cs and RibbonX.xml, and the override for CreateRibbonExtensibilityObject. Instead, I’ll add another new item – this time, a Ribbon Visual Designer item, and I’ll name the class VisualRibbon. I can use the designer to put a ToggleButton on the custom tab, and set its properties (including using the same ImageMso RibbonIDs power tool for the OfficeImageId property).

The one slight anomaly is that I need to set the RibbonType property of the custom Ribbon to a string that matches the string that the host application will pass in when it calls the IRibbonExtensibility.GetCustomUI method. Previously, when I used the low-level support, this was irrelevant, because it is not actually used in the code anywhere. However, this value is used by Outlook because Outlook supports multiple custom Ribbons for multiple message classes. For this reason, the VSTO Ribbon Visual Designer allows you to set this property, and offers a dropdown list of the known valid property values. These include values such as “Microsoft.Excel.Workbook”, “Microsoft.Outlook.Appointment”, etc. Of course, there is no VSTO Access support, so the dropdown list does not include the value that Access will pass in. Access will pass in the value “Microsoft.Access.Database” – so you need to set this value manually in the code (in the VisualRibbon.Designer.cs, which contains the InitializeComponent method and other methods in the hidden part of the partial class):

this.RibbonType = "Microsoft.Access.Database";

Finally, I can implement the Click handler for my new ToggleButton to behave in the same way as in my low-level implementation:

private void toggleButton1_Click(object sender, RibbonControlEventArgs e)

{

    Globals.ThisAddIn.taskPane.Visible =

        ((Microsoft.Office.Tools.Ribbon.RibbonToggleButton)sender).Checked;

}

That’s it – F5 to test.

As before, note that this is not a supported scenario: there are 2 reasons for this post. The first reason is to explore the possibilities off the beaten track of officially documented features in VSTO – purely in the interests of scientific curiosity. The second reason is to make an important point about the strategic design of VSTO add-ins. We’re very careful to build our tooling and runtime support in such a way that it does not block support for potential additional hosts. As I’ve shown, there’s nothing in the VSTO add-in class, the VSTO custom task pane wrappers, or the VSTO ribbon wrappers and designers that prevents their use with host applications that are not (yet) supported – even host applications that themselves do not yet support task panes or ribbons.