Listing Button Faces in the Command Bar for the Microsoft Office System

 

John R. Durant
Andrew Whitechapel
Microsoft Corporation

August 2003

Applies to:
    Microsoft Office 2003
    Microsoft Office XP
    Microsoft Office 2000

Summary:   Explore the code of a managed Office solution that displays all the button faces available for Microsoft Office System command bars. Learn how to work with the command bar object model in managed code while also making the solution cross-version compatible. The final solution displays all available Office command bar button faces and faceIDs, a very useful tool for developing custom command bars. (24 printed pages)

Contents

Introduction
Creating the Add-in
Preparing the Add-in For Use
Coding the Interface
Building and Deploying the Add-in
Adding Support for Other Applications
Conclusion

Introduction

One of the most difficult things to do as a Microsoft® Office developer is to work with command bars and command bar buttons. Solutions from the simple to the sophisticated may require regular text for the captions on the small buttons, while still others feature an attractive little icon sometimes alone or sometimes with accompanying text. An example of all three is shown here in Figure 1.

Figure 1. Buttons can have an image, text, or both

When creating a command bar with buttons featuring icons, there are thousands of possible icons from which one can choose. The most common practice is trying to find a suitable command bar face is to write a procedure in a macro with a looping structure to loop through numbers, assigning them as the Faceid property of a test button to see what the resulting icon would look like.

The solution for this article builds on this same concept but adds a more polished look (as seen in Figure 2). Moreover, it is usable in different versions of Microsoft Office in different applications such as Microsoft Office PowerPoint® 2003, Microsoft Office Word 2003, or Microsoft Office Excel 2003.

Figure 2. The Add-in displays button faces and provides the face ID in the tool tip text

Creating the Add-in

The add-in is a Microsoft .NET assembly that uses COM interoperability to work with the Office object model. In this case, the .NET assembly is written in C#, but the same could be easily ported to another .NET-compliant language if desired. To create the project, follow these steps to launch and use the Extensibility Wizard:

  1. In Microsoft Visual Studio® .NET, create a project, and in the New Project dialog box, select Extensibility Projects.

  2. In the Templates window of the dialog box, click Shared Add-in as shown in Figure 3.

    Figure 3. Select the Shared Add-in project type in Visual Studio .NET

  3. Give the project a name such as ButtonFaces, specify the project location and then click OK.

  4. In the next step (Figure 4), select the applications to host the add-in. Click Microsoft Word and Microsoft Excel and then click Next.

    Figure 4. Use the Extensibility Wizard to select the applications to host the custom add-in

  5. The dialog box let's you type a name and description for your add-in. Configure these as shown in Figure 5.

    Figure 5. Configure the name and description of the add-in

  6. In the next window (Figure 6), check both boxes so that the add-in loads when the host application starts, and any user can employ the add-in. Click Next.

    Figure 6. Choose when to load the add-in and which users should access it

  7. Click Finish to complete the steps in the Extensibility Wizard.

Preparing the Add-in For Use

With the project set up, Visual Studio .NET creates a class called Connect. This class includes methods, automatically generated by the Extensibility Wizard, for implementing the interface for add-ins, the IDTExtensibility2 interface. The methods of interest begin with the following:

public void OnConnection(object application, 
  Extensibility.ext_ConnectMode connectMode, 
  object addInInst, ref System.Array custom)
  {
    applicationObject = application;
    addInInstance = addInInst;
    if(connectMode != Extensibility.ext_ConnectMode.ext_cm_Startup)
    {
      OnStartupComplete(ref custom);
    }
  }

This method fires when the add-in becomes enabled in the host application. A reference to the host application is passed as a parameter to the method. This allows the code to set up an internal variable that points to the host application. From here, the code calls another wizard-generated method, OnStartupComplete, to begin preparing the add-in for use. The code for this method makes use of some variables for accessing command bars, adding a new button to a menu in the host application, and for discovering the host application's type. The code for these declarations and the OnStartupComplete method are shown here:

