I have been working on a VSTO 2005 project in the past few weeks and although the progress made since VSTO 2003 is substantial in terms of productivity and freedom, there are still a few gotchas. In the next few days I will be posting information on some of the issues I have come across and any helpful tips I have discovered. The project was a Word Template one, so some of the posts will be specific to that, but most of the code will apply to VSTO 2005 in general.

 

Adding a toolbar is quite a common task and it is not that difficult, but there are a few little details that you need to be aware of. Let’s have a look at some code first

 

private Office.CommandBar AddToolbar()

{

    // Create the toolbar

    Office.CommandBar toolbar = this.Application.CommandBars.Add(

        "Toolbar name", MsoBarPosition.msoBarTop, missing, false);

   

    // Add a button to it

    Office.CommandBarButton button = (Office.CommandBarButton)

        toolbar.Controls.Add(MsoControlType.msoControlButton, missing,

        missing, missing, false);

 

    // Set the button's style

    button.Style = Office.MsoButtonStyle.msoButtonCaption;

 

    // Set the button's caption

    button.Caption = "Click me";

 

    // Assign a tag value and the Click event will be fired once

    // for every document that is open

    //button.Tag = “a unique value”;

 

    // Ensure the toolbar is visible, by default it won't be

    toolbar.Visible = true;

 

    // Return the newly created toolbar

    return toolbar;

}

 

Let’s start with the first line of code that actually creates the CommandBar object. If the code is placed in the ThisDocument.cs file then you have access to the Word application through the homonymous property; otherwise you can use Globals.ThisDocument.Application. First you pass in the name of the new toolbar, then where you want it (top, bottom, floating, etc.), then whether you would like to replace the currently active menu bar or not (it is a Boolean parameter, but you can use missing which defaults to false) and lastly and most interestingly whether you would like to add the new toolbar as a temporary or a permanent one. The difference between the two is that a permanent toolbar is saved to the template and will be available next time a document is created from the template without having to execute this code. A temporary one is not saved in the template and it will have to be added every time you create a document from the template. Unless there is a reason to do otherwise, I prefer to add the toolbar as permanent to save myself some time during the creation of a new document.

 

The second line of code adds a control to the toolbar. You specify what type of control you would like (drop-down, popup, combo, etc.), in this case a button, then you have some parameters that apply to built in controls which we are not interested in here and finally again whether the control should be temporary or permanent. After that, the style of the button is set (caption, image, etc.) and since we chose a caption button we give it a caption. The line of code that is commented out line refers to the Tag property of the button which when given a value, the Click event of the button will be fired in every open document that was based on the template; otherwise the event will be fired only in the currently active one. This is quite handy if you need to perform an action across multiple open documents. The rest is for making the toolbar visible, which is the same as right clicking on Word’s toolbar pane and selecting a toolbar by name.

 

As you may have noticed, the code above does not subscribe to the button’s Click event. This is not an omission; there is a very good reason for it. If you remember, a toolbar can be added either as permanent or not and here we are adding it as a permanent one. This means that after the fist time, any document that gets created from the template, the toolbar will already exist and will not be necessary to add it again. In that case, the above does not need to and should not be executed. If the code that listens to the Click event of the button was added here, the button for any document created from the template after the first one would not work. Don’t get ahead of yourself and think that if the toolbar is added as a temporary one, then this is not an issue. The toolbar, regardless of whether it is a temporary one or not, is added to a Word application (i.e. process) and therefore if another document is created from the template whilst the first one is still open, by default, it will be created within the same Word application and the toolbar will therefore already exist. So, now that I have convinced you that you should not subscribe to the buttons’ Click event during creation of the toolbar, let’s move on to the code that takes care of this issue

 

private void SubscribeToToolbarEvents(Office.CommandBar toolbar)

{

    // Go through each control in the toolbar

    foreach (Office.CommandBarControl control in toolbar.Controls)

    {

        // If the control is a button

        if (control is Office.CommandBarButton)

        {

            // Cast the control to a button

            Office.CommandBarButton button = (Office.CommandBarButton)control;

           

            // Subscribe to the button's Click event

            button.Click += new Office._CommandBarButtonEvents_ClickEventHandler(

                ThisDocument_Click);

 

            // Add the button to a class scope collection of buttons

            this.toolbarButtons.Add(button);

        }

    }

}

 

