RibbonX API

Extend The 2007 Office System With Your Own Ribbon Tabs And Controls

Eric Faller

This article discusses:

  • An introduction to the Ribbon
  • RibbonX controls and features
  • Upgrading add-ins to use RibbonX
  • Building add-ins for Word and Excel
This article uses the following technologies:
The 2007 Microsoft Office system

Code download available at: RibbonX 2007_02.exe(201 KB)

Contents

Support for Existing Add-Ins
New Features in RibbonX
StartFromScratch Mode
Command Disabling and Repurposing
Demand-Loading
Upgrading a Word Template to RibbonX
Adding XML and Images
A COM Add-In for Excel
Implementing the Callback Functions
Invalidating getVisible Callback Values
Other Callbacks
Wrap-Up

If you've seen the 2007 Microsoft Office system, you will have noticed that the new Microsoft® Office user interface is radically different from that of previous Office releases. So you probably won't be surprised to hear that the extensibility model behind the UI is also completely new.

Much like the old toolbars and menus it extended, the CommandBars object model (available since Office 97) was difficult to use effectively. The new model is known as RibbonX and claims many of the same attributes of the new UI itself: ease of use, consistency, modernity, and predictability for end users.

RibbonX disentangles the design of the UI from the code behind it by using XML to specify its contents and structure, with a dynamic callback mechanism behind it. I'll go into all the details later in this article, but first there are a few things that seasoned Office developers may want to know about the new UI.

In moving away from the old UI, Microsoft is also leaving behind a lot of familiar terminology and replacing it with new and unfamiliar concepts. Let's start by going over the basics of the new UI to ensure we're all on the same page. Figure 1 shows Microsoft Office Word 2007 with each of the major UI components labeled.

Figure 1 Elements of the New Word 2007 Interface

Figure 1** Elements of the New Word 2007 Interface **(Click the image for a larger view)

1. Ribbon The large rectangular region above the document is known as the ribbon. It contains the title bar, the Office Button, the Quick Access Toolbar, and the Tabs. RibbonX applies mainly to the ribbon and everything inside of it.

2. Office Button This button drops the Office Menu, which is the rough equivalent of the File menu in previous releases of Office. The Office Menu contains commands that act on documents rather than on the contents of documents. RibbonX add-ins can freely alter the contents of the Office Menu (though they cannot customize the Office Button itself).

3. Quick Access Toolbar This toolbar contains commonly used commands and is the main location for end-user customizations. Users can right-click on any ribbon control and add it to the Quick Access Toolbar (including custom RibbonX controls). Because it's meant to be the space that "belongs" to the end user, RibbonX add-ins are not usually allowed to alter the Quick Access Toolbar unless they have enabled StartFromScratch mode.

4. Tabs The tabs make up the main content of the ribbon and contain UI controls that deal with the contents of the document at hand. RibbonX add-ins can create their own custom tabs and alter the visibility and labels of the built-in tabs.

5. Contextual Tabsets When objects such as pictures or tables are selected inside the document, contextual tabsets appear and contain all the UI elements for dealing with those objects. RibbonX add-ins can alter the visibility of built-in tabsets and add custom tabs to them. One feature not supported in the 2007 Office release is the creation of custom contextual tabsets. Tabsets contain contextual tabs, which otherwise behave the same way as regular tabs.

6. Groups Tabs contain sets of groups, which in turn contain individual UI controls. RibbonX add-ins can alter the visibility of built-in groups and create their own custom groups. One thing they cannot do is alter the contents of built-in groups. This restriction protects the UI layout and guards add-ins against conflicts with each other and future versions of Office. Groups optionally have dialog box launchers in the corner that display dialogs relevant to the group (such as the Font or Paragraph dialogs).

7. Task Panes Several task panes still exist in the 2007 Office system, and it's now possible to have more than one open at a time. COM add-ins can now create CustomTaskPanes that host content such as ActiveX® controls or Windows® Forms controls. (The CustomTaskPane feature is distinct from RibbonX and is not covered in this article.)

8. MiniToolbar The MiniToolbar is a collection of common formatting commands that appears above text selections and right-click context menus. RibbonX add-ins cannot modify the contents of the MiniToolbar, but they can disable or repurpose the built-in commands on it.

9. Context Menus These are the same right-click context menus that we all know and love from previous versions of Office. In the 2007 Office release, RibbonX does not apply to context menus, but they can be extended and customized using the CommandBars object model just as they could in the past.

10. Status Bar The status bar contains several handy new controls, such as word count and the view slider. The status bar cannot be customized by add-ins in the 2007 Office system, though it can be hidden.

Support for Existing Add-Ins

If you already have add-ins written against Office 2003, you might wonder whether they will still work in the 2007 release. Happily, the answer is yes, they will still work just fine (unless they do crazy things like checking the version of Office and refusing to run on newer releases). All of the old menus and toolbars still exist under the covers for the simple reason that legacy add-ins that manipulate them still need to run on the 2007 Office system. How do they show up in the new UI? Let's take a look at an example.

Figure 2 shows a hypothetical Word 2003 add-in that creates a custom toolbar and adds a few buttons to the old menus and toolbars, along with how it looks when loaded in Word 2007.

Figure 2a The Same Add-Ins in Word 2003 and Word 2007

Figure 2a** The Same Add-Ins in Word 2003 and Word 2007 **

Figure 2b

Figure 2b

As you can see, when a legacy CommandBar add-in is loaded, the Add-Ins tab shows up in the ribbon. The Add-Ins tab contains three groups: Menu Commands, Toolbar Commands, and Custom Toolbars. The Menu Commands and Toolbar Commands groups contain custom controls that were added to the legacy built-in menus and toolbars, respectively. They will also display any legacy built-in controls that were repurposed by add-ins to perform custom actions (by setting the OnAction or Hyperlink properties). The Custom Toolbars group displays any legacy custom toolbars as horizontal sections. The tooltips of the controls show the name of the legacy toolbar they are from.

In the event a misbehaved add-in leaves behind buttons after it is uninstalled, the user can right-click on the controls in the Add-Ins tab and delete them from the legacy CommandBar structure.

New Features in RibbonX

RibbonX brings Office programming into the modern age with XML-based UI declaration. Instead of writing complicated code that builds up the UI using a series of object model calls, you can create an XML file that specifies the appearance of the UI in a structured markup form. This has numerous advantages for the add-in writer.

First, you're separating the UI from the code. The UI designer doesn't need to know how to update the code manually in order to try out new designs. In fact, if the add-in chooses to load its XML from an external file, the UI can be modified and perfected without even recompiling the add-in. This eliminates the middle step from the iterated Edit, Compile, Run cycle required by hardcoded UI systems.

Second, the add-in's complete UI is known by Office. This may not sound significant until you realize that with CommandBars, previous versions of Office had no idea which toolbars and menus belonged to which add-in. All of the UI modifications came in over COM calls, which were not traceable back to any individual add-ins. This led to several annoying problems, which can now be avoided completely.

Third, Office can automatically clean up an add-in's UI. Previously add-ins had to have special code that went through and deleted their menus and toolbars on shutdown or uninstall. If the add-in crashed or failed to clean itself up properly (as many did), it would leave useless buttons in the UI. Since the 2007 Office system cleans up add-in UI automatically, there's no need to write any cleanup code and no possibility that there will be left-over UI.

Versioning between different releases of Office is another issue you'll avoid. Valid XML files are required to contain an xmlns namespace declaration that identifies which schema version they comply with. This provides RibbonX with information about which version of Office a particular add-in targets and allows it to map the UI appropriately, given any changes that might have occurred between versions. The lack of versioning and ownership in the CommandBars object model are two of the main reasons that legacy add-ins often broke when upgrading to new releases of Office and that the Add-Ins tab is unable to better distinguish between the jumble of controls added to it by the OM calls.

Finally, XML has good tool support. Given the RibbonX schema definition file (XSD), a wide variety of XML editors (including Word 2007 itself) can be used to create valid RibbonX UI files with drag-and-drop simplicity.

The CommandBars object model can be described as using a "push" model, where add-ins are pushing all of the UI data to Office by setting various properties in the object model. Each property of the controls in the UI must be explicitly set by the add-in at startup, even if the menu or toolbar where that control lives is never shown. If this involved loading up a large number of image files, it could be quite expensive, performance-wise.

RibbonX uses a "pull" model, where Office pulls the data from the add-in only when it's needed. Instead of setting properties like CommandBar.Name or CommandBarButton.Picture, add-ins provide get callbacks, such as getLabel or getImage. Office calls these callbacks when it needs to know what the label or image of a control is.

The main advantage of the pull model is performance. In the 2007 Office release, it's probable that most of an add-in's UI will not be visible when the application starts, especially if that add-in has added its own top-level tab or a group to the Add-Ins tab. There is no immediate need to incur the performance penalty of loading up all of the images for those controls. Only when the user clicks that tab do the images need to be loaded. RibbonX delays the calling of callbacks as much as possible in order to amortize the cost of resource loading across the entire session, and keeps Office applications starting up snappily even when several add-ins are installed.

If Office is deciding when to call these callbacks, you might wonder how to change control properties on the fly. RibbonX caches the callback return values and won't call back until the add-in invalidates them. Add-ins can do this using the Invalidate and InvalidateControl methods on the IRibbonUI interface. Once a control's properties have been invalidated, RibbonX will know to call back the next time they are needed. If that control is currently on the screen, it will call back and update the value immediately. If it's not on the screen, RibbonX won't call back until it is. We'll get into all the nitty-gritty details of how to implement callback functions and do invalidations in the sample add-ins later in the article.

RibbonX provides many different control types for extensibility, including those shown in Figure 3. Only the first six of the items in this list were available in CommandBars, so there are many new UI options available to RibbonX add-ins. Perhaps the most compelling new control types are a splitButton that includes both a button and dropdown menu (see Figure 4), and a gallery that expands to display a selection of images. They are both used extensively in the 2007 Office release to reduce UI clutter and easily preview visual operations. Add-in writers are encouraged to use these new control types to give their UI the same impact.

Figure 3 RibbonX Controls

RibbonX Controls
1. button
2. toggleButton
3. editBox
4. menu
5. comboBox
6. dropDown
7. dialogBoxLauncher
8. gallery
9. splitButton
10. label
11. checkBox
12. group
13. tab
14. superTip

Figure 4 SplitButton

Figure 4** SplitButton **

StartFromScratch Mode

Many developers use Office as a platform for building their own self-contained apps. While running, these apps often remove all of the built-in Office UI and replace it with their own. There was no standard mechanism for doing this using CommandBars, so it was fraught with difficulty and error.

RibbonX introduces StartFromScratch mode, which makes hiding the Office UI as easy as writing one line of XML. Setting startFromScratch="true" on the root <ribbon> tag will do the following things:

  • Hide all of the main top-level built-in tabs (but not the contextual tabsets)
  • Hide the contents of the Quick Access Toolbar
  • Enable the add-in to add its own buttons to the Quick Access Toolbar (this is normally disallowed)
  • Hide all the commands on the Office Menu except for New, Open, and Save

Once these changes are applied, add-ins can make further UI changes, including revealing some of the built-in tabs or hiding further TabSets or Office Menu items.

Most of StartFromScratch mode's functionality can be duplicated by manually hiding all of these UI elements, but StartFromScratch does have a couple of advantages over doing this by hand. First, it's a whole lot easier to write one line of XML than fifty. Second, it's designed to be forward-compatible with future versions of Office. If the next version of Word adds new top-level tabs or Office Menu items, StartFromScratch mode should automatically hide them, but add-ins that hide the UI by hand will need to be updated to hide these new elements.

Another great feature of StartFromScratch mode is that it works on a per-document basis. If the user is editing multiple documents in Multiple Document Interface (MDI) mode, one of those documents can enable StartFromScratch mode, and all the UI will automatically hide and show as the user switches between that document and others. No code required!

Command Disabling and Repurposing

Another operation commonly performed by Office add-ins is disabling built-in commands. RibbonX also makes this as easy as writing one line. Developers can disable built-in commands using lines like this in the <commands> section of their XML:

<command idMso="Bold" enabled="false"/> 

This will disable the Bold button anywhere it appears in the UI, which in the default configuration includes the Font group in the ribbon and also in the MiniToolbar.

Because this didn't happen with CommandBars, it's important to note that this disables the Bold button in both locations. Add-ins were required to enumerate through the entire UI and manually disable every copy of the button, in case the user had customized their toolbars and placed a copy somewhere else. They also had to search for commands by their ID number (43, anyone?), instead of human-readable names like "Bold". A getEnabled callback can also be used if the add-in wants to conditionally disable the command only at certain times.

The <commands> XML element is also the home to the RibbonX Command Repurposing feature. Add-ins often enhance or extend the built-in functionality of Office, take over existing UI buttons, and repurpose them. Alternatively, IT managers sometimes choose to restrict functionality using repurposing (for example, showing a password dialog box when the Print button is clicked).

Any button that performs an action on click can be repurposed using RibbonX (though more complicated control types such as galleries or comboboxes cannot be repurposed). Here's an example of the XML required to repurpose the Save button:

<command idMso="Save" onAction="MySaveFunction"/>

When Save is clicked, the add-in's MySaveFunction will run, giving the add-in the opportunity to perform its own actions and choose whether to allow the built-in Save function to execute.

Once again, all copies of the Save button will be repurposed by this one line of XML (both on the Office Menu and on the Quick Access Toolbar), and if an individual document is doing the repurposing, it won't apply to any other documents which are also open (unless that document is loaded as a global template or add-in).

Demand-Loading

One problem with Office add-ins is that their DLLs often take a while to start up, especially if they are written in managed code and the common language runtime (CLR) is not already loaded. Users with several add-ins installed can see a significant performance hit every time they launch an Office application. The irony is that the user probably won't even use most of the add-ins' functionality in any particular session, but they pay the price in all of them.

