Custom Button Faces in a Managed Code Add-in for the Microsoft Office System

 

John R. Durant
Microsoft Corporation

August 2003

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

Summary: Explore the code of a managed add-in for Microsoft Office applications that retrieves icons from a resource file and displays them on a custom menu. Learn how to create a resource file and place icon images in it. Then, see how you can access the resource file's contents in your code and place its icon images on the faces of custom buttons. (12 printed pages)

Contents

Introduction
Working With Custom Button Faces
Setting up the Add-in
Creating Resource Files
Accessing Resource Files
Building and Deploying the Add-in
Conclusion

Introduction

Working with Microsoft® Office solutions often requires adding custom menus, command bars, and buttons to the host application so users find the solution easier to use. Fortunately, the Microsoft Office System exposes rich object models for this purpose. You can add entries to existing menus or command bars, configure new menus and toolbars, and configure their contents. A typical command bar has a number of buttons, some with text, and others with a picture only, and others with both.

When adding buttons to a command bar, a developer can specify which image, if any, should appear on the face of the button. Office developers are happy to know that they can choose between thousands of icons. To choose one, set the FaceId property of the target button equal to the numeric value of an existing button face. All of the button faces one normally sees in Office are possible faces for custom buttons. Figure 1 shows some of the buttons on the Standard toolbar along with their respective ID values.

Figure 1. Buttons on a toolbar along with their face ID values

You can read or set the FaceId property of a button at design time or at runtime, but usually it is set when you add the button. The code below shows a new command bar to which a custom button is added. The FaceID of the button is set to 17. Ensure that you do not confuse the FaceId property with the Id parameter that is passed to the Add method of the Controls collection. In the following code example, the Id parameter of the Add method is set to 23, but this specifies the type of button to add, not the face or image associated with the button.

Set newBar = CommandBars.Add(Name:="CustomBar", _
  Position:=msoBarTop, Temporary:=True)
newBar.Visible = True
Set customButton = newBar.Controls.Add(Type:=msoControlButton, Id:=23)
customButton.FaceId = 17

As convenient as it is that there are many thousands of possible button faces already available in an Office application like Microsoft Word, business needs sometimes require a custom button face. In addition, creating Office applications that need to be localized can benefit from using custom button faces as the application is adjusted to accommodate linguistic and cultural differences. Resource files, as employed in this add-in, are a great way to store multiple strings or images for use in a localized application.

Working With Custom Button Faces

Custom button faces are just images. You can use .jpg, .tiff, .ico, .gif files, or other image types. Type of image is not terribly important, because in the end Office buttons can acquire a custom button face from a bitmap saved to the Windows clipboard. For example, you must convert image types such as .ico files to BMP format and then copy to the Clipboard before you can use them as custom button faces. In the following Microsoft Visual Basic® for Applications (VBA) example, the code copies the face from one control to the face of another using the CopyFace and PasteFace methods.

Set myControl = CommandBars.FindControl(Type:=msoControlButton, Id:=2)
myControl.CopyFace
Set myControl = CommandBars.FindControl(Type:=msoControlButton, Id:=23)
myControl.PasteFace

After the PasteFace method is called, the button from which the method was called acquires the image saved to the clipboard. Use the CopyFace method to copy the face of the specified button to the Clipboard. Use the PasteFace method to paste the contents of the Clipboard onto the face of the specified button. The PasteFace method fails if the Clipboard is empty. If the image on the Clipboard is too large for the button face, the image does not scale down.

There is no special kind of Office application that you must use to work with buttons and custom button faces. However, an add-in is a common candidate for a solution that requires them, and an add-in is a great way to explore the Office object model in managed code. In addition, an add-in that is written in managed code easily demonstrates the power of resource files and their use with Office.

Setting Up the Add-in

The add-in for this solution has few features except for the creation of a special menu with two buttons. This menu, shown in Figure 2, appears on the Menu Bar command bar of Microsoft Word, and displays two buttons with custom button faces.

Figure 2. The result of a custom add-in shows two menu items with custom button faces

The add-in is a .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:

