Synchronizing Ribbon and Task Pane

The new custom task pane model in Office 2007 is interesting. It certainly opens up a wide range of opportunities for providing a better user experience than the doc-level ISmartDocument-based task pane. It’s also interesting in that it provides some challenges for Word and InfoPath developers – see previous post.

The thing that’s most immediately obvious is that individual add-in developers now have a lot of freedom in how they design the resulting user experience. Office provides the basic framework for hooking up your task pane, and it leaves you to implement the task pane in whatever way you see fit. Consider that, unlike the task pane in Office 2003, the new task pane does not stack. Rather, it tiles. So, if the application is running say 10 add-ins, and each add-in implements one or more task panes, if they’re all visible at the same time, the user will run out of screen real estate pretty fast.

To mitigate this, the Office UI guidelines suggest that you should never develop an add-in which programmatically forces the task pane on the user outside their control. Your custom task pane should not be visible on startup, and the user should always be able to make it visible or hidden at will. The simplest approach is to provide a ribbon button to allow the user to toggle the task pane visibility.

By implication, you should never assume in your add-in code that your task pane is visible at any given time. That means you need to sink the VisibleChangedEvent on your task pane so you always know its state – the user might open/close your task pane via the custom ribbon button you conveniently supply, but they might also simply hit the X box to close it directly. By further implication, you’re responsible for synchronizing the toggled state of your ribbon button (assuming you’re providing a togglebutton) to the actual state of the task pane.

Here’s how you could set this up in a very simple way. The following code snippets assume you’ve created a VSTO add-in and added a simple ribbon class (via the Add Item project wizard). Here’s the markup for my togglebutton:

<toggleButton id="standardToggle"

              label="Standard"

              onAction="OnStandardToggle"

              getPressed="GetPressed"/>

 

The interesting stuff in my ribbon class is after the #region block for “standard stuff”:

[ComVisible(true)]

public class RibbonX : Office.IRibbonExtensibility

{

    #region Standard stuff

    private Office.IRibbonUI ribbon;

    public RibbonX()

    {

    }

    public void OnLoad(Office.IRibbonUI ribbonUI)

    {

        this.ribbon = ribbonUI;

    }

    public string GetCustomUI(string ribbonID)

    {

        return Properties.Resources.RibbonX;

    }

    #endregion

    private bool isTaskPaneVisible;

    public bool IsTaskPaneVisible

    {

        get { return isTaskPaneVisible; }

        set

        {

            isTaskPaneVisible = value;

            ribbon.InvalidateControl("standardToggle");

        }

    }

    public bool GetPressed(Office.IRibbonControl control)

    {

        switch (control.Id)

        {

            case "standardToggle":

                return isTaskPaneVisible;

            default:

                return false;

        }

    }

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

    {

        Globals.ThisAddIn.ctp.Visible = isPressed;

    }

}

 

So, I have a bool field in my ribbon class, exposed via a property which invalidates my ribbon togglebutton inside the setter. Invalidating my control will make Office call back to my GetPressed method. In my GetPressed method, I return the current value of the flag I’m using to cache the visible state of my task pane. When the user clicks my togglebutton, I set the visible state of the task pane.

Over in my add-in’s main class, I make sure my CustomTaskPane field is accessible to my ribbon class. I create the task pane in my Startup method, and at the same time I hook up an event handler for the VisibleChanged event. When I get this event, I toggle the state of the bool flag in my ribbon class:

 

private RibbonX ribbon;

internal CustomTaskPane ctp;

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

{

    this.Application = (Excel.Application)ExcelLocale1033Proxy.Wrap(typeof(Excel.Application), this.Application);

    ctp = this.CustomTaskPanes.Add(new ContosoAqua.ContosoControl(), "Contoso");

    ctp.Visible = false;

    ctp.VisibleChanged += new EventHandler(ctp_VisibleChanged);

}

void ctp_VisibleChanged(object sender, EventArgs e)

{

    ribbon.IsTaskPaneVisible = !ribbon.IsTaskPaneVisible;

}

 

Pretty simple, yes?

Another thing you should do is to design your add-in so that it does not need multiple task panes. One way to achieve this is to use a TabControl on your task pane – this is a very simple and cheap mechanism to provide your users with multiple different controls for each different context that you’ve identified for your solution, yet at the same time reduce the impact on their screen real estate.

Implementing such a task pane is pretty trivial. Here’s one I built earlier – I have a UserControl with a TabControl with 3 tabs. I’ve populated each tab with the controls I want for that context (actually, I created a separate UserControl with the controls I wanted for each tab). Then create a CustomTaskPane using the outer UserControl, and Robert is your father’s brother: