Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Creating a CommandBar and adding Commands to it in a VS.NET Add-in


:P
On this page:

A quick summary for creating a CommandBar and attaching commands to it

 

I’ve been spending a bunch of time trying to get everything working correctly for my Html Help Builder Add-In and I thought I’d summarize the part that’s been the most painful about this plug-in which has been getting the damn menu to behave correctly.

 

There are still some open issues, but at least at this point I have a consistent and stable menu structure that properly cleans up after itself.

 

Let’s start with some basic terminology. VS.NET 2003 uses the Office CommandBar model to create its menus and extend them. This model sucks and is documented really badly, but that’s what we’re stuck with for now. Every menu item is based on an underlying Command object that contains the base functionality that is then used to create a CommandBarControl of some sort that actually binds this Command to a real control that has display attributes. So a Command is sort of the Interface for the control, and the control is the actual instance.

 

There are a bunch of different controls available. The most common are CommandBarButtons which are most of the menu items you see on menus. Buttons that point at submenus are CommandBarPopups, which correspond to a CommandBar object actually. In this entry I’ll talk about these two types of objects which are used to create a new CommandBar, attach it to one of the top level menu pads (Help in this case).

 

To get a reference to a CommandBar you use the following Syntax:

 

CommandBar commandBarHelp = (CommandBar) commandBars["Help"];

 

Where Help is the name of the CommandBar object – in this case the Help menu popup. The CommandBar object contains a Controls collection that holds the actual items for that popup. Most of these controls will be CommandBarButtons. Now I want to add a menu to this popup.

 

Let’s start with the two ways to deal with commandBar object creation. One way is to create a permanent CommandBar which is added as a ‘named’ bar that Visual Studio remembers:

 

CommandBar commandBar = applicationObject.Commands.AddCommandBar(

HelpBuilderVsAddin.WWHELP_APPNAME,

      vsCommandBarType.vsCommandBarTypeMenu,commandBarHelp,InsertionIndex)

      as CommandBar;

     

Permanent menu pads are remembered by Visual Studio so you don’t have to reload them each time VS starts – it will load up the pads automatically and route clicks to your handlers.

 

If you create your add-in to hook up with:

 

if(connectMode == Extensibility.ext_ConnectMode.ext_cm_UISetup)

 

then the above should work well. The thing to remember with named Commands is that you can’t add them more than once – if they already exist VS will give an error, so you have to be very careful to clean up after yourself when the Add-In uninstalls.

 

However, I decided that in my application the add-in could install uninstall completely through external means and I ended up seeing inconsistencies with ext_cm_UISetup modes where the menus would not always show up properly. Instead I decided to hook menus every time VS starts which has been more reliable. So I hook my addin setup with:

 

if(connectMode == Extensibility.ext_ConnectMode.ext_cm_Startup )

 

and add my menus when VS starts every time. To do this create a menu bar that is not a named menu bar with the following code:

 

CommandBarControl commandBarPopup =

      commandBarHelp.Controls.Add(MsoControlType.msoControlPopup, 1,"",

                                            InsertionIndex,true);

 

commandBarPopup.Caption = HelpBuilderVsAddin.WWHELP_APPNAME;

commandBarPopup.BeginGroup = true;

commandBarPopup.Visible = true;

 

// *** Must retrieve the Command Bar Object directly via COM - not provided on the object itself

CommandBar commandBar =

   commandBarPopup.GetType().InvokeMember("CommandBar",

                        BindingFlags.Instance | BindingFlags.GetProperty,

                        null,commandBarPopup,null) as CommandBar;

 

Note that I add a new control to the Help menu as a Popup (which gets the > and dropdown), which gives me a CommandBarPopup. Now my problem here had been how to retrieve the CommandBar object that underlies this popup, which for some unknown reason is NOT exposed by the Interop wrapper. The key is that the COM object exposes a CommandBar property that the VS.NET Interop assembly doesn’t expose, which means you have to use Reflection to retrieve the CommandBar.

 

Once you have the command bar you can now start adding commands.

 

object []contextGUIDS = new object[];
IconId = 0;  // No Icon

Command command = this.DTE.Commands.AddNamedCommand(

            this.addInInstance,

            "ShowHelpBuilder","Show Html Help Builder","Show Html Help Builder",

            IconId == 0 ? true : false, IconId,

            ref contextGUIDS,

            (int)vsCommandStatus.vsCommandStatusSupported+

            (int)vsCommandStatus.vsCommandStatusEnabled);

 

Once you have the Command you can create a control and attach it to a specific CommandBar:

InsertionIndex = 1;

CommandBarControl cb = command.AddControl(commandBar,InsertionIndex); 

 

The insertion index is index where the control is to be inserted. The control is inserted before the control that matches the index. If the index is too large the control goes at the end. If you want to find a specific control to insert after you can use commandBar.Controls to either find the count or look for a specific control via its caption or control Id. Most of the VS.NET built-in controls have a fixed ID that you can reference for example. For example I search for the insertion index of the Help Menu’s Technical Support link like this:

 

// *** Add the menu items on the bottom of the Help menu after the Tech Support link

int InsertionIndex = 14; // default location

 

// *** Try to find the TechSupport ID (815)

for( x =1 ; x < commandBarHelp.Controls.Count; x++)

{

      if (commandBarHelp.Controls[x].Id == HelpBuilderVsAddin.WWHELP_HELPMENU_PREVITEM_ID ) // Technical Support Item  // Caption.StartsWith("&Technical Support");

      {

            x++;

            InsertionIndex = x;

            break;

      }

}

 

Originally I searched by caption but this can cause problems if you’re running an international version of VS, so that’s not safe. Using an ID is a better choice. Be aware though that not all IDs are unique as some buttons map to a single command Id.

 

Since my Add-In is adding a bunch of new Commands I created a more generic routine that creates a new Command and Control in one pass with options for the most common things to set like Hotkeys and group separators. The routine takes a bunch of input parameters, but reduces the overall process to a single line of code with easy to understand parameters. It also deals with the situation where a command already exists and handles that case which would otherwise cause VS to throw an exception.

 

/// <summary>

/// Adds a new Command and creates a new CommandBar control both of which

/// can be returned via the AddCommandReturn object that holds refs to both.

/// </summary>

/// <param name="Name">The name of the Command. Must be handled in the Addin</param>

/// <param name="Caption">The Caption</param>

/// <param name="Description">Tooltip Text</param>

/// <param name="IconId">Icon Id if you use a custom icon. Otherwise use 0</param>

/// <param name="commandBar">The Command bar that this command will attach to</param>

/// <param name="InsertionIndex">The InsertionIndex for this CommandBar</param>

/// <param name="BeginGroup">Are we starting a new group on the toolbar (above)</param>

/// <param name="HotKey">Optional hotkey. Format: "Global::alt+f1"</param>

/// <returns>AddCommandReturn object that contains a Command and CommandBarControl object that were created</returns>

public AddCommandReturn AddCommand(string Name,string Caption, string Description,

      int IconId,CommandBar commandBar, int InsertionIndex,

      bool BeginGroup, string HotKey)

{

      object []contextGUIDS = new object[] { };

     

      // *** Check to see if the Command exists already to be safe

      string CommandName = this.addInInstance.ProgID + "." + Name;

      Command command = null;

      try

      {

            command = this.DTE.Commands.Item(CommandName,-1);

      }

      catch {;}

     

      // *** If not create it!

      if (command == null)

      {

            command = this.DTE.Commands.AddNamedCommand(

                  this.addInInstance,

                  Name,Caption,Description,

                  IconId == 0 ? true : false, IconId,

                  ref contextGUIDS,

                  (int)vsCommandStatus.vsCommandStatusSupported+

                  (int)vsCommandStatus.vsCommandStatusEnabled);

 

            // *** If a hotkey was provided try to set it

if (HotKey != null && HotKey != "")

            {                            

                  object [] bindings = (object [])command.Bindings ;

                  if (bindings != null)

                  {

                        bindings = new object[1];

                        //bindings[0] = (object)"Windows Forms Designer::alt+f1";

                        bindings[0] = (object) HotKey;

                        try

                        {

                              command.Bindings = (object) bindings;

                        }

                        catch(Exception ex) { string t = ex.Message; }

                  }

            }

      }

      CommandBarControl cb = command.AddControl(commandBar,InsertionIndex);

      cb.BeginGroup = BeginGroup;

 

      return new AddCommandReturn(command,cb);

}

 

I use a class to return the Command and Control objects so that they can be further used or modified. Commands are mostly read only once created but you might want to use a single command for multiple controls. The class looks like this:

 

/// <summary>

/// Class used to return multiple return values for the AddCommand method.

/// Returns both the Command and CommandBarControl objects so the client

/// code can further customize those objects.

/// </summary>

public class AddCommandReturn

{

      public AddCommandReturn(Command cmd, CommandBarControl ctrl)

      {

            this.Command = cmd;

            this.Control = ctrl;

      }

      public Command Command = null;

      public CommandBarControl Control = null;

}

 

For example in Html Help Builder various command end up on several of the Context menus .

 

// *** The various Context menus we will also attach to

// *** Selection: WinForm selection menu, Container Menu for Forms, Code Window for source

CommandBar ContextBar = (CommandBar) commandBars["Selection"];

CommandBar ContainerBar = (CommandBar) commandBars["Container"];

CommandBar CodeWindowBar = (CommandBar) commandBars["Code Window"];

 

 

// *** Context Menu Update Option

AddCommandReturn Return  = this.AddInHelper.AddCommand("UpdateFromHelpBuilderContext",

"Update from Html Help Builder",

      "Updates the Control's HelpString from Html Help Builder's current topic",

      101,ContextBar,ContextInsertionIndex,false"Global::ctrl+f1");

 

command = Return.Command;

command.AddControl(ContainerBar,ContainerInsertionIndex);

command.AddControl(CodeWindowBar,CodeWindowInsertionIndex);

 

This wrapper reduces the code considerably and makes it much easier to read the OnConnection() routine (or whatever method you route to create your menu) in the startup of the Add-In.

 

BTW, notice the code for the Hotkey. It uses Command Bindings to attach a hotkey. This will work only if you are not using the default keyboard scheme in VS.NET. The default scheme is Read Only and doesn’t allow attachment of new keys so a copy is required first. You can do this by going into Settings, Tools | Options | Keyboard and using the Save As… option to copy the default scheme. I’m not aware of a way to do this programmatically, but it’s probably doable that way too, although this is probably not a great choice without asking the user first.

 

The above approach manages Commands smartly so this will work regardless of whether you install Commands once during configuration, or whether you re-create the Commands each time VS.NET starts. I prefer to do this actually because it gives more flexibility and doesn’t appear to cause anything in the way of overhead. If you do want to rebuild the Commands on each start make sure that you remove them in the OnDisconnection:

 

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode,

                            ref System.Array custom)

{

      if(disconnectMode == Extensibility.ext_DisconnectMode.ext_dm_HostShutdown |

            disconnectMode == Extensibility.ext_DisconnectMode.ext_dm_UserClosed)

      {

            // *** Make sure we always remove all commands

            this.AddInHelper.RemoveCommand("HelpBuilder.vsAddin.ShowHelpBuilder");

            this.AddInHelper.RemoveCommand("HelpBuilder.vsAddin.UpdateFromHelpBuilder");

            this.AddInHelper.RemoveCommand("HelpBuilder.vsAddin.ShowHelpBuilderContext");

           

      }

}

 

where the RemoveCommand method looks like this:

 

public void RemoveCommand(string Command)

{

      Command cmd = null;

      try

      {

            cmd = this.DTE.Commands.Item(Command,-1);

      }

      catch{;}

 

      if (cmd != null)

            cmd.Delete();

}

 