To create an add-in using Microsoft Visual Studio .NET

  1. In Microsoft Visual Studio® .NET, on the File menu, point to New and click Project.
  2. In the Project Types pane, expand the Other Projects node and click Extensibility Projects.
  3. In the Templates pane, click Shared Add-in.
  4. Type CustomFaces as the name, specify the location, and then click OK. The Extensibility Wizard launches.
  5. Click Next to begin the wizard. Click Create an Add-in using Visual C# and then click Next.
  6. For this solution, select Microsoft Word as the Application Host. Clear all other check boxes and then click Next.
  7. Type a name and description for the custom add-in, and then click Next.
  8. On the Choose Add-in Options page, select both check boxes to ensure that the add-in loads when the host application starts, and any user can employ the add-in.
  9. Click Next.
  10. Review the summary information and click Finish to complete the steps in the Extensibility Wizard.

Note While this article does not explain all of the details for working with add-ins or doing so in managed code, it does review a few basic aspects of the code involved.

After specifying the settings for the add-in, the Extensibility Wizard adds several methods that are implementations of the IDTExtensibility2 interface, the interface used for add-ins of various kinds. Of particular interest are three of these methods, OnConnection, OnDisconnection, and OnStartupComplete. The OnConnection method is fired when the add-in is loaded for any reason. You can load an add-in can be loaded in many ways, for example when the host application starts, as was specified when setting up this add-in. However, you can also configure add-ins to load programmatically or enable a user to load it by using the user interface (UI) of the host application. In this solution, the add-in creates the menu with buttons even if the application is not starting. In the code for the OnConnection method, conditional logic calls the OnStartupComplete method manually.

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);
   }
}

The OnStartupComplete method, when called from the OnConnection method, does the work of setting up the menu and preparing the host application for the custom solution. The following example shows entire code for the method:

public void OnStartupComplete(ref System.Array custom)
{
  Office.CommandBar bar;
  Office.CommandBars bars;
  Office.CommandBarPopup ctlMenu;
  System.Type applicationType;
  object missing = Missing.Value;
  try
    {
       applicationType = applicationObject.GetType();
       bars = (Office.CommandBars)applicationType.InvokeMember(
         "CommandBars", BindingFlags.GetProperty, null, 
         applicationObject, null);
       bar = bars["Menu Bar"];
       int windowIndex = bar.Controls["Window"].Index;
       Office.CommandBarControl ctl;
       ctl=(bar.FindControl(missing,missing,"&Custom",missing,missing));
       if (ctl!=null)
         {
            ctl.Delete(missing);
          }
       ctlMenu = (Office.CommandBarPopup)
       bar.Controls.Add(
       Office.MsoControlType.msoControlPopup,
       missing,missing,windowIndex,true);
       ctlMenu.Caption = "&Custom";
       ctlMenu.BeginGroup = false;
       ctlMenu.Tag = ctlMenu.Caption;
       AddSubButton(ctlMenu,"Icon1");
       AddSubButton(ctlMenu,"Icon2");
     }
     catch (Exception exc)
     {
       MessageBox.Show(exc.Message,"bars setup");
     }
}

By using reflection in Windows .NET, the code acquires a reference to the CommandBars collection of the host application. This is done by using the InvokeMember method of the application's type. This allows the code to gain access to the Menu Bar so that it can receive the new custom menu.

applicationType = applicationObject.GetType();
       bars = (Office.CommandBars)applicationType.InvokeMember(
         "CommandBars", BindingFlags.GetProperty, null, 
         applicationObject, null);

After gaining a reference to the Menu Bar, the code can access it and determines where the Window menu is located. This is so that the solution can place the custom menu just before the Window menu. The code then checks to see if the custom menu already exists on the command bar. If so, it removes the first custom menu.

bar = bars["Menu Bar"];
       int windowIndex = bar.Controls["Window"].Index;
       Office.CommandBarControl ctl;
       ctl=(bar.FindControl(missing,missing,"&Custom",missing,missing));
       if (ctl!=null)
         {
           ctl.Delete(missing);
         }

Finally, the code sample adds the custom menu anew, specifying its location and caption. Before exiting, the code sample calls a custom procedure, AddSubButton, to add two menu items to the new custom menu.

       ctlMenu = (Office.CommandBarPopup)
       bar.Controls.Add(
       Office.MsoControlType.msoControlPopup,
       missing,missing,windowIndex,true);
       ctlMenu.Caption = "&Custom";
       ctlMenu.BeginGroup = false;
       ctlMenu.Tag = ctlMenu.Caption;
       AddSubButton(ctlMenu,"Icon1");
       AddSubButton(ctlMenu,"Icon2");