Since slowdowns reflect poorly on both Office and the add-ins, Office provides a feature for COM add-ins known as demand-loading. This feature has been upgraded in the 2007 Office release and extended to include RibbonX support. An outline of the process follows. The add-in writer sets the add-in's LoadBehavior registry key to ConnectFirstTime (16). On the first boot of the application since the add-in was installed, the add-in is started up. Office queries the add-in for its RibbonX XML and caches it away on disk. RibbonX then calls all of the add-in's Get callbacks in order to load all of its images and cache the initial state of the add-in.

On shutdown, the add-in's registry key is changed to DemandLoad. On subsequent boots of the application, the add-in is not started, but its RibbonX UI is shown as if it had been loaded. As soon as the user clicks one of the add-in's buttons, the add-in is loaded in order to execute the user's command.

Demand-loading is transparent to the end user and, aside from setting the registry key, developers don't have to do any work either. (Compare this to the difficulty of setting up <!ProgID> strings to use demand-loading with CommandBars.) Obviously, demand-loading won't be useful for every add-in, but it should benefit the majority of add-ins that just add a single button or menu item to the UI and then activate their functionality when it's clicked. More complicated add-ins that need to run elaborate code before the user interacts with their UI won't be able to take advantage of demand-loading, however, since they need to be loaded in order to run code.

Upgrading a Word Template to RibbonX

Now that we've covered all the major features of RibbonX, let's dive into the details with some example add-ins and code snippets. First I'll take a look at a scenario many developers will encounter when switching to the 2007 Office system: upgrading a legacy add-in from CommandBars to RibbonX.

The add-in I'm going to upgrade is a fairly simple Word template (.dot) that provides functionality for inserting blank pages into a legal document, complete with the standard (and self-contradictory) "This page intentionally left blank" text.

The add-in is implemented with a button on the Insert menu in the document's attached CommandBars. The button's OnAction property is set to InsertBlankPage, which is a Visual Basic® for Applications (VBA) macro that contains the following code:

Sub InsertBlankPage()
    ' Insert a blank page
    Selection.InsertBreak Type:=wdPageBreak
    Selection.ParagraphFormat.Alignment = wdAlignParagraphCenter
    Selection.Font.Size = 14
    Selection.Font.Color = wdColorGray50
    Selection.Font.Name = "Arial Black"
    Selection.TypeText Text:="This Page Intentionally Left Blank"
    Selection.InsertBreak Type:=wdPageBreak
End Sub

It's obviously not too complicated, but it works as a demo. The file is available for download from the MSDN®Magazine Web site. You can install it as a global template using the Templates and Add-Ins dialog.

The first step when upgrading a legacy CommandBar add-in to the new 2007 Office system UI is to map the old UI into the new model. With all of the toolbars and menus replaced by the ribbon, it's not immediately obvious where the add-in's UI will fit best.

Before you even get to that, take a step back and ask the question, "Do we even need to upgrade this add-in at all?" In many cases, if the budget is tight, the answer might be "No." If you load the add-in in Word 2007, it works just fine in the Add-Ins tab (see Figure 5).

Figure 5 Legacy Add-In

Figure 5** Legacy Add-In **

There's nothing particularly wrong about how it works here, but you might not like how it's stuck in the Menu Commands group with all the other legacy add-ins, and it isn't using any of the new features of RibbonX. It also wouldn't make a very good demo to just quit at this point, so let's continue.

Since the add-in inserts pages into a document, a good place for the UI would probably be on the Insert tab. If the add-in dealt with manipulating the document as a whole, you might choose to put its UI into the Office Menu. If there was no good place for it in the existing UI, a custom group on the Add-Ins tab would be best (to avoid creating a whole tab with just one button).

The built-in Insert tab for Word contains a group for inserting pages and, lo-and-behold, it already contains a button for inserting blank pages (a new feature in the 2007 Office release). Since the legacy add-in also inserts "Intentionally Blank" placeholder text, I'd like to distinguish it from the built-in functionality. Let's add a custom group named "Legal Pages" next to the other group, and have a big button with an instructive icon. Figure 6 shows what it should look like when it's finished.

Figure 6 Insert Page Add-In

Figure 6** Insert Page Add-In **

The next step is to come up with the XML file that will create the desired result. In demos it's often quickest just to start with the final product and then explain how it works. In that spirit, Figure 7 shows the final XML.

Figure 7 Insert Blank Page XML

<customUI xmlns="https://schemas.microsoft.com/office/2006/01/customui">
  <ribbon>
    <tabs>
      <tab idMso="TabInsert">
        <group id="LegalGroup" label="Legal Pages" 
               insertAfterMso="GroupInsertPages">
          <button id="BlankPageButton" label="Intentionally Blank Page"
                  size="large" image="BlankIcon"/>
    </group>
   </tab>
  </tabs>
 </ribbon>
</customUI>