private Office.CommandBarButton getButtonFaces;
private Office.CommandBars bars;
private Type applicationType;
public void OnStartupComplete(ref System.Array custom)
  {
  Office.CommandBar bar = null;
  string buttonName = "Get Button Faces";

  try
    {
     applicationType = applicationObject.GetType();
     bars = 
     (Office.CommandBars)applicationType.InvokeMember(
     "CommandBars", BindingFlags.GetProperty, null, 
     applicationObject, null);
       bar = bars["Tools"];  
       object missing = Missing.Value;
   Office.CommandBarButton button = (Office.CommandBarButton)
   bar.FindControl(Office.MsoControlType.msoControlButton, 
     missing, buttonName, true, true);
   if (button == null)
     {
       getButtonFaces = (Office.CommandBarButton) bar.Controls.Add(
            Office.MsoControlType.msoControlButton, 
            missing, missing, missing, missing);
       getButtonFaces.Caption = buttonName;
       getButtonFaces.Style = Office.MsoButtonStyle.msoButtonCaption;
       getButtonFaces.Tag = buttonName;
     }
     else
     {
          getButtonFaces = button;
     }
     getButtonFaces.Click += 
          new Office._CommandBarButtonEvents_ClickEventHandler(
          getButtonFaces_Click);
    }
  catch(Exception ex)
    {
       MessageBox.Show(ex.Message);
    }      
  }

The purpose of this code is to add a button to the Tools menu of the host application like the one shown in Figure 7.

Figure 7. The Add-in launches from the Tools menu of the host application

Using the InvokeMember method, the code gets a reference to the CommandBars collection of the host application and then retrieves the Tools command bar. The InvokeMember method is employed when using what is known as reflection in the .NET Framework (part of the System.Reflection namespace). Reflection allows code to discover and inspect the members of types within an assembly. Using reflection, you can also invoke members of those types using the specified binding constraints and matching the specified argument list for the member. Because the code includes a reference to the type for the host application, we can use the InvokeMember method of that type to do things such as retrieve the CommandBars collection in the host application.

   bars = (Office.CommandBars)applicationType.InvokeMember(
     "CommandBars", BindingFlags.GetProperty, null, 
     applicationObject, null);
       bar = bars["Tools"];  

Using the FindControl method, the code looks to see if the desired button is already installed. In Excel, attempting to add an existing button throws an exception, but Word allows same button to be added multiple times. Catching the exception in our code would not accommodate both situations, so it is better to simply find out if the button exists before attempting to add it.

bar.FindControl(Office.MsoControlType.msoControlButton, 
     missing, buttonName, true, true);

If the button is not installed, then the code adds the button to the target command bar.

   if (button == null)
     {
       getButtonFaces = (Office.CommandBarButton) bar.Controls.Add(
            Office.MsoControlType.msoControlButton, 
            missing, missing, missing, missing);
       getButtonFaces.Caption = buttonName;
       getButtonFaces.Style = Office.MsoButtonStyle.msoButtonCaption;
       getButtonFaces.Tag = buttonName;
     }

Otherwise, a reference to the existing button is acquired.

     else
     {
          getButtonFaces = button;
     }

In either case, the button, getButtonFaces, launches the main function of the application. Because the button is the means to build the command bar that is the bulk of the add-in, we need an event procedure for that button. One is added in the following way:

     getButtonFaces.Click += 
          new Office._CommandBarButtonEvents_ClickEventHandler(
          getButtonFaces_Click);

Coding the Interface