The wrapper here saves you from having to handle the Exception handling in your client code. Again, this applies only if you add commands on every startup in your OnConnection code. Otherwise simply leave the command in place and let AddCommand() reuse it next time around.

 

Command Browsing Tip

When working with Commands and Controls and trying to figure out what the names of menus and child menus are it’s often useful to see a list of the available Commands and Controls. You can use a variation of the following method to scan through all Commands and CommandBars/Controls. The code below will copy the entire command list and all CommandBars and their control captions and Ids to the clipboard. If you pass in a Caption name it will also try to locate the parent CommandBar, so you if you see a ShortCut menu you like to use but don’t know the name to just pass in the name of the caption exactly as it appears on the shortcut menu – don’t forget about the & for shortcut hotkeys.

 

Simply call this method from your startup code or from an debug menu option:

 

/// <summary>

/// Helper function that can be used to find which command bar a command lives on

/// Pass in the caption and it will tell all commandbar names it lives on.

/// </summary>

/// <param name="SearchCaption"></param>

private void ShowCommandBarByName(string SearchCaption)

{

      System.Text.StringBuilder sb = new System.Text.StringBuilder();

 

      foreach(Command oCommand in applicationObject.Commands)

      {

            if (oCommand.Name != null &&  oCommand.Name.Trim() != "")

                  sb.Append(oCommand.Name + "\r\n");

      }

 

      sb.Append("\r\n---------------------------------------------------------\r\n");

 

      foreach(CommandBar oCommandBar in this.applicationObject.CommandBars)

      {

            sb.Append(  oCommandBar.Name + "\r\n" );

 

            foreach(CommandBarControl oControl in oCommandBar.Controls)

            {

                  if (oControl == null || oControl.Caption.Trim() == "")

                        continue;

 

                  sb.Append(  "\t" + oControl.Caption + " - " +

                              oControl.Id.ToString()  + "\r\n" );

                  if(SearchCaption == oControl.Caption)

                  {

                        MessageBox.Show("ComamndBar: " + oCommandBar.Name +

                                                "\r\nID: " + oControl.Id.ToString());                       }

            }

      }

 

      Clipboard.SetDataObject( sb.ToString() );

}

 

This dumps the names of all commands and all controls and their IDs to the clipboard. You can paste this into a text editor and easily see what menu options are available. The VS.NET Samples include a Command Browser that will let you do the same sort of thing interactively, but this feature is still nice because you can hide this somewhere in your app and trigger it optionally on client machines for debugging if your menus aren’t working…

 

None of this is big news I suppose, but I needed to write this up so I can remember it all next time I need to write an add-in. <g> This stuff is so woefully underdocumented because technically the menu/toolbar system belongs to Office and the Dev Tools, yet neither is documenting it as part of the immediate product SDKs which is really a pain. What little documentation there is incomplete and scattered about all over the place.

 

And finally I also want to thank Carlos Quintero, who helped me tremendously and whose VS.NET add-in MSDN KB entries were of most help in solving my problems however simple they might have been.

 

 


The Voices of Reason


 

OdeToCode Link Blog
August 17, 2004

# Command Bars in VS.NET 2003


Avi
February 11, 2005

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

Hi,
1st of all - amazing article!

2nd, i tried your code and everything is going fine except that the new Command displayed with no text...
the result is -
Help
-> PopUpCaption
-> blank command...

can u help me please?
Avi.

Russell Garner
February 25, 2005

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

The line if (commandBarHelp.Controls[x].Id == HelpBuilderVsAddin.WWHELP_HELPMENU_PREVITEM_ID ) is at first glance a comfortable way of dealing with things, however, this is not the full story.

These Id's are *not* fixed. I wanted to add an item on the project context menu below A&dd and to this end looked for the Id, which is 33077 on my box. However, on the machine next to me it is 33079, and on my laptop it is 33070.

I've had a look through the rest of the properties the control exposes, and it's not good news - there's no obvious way of spotting a menu item which represents the same menu item across multiple installs of VS.NET. For now, I'm using a combination of the caption and looking for any Id that starts 33000 :)