As you can see, it's pretty straightforward. The root tag is <customUI> and specifies the 2007 Office system RibbonX XML namespace to start things off. Then it goes through a series of tags that mirror the structure of the UI: Ribbon > Tabs > Tab > Group > Button.

The first interesting item is the idMso="TabInsert" property on the <tab> tag. All controls in the XML must have ID properties to identify them. Built-in controls use idMso to show that they are Office controls and to distinguish them from custom controls, which use the id property. Thus the XML file is essentially saying "Find the built-in Insert tab and create a custom group inside of it." The insertAfterMso="GroupInsertPages" property says "Insert this group after the built-in Pages group." Finally, button id="BlankPageButton" says "Create a custom button inside the group."

In this example, the labels of the group and button are specified statically in the XML, so they can never change. That's fine in this case, but if you wanted to dynamically change the labels (for localization purposes, perhaps), you could instead use the getLabel property to specify a function that will return the label.

The last interesting bit is the image="BlankIcon" property. This instructs RibbonX to search for an image in the Word template file with that ID. You'll need to stick this image into the file at the same time you insert the XML.

Adding XML and Images

Attached RibbonX XML is only supported in the new file format for the 2007 Office release, so the first thing you'll need to do is open up the .dot file and save it as a .dotm file (the added "m" means that the file has macros). Next, rename the file with a .zip extension and open it up to check out the contents, shown in Figure 8.

Figure 8 Inside the Document Template

Figure 8** Inside the Document Template **(Click the image for a larger view)

The details of the Open XML file format are a topic for another article, but it suffices here to say that the files are essentially compressed packages containing a bunch of files that are related to each other in various ways. In this example, document.xml is the main Word document, and it's related to various things such as the style information, the VBA code and the attached CommandBars. Since I don't want the file to have the attached CommandBars anymore, let's just delete attachedToolbars.bin file out of the .zip file and then pack it back up.

We could manually edit the .zip file to insert the RibbonX XML and the icon image, but it would be much easier to just use a tool, so let's do that. Over at openxmldeveloper.org there are a whole bunch of tools for dealing with the Open XML file format. The one you want is called Custom UI Editor.

Custom UI Editor is a handy little tool for inserting RibbonX XML and associated images into Open XML format files. Use it to open the .dotm file, then paste in the XML and insert the image (see Figure 9). Save the file. If you're curious, you can open it back up as a .zip file and see that the tool created the XML file at \customUI\customUI.xml and the image at \customUI\images\BlankIcon.png. It also modified a couple of rels relationships to point to these new files.

Figure 9 Editing the Template

Figure 9** Editing the Template **(Click the image for a larger view)

If you open up the file in Word now you'll see the new button, but it won't do anything when clicked. That's obvious, since you haven't told it which macro to call. To do that, add the properties shown here in red to the XML:

<button id="BlankPageButton" label="Intentionally Blank Page" 
        size="large" image="BlankIcon"
        onAction="RibbonXOnAction" tag="InsertBlankPage" />

At first it might be strange to introduce a new macro called RibbonXOnAction instead of just reusing the InsertBlankPage macro. The reason I did it this way is that RibbonX OnAction macros have different signatures than their CommandBar counterparts do. If you had lots of macros, you would need to change them all by hand, which could be time consuming and error prone. It would also involve breaking the code so that it would not run on previous versions of Office.

You can get around both of these problems by having the new RibbonX macro just call the old CommandBar macros. This is where the tag property comes in: it's a string property that isn't used in the UI, but is passed on to the add-in's code for use there. Here's how it's used in the RibbonXOnAction macro, which you can paste into a new VBA module:

Sub RibbonXOnAction(button As IRibbonControl)
    Application.Run button.Tag
End Sub

The RibbonX OnAction macro takes an IRibbonControl object as a parameter, which represents the button that was clicked. In CommandBars, OnAction macros did not have parameters, which sometimes made it difficult to figure out which button was being clicked. The IRibbonControl object provides ID and Tag properties to solve this problem in RibbonX.

Once you have that macro in place, the fancy new button starts working! That's all it took to upgrade this add-in's code to run using RibbonX: one new one-line macro and no changes to the existing code. Slick.

A COM Add-In for Excel

As a second example, let's make things a bit more complicated and write a COM add-in for Excel®. The add-in will be a miniature line-of-business (LOB) app for filling out and submitting timecards. I'll only be able to show snippets of the code here, but the full source is available in the code download.

Once again the first step is to figure out what sort of UI is needed. The add-in allows users to create timecards and then edit and submit them. Since the only timecard UI that you need to show at first is a New Timecard button, place it by itself on the Office menu (see Figure 10).

Figure 10 A New Button

Figure 10** A New Button **

Once the New Timecard button is clicked (or an existing timecard is opened), display the rest of the timecard UI in a custom tab. Since various Excel tabs such as Page Layout, Formulas, Review, and Data don't apply to filling out timecards, hide those at the same time (see Figure 11).