The AddSubButton procedure is explained in detail later in the article. However, because it makes use of a resource file in the .NET application, it is important to understand how to create the resource file and access it programmatically.

Creating Resource Files

Resource files are containers for things that are used by an application. They commonly hold strings or images, providing the images are converted to ASCII format. These files are set up at design time, and you can access the contents programmatically during runtime. There are multiple methods for placing things in a resource file. You can use Visual Studio .NET to add a resource file and then place items in it using the integrated development environment (IDE). However, this is not easy to do when attempting to load a file with images. Fortunately, the Visual Studio .NET SDK has a sample called the Resource Editor, that, when built and run, allows you to add to a resource file more easily.

Note   The default location for the Resource Editor sample is the following:

local_drive:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\Samples\Tutorials\resourcesandlocalization

You can use the Resource Editor application to add items to a resource file and then save the file. The following procedure shows you how to use the Resource Editor to add items to a resource file.

To add items to a resource file by using the Resource Editor:

  1. In the Resource Editor, in the Add section, select System.Drawing.Icon as the type of item to add to the resource file from the drop-down list. The solution includes two icons with .ico extensions.
  2. In the text box to the right of the Add section, type Icon1 as the name of the first item to add. This enables the Add button.
  3. Click Add.
  4. Type Icon2 as the name of second item to add and then click Add.
  5. Next, click File and then click Save As. . .
  6. Type IconResource.resX as the name for the project and browse to the parent directory of the sample add-in project.
  7. In the Save as type list, select ResX Files (*.resX) and then click Save to save the resource file
  8. Close the Resource Editor.

You have successfully created a resource file and added two icons to the resource file. The next step is to add the resource file to the add-in project.

Figure 3. Two resources successfully added to a resource file

The next procedure describes how to add the resource file to the project in Visual Studio .NET.

To add the resource file to the add-in project in Visual Studio .NET

  1. In Solution Explorer, right-click on the project and point to Add and then click **Add Existing Item. . . **.
  2. Under Files of type, in the drop-down list, click All files (*.*), locate and click IconResource.resX, and then click Open.
  3. Once you add the resource file to the project, in Solution Explorer, double-click the file to display the data in the IDE (see Figure 8) using an XML or Data view. The XML view also includes the ASCII representation of the image files.

Figure 4. Display the contents of a resource file in the IDE

Accessing Resource Files

Once you create and place the resource file in the project, its contents are inexorably bound up with the executable assembly when you build the project. You can then easily access its contents programmatically. This work is done in a custom procedure, AddSubButton, called by custom code at the end of the built-in OnStartUpComplete procedure. The following example demonstrates how those calls are made:

       AddSubButton(ctlMenu,"Icon1");
       AddSubButton(ctlMenu,"Icon2");

The AddSubButton procedure accepts two parameters. The first is for the object representing the parent menu to which the new button is added. The second is a string containing the name of the icon image that is extracted from the resource file. The following example shows the icon image applied as the face for the new button:

private void AddSubButton(Office.CommandBarPopup cb, string buttonName)
{
  try
    {
      object missing = Missing.Value;
      Office.CommandBarButton customButton;
      customButton = (Office.CommandBarButton) cb.Controls.Add(
        Office.MsoControlType.msoControlButton, 
        missing, missing, missing, missing);
      customButton.Style = Office.MsoButtonStyle.msoButtonIconAndCaption;
      ResourceManager rm = new ResourceManager(
        "CustomFaces.IconResource", this.GetType().Assembly);
      System.Drawing.Icon res;
      res = (Icon) rm.GetObject(buttonName);
      Bitmap bmp;
      bmp=res.ToBitmap();
      Clipboard.SetDataObject(bmp,true);
      customButton.FaceId = 0;
      customButton.Caption=buttonName;
      customButton.PasteFace();
    }
    catch (Exception exc)
    {
      MessageBox.Show(exc.Message,"Adding Sub");
    }
}