This piece of code is pretty straight forward, given a toolbar you go through its buttons and subscribe to its Click event. There is a small detail, however, which might go unnoticed but is of great importance; each button is added to a class scope collection. If this step is not taken, after the first pass of the Garbage Collector the Click event of the buttons will stop firing. This might appear very strange at first, but if you consider that what we are dealing with here is COM Interop, it actually makes sense. The instances of CommandBarButton that we get from accessing a toolbar’s Controls property, are not the actual objects stored in the toolbar; they are just wrappers. As a result, unless you keep a reference to them by adding them to a class scope collection, nothing else has a reference to them and they get garbage collected. The API needs to be as lightweight as possible, so the VSTO team has decided to not keep the wrapper objects around unless they are required; otherwise for every unmanaged object in Word exposed through VSTO would have a managed counterpart making Word’s memory consumption almost double as soon as VSTO is used.

 

Alright, now that we have gone through each individual issue, let’s put the code together

 

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

{

    // See if the toolbar already exists

    Office.CommandBar toolbar = this.GetToolbar(TOOLBAR_NAME);

 

    // If it does not exist

    if (toolbar == null)

    {

        // Add the toolbar

        toolbar = this.AddToolbar();

    }

 

    // Start listening to the events fired by the toolbar and its buttons

    this.SubscribeToToolbarEvents(toolbar);

}

 

 

private Office.CommandBar GetToolbar(string toolbarName)

{

    // Go through each toolbar in the application

    foreach (Office.CommandBar toolbar in this.Application.CommandBars)

    {

        // Iff the toolbar has the specified name

        if (toolbar.Name.CompareTo(toolbarName) == 0)

        {

            // Return it

            return toolbar;

        }

    }

 

    // A toolbar with the specified name was not found, return null

    return null;

}

 

private Office.CommandBar AddToolbar()

{

    // Create the toolbar

    Office.CommandBar toolbar = this.Application.CommandBars.Add(

        "Toolbar name", MsoBarPosition.msoBarTop, missing, false);

 

    // Add a button to it

    Office.CommandBarButton button = (Office.CommandBarButton)

        toolbar.Controls.Add(MsoControlType.msoControlButton, missing,

        missing, missing, false);

 

    // Set the button's style

    button.Style = Office.MsoButtonStyle.msoButtonCaption;

 

    // Set the button's caption

    button.Caption = "Click me";

 

    // Assign a tag value and the Click event will be fired once

    // for every document that is open

    //button.Tag = Guid.NewGuid().ToString();

 

    // Ensure the toolbar is visible, by default it won't be

    toolbar.Visible = true;

 

    // Return the newly created toolbar

    return toolbar;

}

 

private void SubscribeToToolbarEvents(Office.CommandBar toolbar)

{

    // Go through each control in the toolbar

    foreach (Office.CommandBarControl control in toolbar.Controls)

    {

        // If the control is a button

        if (control is Office.CommandBarButton)

        {

            // Cast the control to a button

            Office.CommandBarButton button = (Office.CommandBarButton)control;

           

            // Subscribe to the button's Click event

            button.Click += new Office._CommandBarButtonEvents_ClickEventHandler(

                ThisDocument_Click);

 

            // Add the button to a class scope collection of buttons

            this.toolbarButtons.Add(button);

        }

    }

}

 

Those who pay close attention to detail might have noticed that the variable for the toolbar is not at class scope. The reason is that I have come across problems doing that It appears that there is some issue with the wrapper object and the underlying actual toolbar and after a while using the wrapper to access the toolbar causes issues. The one I have come across is with toolbar.Delete() during document shutdown, which will throw an exception if used on a wrapper object held at class scope, but will work correctly if the toolbar wrapper is created again using the GetToolbar method. I have made it my standard practice now to always use this method instead of keeping a variable at class scope for the toolbar.

 

And there you have it, how to add a toolbar in VSTO 2005.