Figure 11 A Custom Timecard Ribbon Tab

Figure 11** A Custom Timecard Ribbon Tab **(Click the image for a larger view)

The timecard UI is clearly a lot more complicated than the previous add-in's was, and so is the XML to create it, as shown in Figure 12. The first thing COM add-ins need to use RibbonX is to implement the IRibbonExtensibility interface. Fortunately, the Visual Studio® IntelliSense® feature makes this easy. Create an add-in by selecting New Project in Visual Studio, going to the Other Project Types category, and selecting Shared Add-in in the Extensibility section.

Figure 12 Timecard UI XML

<customUI xmlns="https://schemas.microsoft.com/office/2006/01/customui"
          onLoad="OnLoad" loadImage="LoadImage">
  <ribbon>
    <officeMenu>
      <button id="NewTimecard" insertAfterMso="FileNew" 
              label="New Timecard" imageMso="StartAfterPrevious"
              onAction="NewTimecard" />
    </officeMenu>
    <tabs>
      <tab id="TimecardTab" label="Timecard"
           getVisible="IsCustomTabVisible">
        <group id="EditingTools" label="Editing Tools">
          <button id="ClearTimecard" label="Clear Timecard" size="large" 
                  imageMso="TableDeleteRowsAndColumnsMenuWord"
                  onAction="ClearTimecard"/>
          <button id="InsertDay" label="Insert New Day" size="large"
                  imageMso="CellsInsertDialog" onAction="InsertNewDay"/>
          <button id="CalculateTotal" label="Calculate Hours Worked" 
                  size="large" imageMso="SlideShowRehearseTimings" 
                  onAction="CalculateHours"/>
        </group>
        <group id="WageTools" label="Wage Tools">
          <editBox id="Wage" label="Hourly wage:"
                   image="dollar1.png" onChange="OnWageChanged"
                   getText="GetWage"/>
          <editBox id="Overtime" label="Overtime bonus:"
                   image="dollar2.png" onChange="OnOvertimeChanged"
                   getText="GetOvertime"/>
          <button id="CalculatePay" label="Calculate Pay" 
                  size="large" imageMso="AcceptInvitation" 
                  onAction="CalculatePay"/>
        </group>
        <group id="SubmissionTools" label="Submission Tools">
          <button id="SignTimecard" label="Sign Timecard" 
                  size="large" image="signature.png" 
                  onAction="SignTimecard"/>
          <button id="SubmitTimecard" label="Submit Timecard" 
                  size="large" image="submit.png" 
                  onAction="SubmitTimecard" getEnabled="IsTimecardSigned"
                  screentip="Make sure to sign your timecard before 
                            submitting" />
        </group>
      </tab>
      <tab idMso="TabPageLayoutExcel" getVisible="IsBuiltinTabVisible"/>
      <tab idMso="TabData" getVisible="IsBuiltinTabVisible" />
      <tab idMso="TabReview" getVisible="IsBuiltinTabVisible" />
      <tab idMso="TabFormulas" getVisible="IsBuiltinTabVisible" />
    </tabs>
  </ribbon>
</customUI>

Click through that dialog and the wizard that comes after it. You'll need to select various options such as which Office apps your add-in should be installed in, and which programming language to use. For this particular example I chose to use C#.

Once your add-in is created, you should see the Connect class, which implements the standard COM add-in IDTExtensibility2 interface. This is the object you need to implement IRibbonExtensibility on. Add that interface and have Visual Studio automatically implement its methods for you. You should end up with something like this:

using Office = Microsoft.Office.Core;
[GuidAttribute("E8407539-C642-43BB-8FCB-2E27A46179DE"), 
 ProgId("TimecardAddin.Connect")]
public class Connect : Object, Extensibility.IDTExtensibility2, 
    Office.IRibbonExtensibility
{
    public string GetCustomUI(string RibbonID)
    {
        throw new Exception("Not implemented.");
    }

    // IDTExtensibility methods
}

The only method of IRibbonExtensibility is GetCustomUI, which returns the RibbonX XML as a string. It also takes a RibbonID parameter, which is a string like "Microsoft.Word.Document" or "Microsoft.Excel.Workbook" and is mainly useful when writing add-ins that run in multiple applications or applications with multiple types of ribbons (such as Microsoft Outlook).

If you add the XML file to the project as an Embedded Resource named customui.xml, you can return it from GetCustomUI using code like this:

public string GetCustomUI(string RibbonID)
{
    Assembly assembly = Assembly.GetExecutingAssembly();
    using(Stream stream = assembly.GetManifestResourceStream(
        "TimecardAddin.customui.xml"))
    {
        return new StreamReader(stream).ReadToEnd();
    }
}