The event handler for the button the code adds to the menu (Figure 7) is all that is needed to launch the primary function of the add-in. The code for the event handler is as follows:

  private void getButtonFaces_Click(
    Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
    {
      if (buttonFaceBar == null)
        {
          BuildCommandBar();
          SetupNavigationButtons();
          AttachButtonFaces(0);
        }
     }

The event handler code calls three other procedures, the first of which contains the bulk of the code for the rest of the add-in. The BuildCommandBar procedure is responsible for building the command bar that displays the icons, storing a reference to the new command bar in the buttonFaceBar variable. The SetupNavigationButtons procedure places navigation buttons on the interface so that a user can look at the button faces in groups of one hundred. The AttachButtonFaces procedure actually displays button faces on the newly created command bar. Looking at the procedures in order of execution, the BuildCommandBar code is the following:

private const short buttonsInRow = 20;
private const short buttonsWithNavigation = 120;
private Office.CommandBar buttonFaceBar;
public void BuildCommandBar()
   {
     try
       {
         buttonFaceBar = bars.Add("Button Faces", 
           Office.MsoBarPosition.msoBarFloating, false, true);
         buttonFaceBar.Protection = 
           Office.MsoBarProtection.msoBarNoChangeVisible
            | Office.MsoBarProtection.msoBarNoResize
            | Office.MsoBarProtection.msoBarNoChangeDock;
        int appTop = 0;
        int appLeft = 0;
        string appName = (string)applicationType.InvokeMember(
          "Name", BindingFlags.GetProperty, null, 
          applicationObject, null);
        switch (appName)
          {
            case "Microsoft Excel":
              appTop = (int)(double)applicationType.InvokeMember(
                "Top", BindingFlags.GetProperty, 
                null, applicationObject, null);
              appLeft = (int)(double)applicationType.InvokeMember(
                "Left", BindingFlags.GetProperty, 
                null, applicationObject, null);
              break;
            case "Microsoft Word":
              appTop = (int)applicationType.InvokeMember(
                "Top", BindingFlags.GetProperty, 
                null, applicationObject, null);
              appLeft = (int)applicationType.InvokeMember(
                "Left", BindingFlags.GetProperty, 
                null, applicationObject, null);
               break;
            default:
              break;
          }
        buttonFaceBar.Top = (int) (appTop + 50);
        buttonFaceBar.Left = (int) (appLeft + 50);
        Office.CommandBarButton button = null;
        for (int i = 1; i <= buttonsWithNavigation; i++)
          {
            object missing = Missing.Value;
            button = (Office.CommandBarButton)
              buttonFaceBar.Controls.Add(
              Office.MsoControlType.msoControlButton, 1,
              missing, missing, missing);
            button.FaceId = 1;
           }
        GetHighestFaceIds(
          (Office.CommandBarButton)buttonFaceBar.Controls[1]);
        buttonFaceBar.Visible = true;
        buttonFaceBar.Width = button.Width * buttonsInRow +6;
      }
    catch(Exception ex)
      {
        MessageBox.Show(ex.Message);
      }      
  }

The main flow of this procedure is that it first adds the new command bar to the CommandBars collection of the host application.

         buttonFaceBar = bars.Add("Button Faces", 
           Office.MsoBarPosition.msoBarFloating, false, true);
         buttonFaceBar.Protection = 
           Office.MsoBarProtection.msoBarNoChangeVisible
            | Office.MsoBarProtection.msoBarNoResize
            | Office.MsoBarProtection.msoBarNoChangeDock;

The Add method of the CommandBars collection accepts four parameters. The first is of the name of the target command bar. The second is for its position of the command bar, in this case a floating command bar. The third with a value of False specifies that the new command bar should not replace the active menu bar with the new command bar. The final parameter tells the host application that the command bar should be removed when the application closes.

Following the addition of the new command bar, the code sets the Top and Left property offsets so that the newly created command bar is positioned at a certain location with respect to the Top and Left properties of the host application. The command bar itself includes a set of buttons, 120 to be precise, that is reused as the user views the thousands of button faces. As each new group of button faces is requested, the new button images are copied to the existing buttons. Even though there are 120 buttons, one of the rows of buttons is actually used for navigation, so the user sees the button faces in groups of one hundred. The interface buttons include buttons for navigation and a cancel button.

        Office.CommandBarButton button = null;
        for (int i = 1; i <= buttonsWithNavigation; i++)
          {
            object missing = Missing.Value;
            button = (Office.CommandBarButton)
              buttonFaceBar.Controls.Add(
              Office.MsoControlType.msoControlButton, 1,
              missing, missing, missing);
            button.FaceId = 1;
           }

Within the loop that adds new buttons, the Controls property of the main command bar is used to add new, blank buttons. The Add method is invoked to add and return a new button. Passing the msoControlButton parameter to this method specifies that the new button should be a simple button rather than a dropdown button or one of a long list of possible button types. The second parameter, here a value of 1, specifies that a blank control of the specified type is added to the command bar. Then, its FaceId property is set to 1 which causes it to be blank or with no image for its face. This is different from specifying the type of control as it merely dictates what button face, if any, should appear on the button.

Completing the interface preparation, the code finds the starting point for the final group of buttons by calling a custom procedure, GetHighestFaceIds. Then, it makes the command bar visible, and sets its width. The width is determined by multiplying the number of buttons by the width of those buttons. Additionally, an arbitrary number is added to the calculation. This number, six in the sample below, may not be suitable for all screen resolutions or tastes. Thorough testing should, of course, prevail. A more context-sensitive calculation could be also be devised.

        GetHighestFaceIds(
          (Office.CommandBarButton)buttonFaceBar.Controls[1]);
        buttonFaceBar.Visible = true;
        buttonFaceBar.Width = button.Width * buttonsInRow +6;

To find the starting point for the final group of buttons, the GetHighestFaceIds procedure loops through numbers in increments of 1000 up to an unreasonably high limit (10 million) although the code exits long before it reaches that number. Setting the FaceId property (testButton.FaceId = i) throws an error when the number is too high. When the exception is thrown, the code loops through in smaller increments before finally identifying the starting point of the final group of button faces. Before quitting, the code continues on to find the final available face ID, storing it in the finalID variable. The GetHighestFaceIds procedure stores the highest face ID value a button starting a group can have for the current version of the Office application in the highestStartID variable.

private int highestStartID;
private int finalID;
public void GetHighestFaceIds(Office.CommandBarButton testButton)
{
   int increment = 1000;
   int loopStart = 1000;
   for (int i = loopStart; i <= 10000000; i += increment)
   {
      try
      {
         testButton.FaceId = i;
      }
      catch (Exception)
      {
         if (increment == 1000)
         {
            i -= 1000 + 100;
            increment = 100;
         }
         else if (increment == 100)
         {
            highestStartID = i - buttonsInGroup;
            i -= 100 + 1;
            increment = 1;
         }
         else
         {
            finalID = i - 1;
            break;
         }
      }
   }
}

With the command bar prepared by calling BuildCommandBar, the getButtonFaces_Click procedure (the event procedure that fires when a user decides to engage the Add-in from the Tools menu of the host application) can set up the navigation buttons and then begin to populate its buttons with viewable images. Setting up the navigation buttons is done in the SetUpNavigationButtons procedure. While a little lengthy, the code is quite simple, just adding navigation buttons with their own fixed button faces and corresponding event handlers. The first button, aptly named firstButton, includes a property setting distinctly different from the other buttons in that its BeginGroup property is set to True. This assures that the button is at the beginning of all other buttons on the command bar. The following code excerpt also contains the values for the button faces pertaining to each of the navigation buttons. Here, values for existing button faces are used. Also, the code includes a constant, firstNavigationButton, which sets the ID of the firstButton navigation button, which is the seventh button of the bottom row. Because there are one hundred buttons, the ID must be 100 plus the seven.

private Office.CommandBarButton firstButton;
private Office.CommandBarButton bigPreviousButton;
private Office.CommandBarButton previousButton;
private Office.CommandBarButton nextButton;
private Office.CommandBarButton bigNextButton;
private Office.CommandBarButton lastButton;
private Office.CommandBarButton cancelButton;  
private const short firstNavigationButton = 107;
private const short firstFace = 154;
private const short bigPreviousFace = 41;
private const short previousFace = 155;
private const short nextFace = 156;
private const short bigNextFace = 39;
private const short lastFace = 157;
private const short cancelFace = 478;
public void SetupNavigationButtons()
{
   try
     {
       firstButton = 
         (Office.CommandBarButton)buttonFaceBar.Controls
         [firstNavigationButton];

         firstButton.BeginGroup = true;
         firstButton.FaceId = firstFace;
         firstButton.Click += 
            new Office._CommandBarButtonEvents_ClickEventHandler(
            firstHundred_Click);
         firstButton.TooltipText = "First 100";

         bigPreviousButton = 
           (Office.CommandBarButton)buttonFaceBar.Controls
           [firstNavigationButton+1];
         bigPreviousButton.FaceId = bigPreviousFace;
         bigPreviousButton.Click += 
           new Office._CommandBarButtonEvents_ClickEventHandler(
           bigPreviousHundred_Click);
          bigPreviousButton.TooltipText = "Previous 1000";

          previousButton = 
            (Office.CommandBarButton)buttonFaceBar.Controls
            [firstNavigationButton+2];
          previousButton.FaceId = previousFace;
          previousButton.Click += 
            new Office._CommandBarButtonEvents_ClickEventHandler(
            previousHundred_Click);
          previousButton.TooltipText = "Previous 100";

          nextButton = 
            (Office.CommandBarButton)buttonFaceBar.Controls
            [firstNavigationButton+3];
          nextButton.FaceId = nextFace;
          nextButton.Click += 
            new Office._CommandBarButtonEvents_ClickEventHandler(
            nextHundred_Click);
          nextButton.TooltipText = "Next 100";

          bigNextButton = 
            (Office.CommandBarButton)buttonFaceBar.Controls
            [firstNavigationButton+4];
          bigNextButton.FaceId = bigNextFace;
          bigNextButton.Click += 
            new Office._CommandBarButtonEvents_ClickEventHandler(
            bigNextHundred_Click);
          bigNextButton.TooltipText = "Next 1000";

          lastButton = 
            (Office.CommandBarButton)buttonFaceBar.Controls
            [firstNavigationButton+5];
          lastButton.FaceId = lastFace;
          lastButton.Click += 
            new Office._CommandBarButtonEvents_ClickEventHandler(
            lastHundred_Click);
          lastButton.TooltipText = "Last 100";
          cancelButton = 
            (Office.CommandBarButton)buttonFaceBar.Controls
            [firstNavigationButton+6];
          cancelButton.FaceId = cancelFace;
          cancelButton.Click += 
               new Office._CommandBarButtonEvents_ClickEventHandler(
               cancelFaceBar_Click);
          cancelButton.TooltipText = "Close";
          Office.CommandBarButton button =
            (Office.CommandBarButton)buttonFaceBar.Controls
            [firstNavigationButton+7];
          button.BeginGroup = true;
       }
     catch (Exception ex)
       {
          MessageBox.Show(ex.Message);
       }
  }

The list of buttons and their purpose is as follows:

Button Name Event Procedure Purpose
firstButton firstHundred_Click Display the first 100 buttons
bigPreviousButton bigPreviousHundred_Click Display the 1000 buttons prior to the current group
nextButton nextHundred_Click Display the 100 buttons after to the current group
previousHundred previousHundred_Click Display the 100 buttons prior to the current group
bigNextButton bigNextHundred_Click Display the 1000 buttons after the current group
lastButton lastHundred_Click Display the lastButton 100 buttons
cancelButton cancelFaceBar_Click Close the custom command bar

The corresponding code for these event procedures is shown here:

private void firstHundred_Click(
  Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
  AttachButtonFaces(0);
}
private void bigPreviousHundred_Click(
  Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
  if (latestStartID >= buttonsInGroup)
  {
    AttachButtonFaces(Math.Max(0, latestStartID - 1000));
  }
}
private void previousHundred_Click(
  Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
  if (latestStartID >= buttonsInGroup)
    {
      AttachButtonFaces(latestStartID - buttonsInGroup);
    }
}
private void nextHundred_Click(
  Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
  if (latestStartID < highestStartID)
    {
      AttachButtonFaces(latestStartID + buttonsInGroup);
    }
}
private void bigNextHundred_Click(
  Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
  if (latestStartID < highestStartID)
  {
    AttachButtonFaces(Math.Min(highestStartID, latestStartID + 1000));
  }
}
private void lastHundred_Click(
  Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
  AttachButtonFaces(highestStartID);
}
private void cancelFaceBar_Click(
  Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
  CloseFaceBar();
}

The event procedure for canceling the use of the custom command bar calls a procedure, CloseFaceBar. This procedure quite simply deletes the custom command bar.

private void CloseFaceBar()
{
  try
  {
     buttonFaceBar.Delete();
     buttonFaceBar = null;
  }
     catch(Exception ex)
    {
       MessageBox.Show(ex.Message);
    }      
}

The AttachButtonFaces procedure uses a starting point and begins populating the buttons with image faces from that starting point. The procedure uses two variables, latestStartID and finalID, to keep track of the ID for button at the beginning of the current group. This is incremented or decremented as the user moves through the groups of button faces.

private const short buttonsInGroup = 100;
private int latestStartID;
public void AttachButtonFaces(int startID)
{
  int i = 1;
  latestStartID = startID;
  UpdateNavigationButtons();
  int relativeFinalID = finalID - latestStartID;

  try
    {
      for (; i <= buttonsInGroup && i <= relativeFinalID; i++)
        {
           Office.CommandbarButton button = 
             (Office.CommandBarButton)buttonFaceBar.Controls[i];
           int id = startID + i;
           button.FaceId = id;
           button.TooltipText = id.ToString();
        }

        buttonFaceBar.Name = String.Format(
          "Button Faces ({0}..{1})", startID +1, startID + i - 1);
        ClearUnusedFaces(i);
     }
    catch (Exception ex)
     {
       MessageBox.Show(ex.Message);
     }
}

To make it clear to the user which group of buttons is being displayed, the display name for the command bar is set using a concatenation of a caption with the values of two placeholders, one for the starting faceID in the group and a second for the ending faceID:

        buttonFaceBar.Name = String.Format(
          "Button Faces ({0}..{1})", startID +1, startID + i - 1);

Using the starting point for the entire group, the code can loop through new button faces until the group limit is reached, buttonsInGroup.

      for (; i <= buttonsInGroup && i <= relativeFinalID; i++)
        {
           Office.CommandBarButton button = 
             (Office.CommandBarButton)buttonFaceBar.Controls[i];
           int id = startID + i;
           button.FaceId = id;
           button.TooltipText = id.ToString();
        }

Along the way, the AttachButtonFaces procedure does two more important things. First, it updates the navigation buttons so that buttons are disabled if they cannot be used. For example, if the user is at the end of the list, buttons that let the user see more button faces should be disabled. Similarly, if the user is at the beginning of the list, it should be impossible to navigate backward, so the corresponding button should be disabled. This logic is processed in the UpdateNavigationButtons procedure.

public void UpdateNavigationButtons()
{
   try
   {
         buttonFaceBar.Controls[firstNavigationButton].Enabled = 
         latestStartID != 0;
         buttonFaceBar.Controls[firstNavigationButton+1].Enabled = 
         latestStartID >= 1000;
         buttonFaceBar.Controls[firstNavigationButton+2].Enabled = 
         latestStartID != 0;
         buttonFaceBar.Controls[firstNavigationButton+3].Enabled = 
         latestStartID != highestStartID;
         buttonFaceBar.Controls[firstNavigationButton+4].Enabled = 
         latestStartID <= highestStartID - 1000;
         buttonFaceBar.Controls[firstNavigationButton+5].Enabled = 
         latestStartID != highestStartID;
       }
   catch(Exception ex)
   {
         MessageBox.Show(ex.Message);
       }      
}

Secondly, the AttachButtonFaces procedure calls the ClearUnusedFaces procedure so that if there are buttons not used at the end of the button faces list, those buttons are cleared of any pre-existing content.

public void ClearUnusedFaces(int firstUnusedButton)
{
   try
   {
      for (int i = firstUnusedButton; i <= buttonsInGroup; i++)
      {
        Office.CommandBarButton button = 
          (Office.CommandBarButton)buttonFaceBar.Controls[i];
        button.FaceId = 1;
        button.TooltipText = "";
      }
    }
    catch(Exception ex)
    {
      MessageBox.Show(ex.Message);
    }      
}

The entire application is coded, except for a routine that runs when the Add-in is unloaded from the host application. There is a built-in event procedure as part of the IDTExtensibility2 interface that lets you conditionally run code, depending on what triggered the unloading of the Add-in.

public void OnDisconnection(
  Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
{
   if(disconnectMode != 
     Extensibility.ext_DisconnectMode.ext_dm_HostShutdown)
   {
     OnBeginShutdown(ref custom);
   }
   applicationObject = null;
}
public void OnBeginShutdown(ref System.Array custom)
{
    object missing = Missing.Value ;
    getButtonFaces.Delete(missing);
    getButtonFaces = null;
}

This code checks to see if the host application is being shut down. This causes the add-in to be unloaded. If the add-in was unloaded as a result of something other than the host application being shut down, the code allows the OnBeginShutdown procedure to run. The code in this event simply removes the button from the Tools menu of the host application.

Building and Deploying the Add-in

The final step is to build and deploy the add-in. When the entire solution was first created by using the Extensibility Wizard in Visual Studio .NET not only was the actual add-in created, but a second project was also created and added to the solution. This second project is the setup project for the add-in itself. The project files (see Figure 8) include dependencies that are detected for the add-in and once the project is built, it becomes a Microsoft Installer package that can be launched to install the add-in.

Figure 8. The overall solution contains the project and setup files for the Add-in

To build the add-in in Visual Studio .NET, on Build menu, click Build Solution. This creates a .NET assembly as well as creates the necessary plumbing for COM interoperability. One way to build the setup project is to right-click on the project in the Solution Explorer window and click Build. This builds the setup project and creates the .MSI file. To find the resulting Microsoft Installer package, look in the same directory as the solution and project files for the application. There you find a ButtonFacesSetup directory. Within this directory you can look in the directory for the type build you have done, release or debug, and find the .MSI file. Double-click this file to launch the installation, but this step is not necessary on your development computer, as it is already registered and prepared for use. To use the add-in, launch Word or Excel and on the Tools menu, click Get Button Faces.

Adding Support for Other Applications

So far, the add-in supports Word and Excel. It is important to remember that each application, while part of the Microsoft Office System, may well have behaviors and properties that are unlike other applications in the same family. For example, slight differences in the command bar support in Word and Excel mean making changes in the code to accommodate them. Similarly, adding support for another application, such as PowerPoint means that you may have to add conditional code at other locations in the code. Thorough knowledge of the object models and rigorous testing ensures that you sort these differences out and adjust your code to develop a stable and reliable add-in.

As far as the project in Visual Studio is concerned, there is really only one adjustment that needs to be made to make the add-in usable in another application. Figure 9 shows the Registry Editor interface for the setup project of the add-in.

Figure 9. Use the Registry Editor to configure registry settings on a target computer

The Registry Editor in a setup project lets you configure registry settings that you want to have configured on the target computer when the setup project runs. For example, in Figure 9 you can see registry keys for Excel, PowerPoint, and Word. The ButtonFaces.Connect key has three values, two of them string values and one for numeric value that tells the host application when the add-in should be loaded. In this case, the value of three specifies that the add-in should be loaded when the host application starts. When the Extensibility Wizard was first run to create the initial project, the registry key for the add-in in the PowerPoint key was not present. This was manually added later to make the add-in usable with PowerPoint. Running the installation project again adds this registry key to the registry on the target machine. Again, you may need to write conditional code to accommodate differences in the object models of the host applications.

Conclusion

Working with command bars is common when designing user-friendly Office applications. However, it is equally common to want images on command bar buttons rather than just text. Before creating a custom image for a button face, it is a good idea to see if there is already one that suits your needs. Chances are that among the literally thousands of button faces there is one that works for your application.

The code in this article makes it easier than ever to look through the library of button faces to find the one you need. Written in C#, this code creates an add-in that can work with Office 2000, Office XP, and Office 2003. Its portability to different Office versions is made possible in part because it does not make assumptions about the number of button faces available in each version. These amounts do vary in different versions, but the add-in code is designed to intelligently find out how many are present and respond accordingly.

© Microsoft Corporation. All rights reserved.