First, the procedure sets up a simple variable, missing, to use for optional parameters when calling some methods in the Office object model. Then, the procedure creates a button by using the Add method of the parent command bar. The type of button is msoControlButton. This button type is a simple button rather than a drop-down or other possible button types. The style is set to msoButtonIconAndCaption. This button style indicates that it can contain text as well as the image from our resource file.

Next, the procedure accesses the resource file by creating an instance of a ResourceManager class:

      ResourceManager rm = new ResourceManager(
        "CustomFaces.IconResource", this.GetType().Assembly);

The ResourceManager class includes methods that allow you to extract its contents. When calling the constructor of the ResourceManager class you pass the name of the resource file as the base name. You use the name of the assembly followed by the name of the resource file without the file extension attribute. The second parameter is a reference to the executing assembly that contains the resource file.

With an instance of a ResourceManager, the procedure finds specific items in its data and returns them at run time.

      System.Drawing.Icon res;
      res = (Icon) rm.GetObject(buttonName);

Because we know that we are extracting data whose type is System.Drawing.Icon, we declare a variable of this type and use the GetObject method of the ResourceManager to return an object instance that is explicitly converted to the Icon type. The name of the resource to be found is the name passed to the custom procedure in the buttonName parameter. Conveniently, the Icon type has a ToBitmap method that allows us to convert the icon to a bitmap that can be copied to the Clipboard.

      Clipboard.SetDataObject(bmp,true);
      customButton.FaceId = 0;
      customButton.Caption=buttonName;
      customButton.PasteFace();

The Clipboard object is globally shared, so no instance need be explicitly created. Using the SetDataObject method, we can place the contents of the bitmap on the Clipboard. The first parameter is for the data to be copied, and the second parameter, a Boolean value, lets you specify whether you want the data to remain on the clipboard after this application exits. This solution does not depend on either setting, but it is interesting to set it to TRUE in testing so that, if the code fails to paste the image on the button face properly at run time, you can easily see if the image was ever copied to the Clipboard by pasting from it into any program. If the Clipboard pastes an image, you can confirm that it was in fact copied, and you can then focus on why it failed to paste properly.

Setting the FaceId property of the custom button tells the host application that a custom button face is used. The code also sets the Caption property for the button to the value of the buttonName parameter, the same value used to look up the contents of the resource file. The final line calls the PasteFace method for the button, which takes the contents from the Clipboard and pastes them on the button as its face.

Building and Deploying the Add-in

The final step is to build and deploy the add-in. When you first created the entire solution by using the Extensibility Wizard not only did you create the actual add-in, but you also created a second project and added it 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 you build the project, it creates an installer package that you can launch to install the add-in.

Figure 5. The overall solution contains the files for the add-in and a companion setup project

There are several ways to build the add-in using Visual Studio .NET. On the Build menu, click Build Solution. This creates an assembly as well as the necessary plumbing for COM interoperability. In the Solution Explorer window, you can also right-click the solution and click Build. This builds the Setup project and creates the MSI file. To find the resulting installer package, look in the same directory as the solution and project files for the application. There you find the CustomFacesSetup directory. Within this directory, you can look according to the build type, Debug or Release, to find the MSI file. Double-click this file to launch Setup. On the development computer, you can also just right-click on the CustomFacesSetup project file in the Solution Explorer and click Install. To use the add-in, launch Microsoft Word and see the custom menu featuring two buttons with custom faces.

Conclusion

Resource files are a great way to store strings of images for use at run time. Most Office developers may be unfamiliar with using resource files. The advent of the .NET Framework makes them more accessible in Office projects. The managed add-in designed in this article creates a custom menu with two buttons in Microsoft Word. Those buttons, rather than using pre-existing faceID values, use custom images for their graphical button faces. The add-in retrieves these images from the resource file that was created at design time. Localized applications or ones that require contextual display of certain images or strings can be made more easily using resource files. While it is true that you can actually retrieve these images from the hard disk rather than from an image file, storing them on disk is riskier and harder to manage. Files can be deleted, moved, or inadvertently replaced, compromising the stability of the Office solution. Resource files are more polished, more professional, and ultimately much easier to do. Office applications can take advantage of all these benefits giving them greater flexibility and power.

© Microsoft Corporation. All rights reserved.