At this point you can build the add-in and install it. Upon starting Excel you should see the customized UI, with the exceptions being that the custom images are not loaded and the buttons don't do anything when clicked.

All of the callback functions referenced in the XML will fail to be called since you haven't implemented them yet. If the "Show add-in user interface errors" option in the Advanced section of the application options dialog is enabled you will see a sequence of error messages about the failing callbacks. You should make sure to check this option while developing RibbonX add-ins. It enables an extensive set of error messages that alert you when anything goes wrong in your add-in. The first error message that shows up explains that the "onLoad" callback failed to run, so let's implement that one first.

Implementing the Callback Functions

The root <customUI> tag in the Ribbon has an optional onLoad callback that add-ins can implement to get a reference to the IRibbonUI object. The IRibbonUI object is used to invalidate cached callback return values whenever the state of the add-in's UI needs to change. I'll get into the details of how to use it soon, so just stuff it into a member variable for now. Add this code to the Connect class:

Office.IRibbonUI m_Ribbon;

public void OnLoad(Office.IRibbonUI ribbon)
{
    m_Ribbon = ribbon;
}

Another optional callback on the <customUI> tag is loadImage, which is how COM add-ins load up the icons for their controls. This is where the image="dollar1.png" lines in the XML come into play. For each string specified in an image property, RibbonX calls loadImage to load that image.

Images can be stored however an add-in wants, but in this example let's add the images as embedded resources just as with the XML. Assuming you have added dollar1.png as a resource in the TimecardAddin assembly, here's how to implement the loadImage callback:

public Bitmap LoadImage(string imageName)
{
    Assembly assembly = Assembly.GetExecutingAssembly();
    Stream stream = assembly.GetManifestResourceStream(
        "TimecardAddin." + imageName);
    return new Bitmap(stream);
}

There are a couple of interesting things to note about this function. First, it's returning a System.Drawing.Bitmap object as the image. Support for System.Drawing.Bitmap is new to the 2007 Office release with RibbonX and is the simplest and recommended way to return images for managed add-ins. Unmanaged (C++ and Visual Basic 6.0) add-ins can return IPictureDisp objects for their images, which is the same format that was used in CommandBars.

The second thing to note is that you're returning just one image that contains both the bitmap data and the transparency data. That's right-no more needing to create two separate Picture and Mask icons for your Office add-ins! With RibbonX, Office now supports full 32-bit icons with alpha channels. Currently the PNG format has the best tool support for alpha channels and is the recommended format for your RibbonX icons.

The next set of callbacks you need to implement are the getVisible callbacks on the built-in tabs and the custom tab. The built-in tabs all use IsBuiltinTabVisible and the custom tab uses IsCustomTabVisible, since these two functions need to return opposite values (the built-in tabs should hide when a timecard is being edited, which is when the custom tab should show up).

The add-in can keep track of whether a particular Excel workbook is a timecard by setting the Category property of the file to Timecard. Files without that property will be ignored.

The IsBuiltinTabVisible and IsCustomTabVisible functions will both use a helper IsTimecard function:

public bool IsCustomTabVisible(Office.IRibbonControl tab) {
    return IsTimecard(tab.Context as Excel.Window);
}

public bool IsBuiltinTabVisible(Office.IRibbonControl tab) {
    return !IsTimecard(tab.Context as Excel.Window);
}
private bool IsTimecard(Excel.Window window)
{
    if (null == window) return false;

    Excel.Workbook workbook = (Excel.Workbook)window.Parent;
    Office.DocumentProperties properties = 
        (Office.DocumentProperties)workbook.BuiltinDocumentProperties;
    Office.DocumentProperty category = properties["Category"];

    return (null != category && null != category.Value && 
            category.Value.Equals("Timecard"));
}

This code uses the Context property of the IRibbonControl that is passed to RibbonX callbacks. The Context property is equal to the Window object in most Office applications. (Access does not have Window objects so it's NULL there, and in Outlook it's equal to the Inspector objects.) Add-ins should use the Window object to determine which document this RibbonX callback is referring to, because it's not always equal to the Application.ActiveDocument property. The Window object can also be used to distinguish between two different windows that are viewing the same document.

From the Window object you can get the Workbook object, and from there you can get the Category property and check whether it's equal to Timecard.

Invalidating getVisible Callback Values

If you start Excel now, you'll see that the custom tab hides itself and the built-in tabs are showing up. This is correct since the default document isn't a timecard. But if you open a Timecard document, the tabs don't show up. Why is that?

For performance reasons, RibbonX caches the return values from get callbacks. If it didn't, it would be calling back to the add-in all the time just in case it wants to switch something in its UI. The add-in needs to let Office know when it wants to change its UI.