In your case, though, Rick, it seems likely that most installations of your product may be defaulting to position 14 ;)

Rick Strahl
February 25, 2005

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

Russell, you're right.

In fact, I found that out a while ago as well. I ended up looking for captions, which is lame as it won't work for localized versions, but better than nothing. I couldn't find another way to do it eventually.

Russell Garner
February 28, 2005

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

Oh, and I forgot to mention - thanks. Without this article I wouldn't have bothered even to get started. You are right - the MSDN docs on extensibility are very poorly written.

Evergod
March 13, 2005

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

Just a comment. I have been trying to figure this problem out for a while now, that my commands would get added to my command bar, but they would never show in my actually command bar popup. What documentation does not seem to tell, (because it may just be really dumb and I should have figured the obviousness of this out a long time ago), that you have to add status checks for your commands into the QueryStatus override of the addin.

IE:

if ( commandName == "Your command's id" )
status = (vsCommandStatus) vsCommandStatus.vsCommandStatusSupported|vsCommandStatus.vsCommandStatusEnabled;

anonymous
May 09, 2005

# VSIP

Microsoft now allows you to debug your vs add-in.
http://msdn.microsoft.com/vstudio/extend/

cheers

Hemant
July 12, 2005

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

Hai,
When I try to set the Shortcut Key (Hotkey), I 'm getting the error as "Unspecified Error". I followed your method but it isn't helping me either. The following is the method i adopted to set the Hotkey

object[] bindings = (object [])cmd.Bindings;

if (bindings != null)
{
bindings = new object[1];
bindings[0] = (object) shortcutKey;

try
{
EnvDTE.Properties props = applicationObject.get_Properties("Environment", "Keyboard");
EnvDTE.Property prop = props.Item("Scheme");

string oldVSK = prop.Value.ToString();
string newVSK = "ExpressCoder.vsk";
prop.Value = newVSK;
cmd.Bindings = (object) bindings;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}

Jdang
June 19, 2006

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

I tried to copy your ideal to load the popupMenu in ext_ConnectMode.ext_cm_Startup not ext_ConnectMode.ext_cm_UISetup.
Before to add a command into commands object, I checked if the item is exist with try and catch block:

try {
command = commands.Item(commandName, -1);
}
catch (Exception ex){
if (command == null)
command = commands.AddNamedCommand2
}

Everytime I run, command is always =null and throws "Value does not fall within the expected range." Do you know why?

Regards,
JDang




Tod1d's Thought Process
October 08, 2006

# Tod1d's Thought Process - C#, ASP.NET, Object Oriented Programming


daniel
October 13, 2006

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

Hi, great article!

I was wondering if you could help me with retrieving custom commandbar buttons. I added two custom buttons in the VS built-in "Code Window" commandbar (using AddNamedCommand and AddControl) and I want to be able later to configure their visibility. But I found no way to exactly identify a button as being mine. I don't want to use the Caption property due to localization reasons. I also cannot use the Id property, because both buttons have the same value. On the other hand, I found that the Tag and Tooltip properties are reset on subsequent VS starts. So, any ideas?

Daniel Ion

mob's dev blog
July 12, 2007

# mob's dev blog - Visual Studio 2005 AddIn Resources


Venkat
September 08, 2007

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

Hi Rick,

Thanks a lot.

Your Article helped me great. I spent 5 hrs without success to create a CommandBar before reading this article.

the Idea
    CommandBar cBar = cmc.GetType().InvokeMember("CommandBar", BindingFlags.Instance | BindingFlags.GetProperty, null, cmc, null) as CommandBar;
.

is really wonderfull.

Venkat

Rick Strahl
October 05, 2007

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

A detailed link on step by step guide to add Toolbar icons:

http://support.microsoft.com/kb/555417

Chathuranga Wijeratna
June 05, 2010

# re: Creating a CommandBar and adding Commands to it in a VS.NET Add-in

I have given up getting the behavior of the VS menu item the way I want, when I come across your blog entry. A great detailed explanations.

Thanks a load.
Chathuranga Wijeratna

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024