This is where the IRibbonUI object discussed earlier comes into play. It contains two methods: Invalidate and InvalidateControl. The Invalidate method will invalidate all cached callbacks, while the InvalidateControl method takes a control ID parameter and invalidates the cached callbacks just for a specific custom control.

You need to invalidate the tab's getVisible callback values whenever a document-switch happens in Excel since that is when the UI might need to become visible. Excel provides the WorkbookActivate event that you can use for this. Add a handler for that event in the OnStartupComplete method:

public void OnStartupComplete(ref System.Array custom)
{
    excelApplication.WorkbookActivate += 
       new Excel.AppEvents_WorkbookActivateEventHandler(
           OnWorkbookActivate);
}

void OnWorkbookActivate(Excel.Workbook Wb)
{
    // the custom tab might need to become visible on workbook
    // switch, so invalidate the cached getVisible values
    m_Ribbon.Invalidate();
}

Fortunately Visual Studio IntelliSense can automatically implement the event handler for you as soon as you type the += characters, so this is easy.

Now if you open Excel and switch between a Timecard document and another document, you'll see that the custom tab shows up only for the timecard and the built-in tabs hide at the same time. Perfect. Now you just need to be able to use the add-in to create timecards in the first place.

The New Timecard button's callback is pretty straightforward: it uses the same OnAction signature shown earlier. It creates a new workbook with the Category property equal to Timecard and sets up the default sheet:

public void NewTimecard(Office.IRibbonControl button)
{
    Excel.Workbook timecard = 
        excelApplication.Workbooks.Add(Missing.Value);
    Office.DocumentProperties properties =
        (Office.DocumentProperties)timecard.BuiltinDocumentProperties;
    properties["Category"].Value = "Timecard";
    Excel.Worksheet timesheet = 
        (Excel.Worksheet)timecard.Worksheets[1];
    NewTimesheet(timesheet);
}

It uses a helper function to initialize the timesheet in the first worksheet of the workbook (the same function is called by the Clear Timecard button).

Other Callbacks

The rest of the callbacks are similar to that of the New Timecard button, so I won't include all their code here, but there are a couple of interesting ones worth mentioning. The EditBoxes that hold the hourly wage and overtime multiplier are slightly more complicated than simple buttons since they require two callbacks in order to behave properly. Here are the callbacks for the wage box:

double m_Wage = 5.25;

public void OnWageChanged(Office.IRibbonControl c, string wage)
{
    m_Wage = double.Parse(wage);
}

public string GetWage(Office.IRibbonControl c)
{
    return m_Wage.ToString();
}

OnWageChanged gets called whenever the user types a new value into the wage box. This example just converts and records the new value, but a more realistic add-in would probably want to do some extra bounds-checking and validation on the value before accepting it.

The GetWage method supplies the initial value for the EditBox. It's important to note that it also gets called when the UI is invalidated by the IRibbonUI object. Add-ins shouldn't rely on the fact that the user's input will stick around inside the EditBox, so I recommend that they keep track of the current value using a pair of onChanged and getText like in this example (OnWageChanged and GetWage).

The add-in stores the wage value as a member variable in the add-in, which is OK since this value is global to the add-in and not specific to the document. If this value was tied to each individual timecard, you would need to store the value in memory associated with the timecard, so that the value would properly update when the user switches between two timecards.

The other control of interest is the Sign Timecard button. It pops up a dialog for the user to enter their name and then writes that name into the timecard:

public void SignTimecard(Office.IRibbonControl button)
{
    SignatureDialog sd = new SignatureDialog();
    if (DialogResult.OK == sd.ShowDialog())
    {
        string signature = sd.GetSignature();

        timesheet.get_Range("E" + (daycount + 5), 
        Missing.Value).Formula = signature;

        m_Ribbon.InvalidateControl("SubmitTimecard");
    }
}

It also calls IRibbonUI.InvalidateControl on the SubmitTimecard button. The Submit button is disabled until the timecard is signed using a getEnabled function that checks for a signature. Once that signature is in place, you need to invalidate that getEnabled return value in order to re-enable the button.

Since the add-in only calls InvalidateControl when the Sign Timecard button is clicked, the Submit button won't become enabled if the user manually types their signature into the timecard's cell (or become disabled if the user deletes their signature). If you wanted to get that working we could hook the SheetChanged event and call InvalidateControl whenever the signature cell is modified.

Wrap-Up

That's about all there is to the Timecard add-in. The whole add-in is only a few hundred lines of code, and that's including all the code autogenerated by Visual Studio. Admittedly, it's pretty simplistic and needs a lot more error checking and validation code before it could actually be used, but it demonstrates how easy it is to use RibbonX to leverage Office as a platform for your own applications. If you're interested in trying it out, download the source code.

Eric Faller is a software developer on the Office User Experience team at Microsoft, working on making the new UI extensible to developers. Reach him at eric.faller@microsoft.com.