If you are an add-in developer, you are probably familiar with adding menu items and toolbar buttons to the Visual Studio environment. In most instances add-in developers take their cue from the code generated by the Visual Studio Add-in Project Wizard.

For example, the following code is generated when you create a new C# add-in project and select the “Create Command Bar UI” check box in the add-in project wizard. 


public
void OnConnection(object application, ext_ConnectMode connectMode, object addInInst,
                         ref Array custom)

{

   _applicationObject = (DTE2)application;

   _addInInstance = (AddIn)addInInst;

   if(connectMode == ext_ConnectMode.ext_cm_UISetup)

   {

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

      Commands2 commands = (Commands2)_applicationObject.Commands;

      string toolsMenuName = "Tools";

 

      // Find the MenuBar command bar, which is the top-level command bar holding all
      // the main menu items:

      CommandBar menuBarCommandBar = ((CommandBars)_applicationObject.CommandBars)["MenuBar"];

 

      // Find the Tools command bar on the MenuBar command bar:

      CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];

      CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;

 

      try

      {

         //Add a command to the Commands collection:

         Command command = commands.AddNamedCommand2(_addInInstance, "My Command", "My Command",

            "Executes My Command", true, 59, ref contextGUIDS,

            (int)vsCommandStatus.vsCommandStatusSupported +

            (int)vsCommandStatus.vsCommandStatusEnabled,

            (int)vsCommandStyle.vsCommandStylePictAndText,

            vsCommandControlType.vsCommandControlTypeButton);

 

         //Add a control for the command to the tools menu:

         if((command != null) && (toolsPopup != null))

            command.AddControl(toolsPopup.CommandBar, 1);

 

      }

      catch(System.ArgumentException)

      {

      }

   }

}



In the code sample above, a new command (“My Command”) is added to the Tools menu. Note how this particular sample retrieves the CommandBar for the “Tools” menu, by way of the Controls collection on the CommandBar representing the main IDE menu named “MenuBar”.

This is the preferred technique for retrieving a CommandBar representing a menu on the main IDE menu, because in theory there could be other CommandBar objects named “Tools” in the DTE.CommandBars collection.


The Problem:


The above mentioned code however, does not account for when the user manually removes the “Tools” menu. Nor does it take into consideration that some add-in or Visual Studio Package may have added an identically named toolbar or menu to the environment.

Consider the following Visual Studio macro:


    Sub PrintCmdBarNames()

        Dim ow As OutputWindow = DTE.Windows.Item(Constants.vsWindowKindOutput).Object

        Dim pane As OutputWindowPane

 

        Try

            pane = ow.OutputWindowPanes.Item("CommandBar Names")

        Catch ex As Exception

            pane = ow.OutputWindowPanes.Add("CommandBar Names")

        End Try

 

        pane.Clear()

 

        Dim nameList As New List(Of String)

        For Each cmdBar As CommandBar In DTE.CommandBars

            nameList.Add(cmdBar.Name)

        Next

 

        nameList.Sort()

 

        For Each name As String In nameList

            pane.OutputString(name & vbCr)

        Next

 

    End Sub

When run, the above macro displays a sorted list of CommandBar names, contained in the DTE.CommandBars collection. Note, that many of the names listed are duplicated.

Given that CommandBars can have identical names; that users may change the Visual Studio IDE’s menu layout; and third parties can add additional menus with identical names to the IDE, it becomes very difficult to retrieve a specific CommandBar from the DTE.CommandBars collection.


The Solution:


If you are a Visual Studio Package developer, you are probably familiar with using .CTC files to add new menus, toolbars and commands to the Visual Studio IDE. For the uninitiated or curious, please refer to the documentation and samples included with the Visual Studio SDK. To briefly summarize, each command, menu and toolbar added via a .CTC file, is uniquely identified by a GUID:ID pair. This GUID:ID pair can be used with the IVsProfferCommands.FindCommandBar method, to retrieve a specific CommandBar object.

Finding the GUID:ID pair for a particular menu or command can be a hit or miss affair. You could search the various .IDL, .CTC, and .H files included with the Visual Studio SDK, or post a message in the MSDN Extensibility Forum and hope for a response. But with Visual Studio SP1, you can now use the undocumented but extremely useful EnableVSIPLogging registry value to help find the GUID:ID pair that indentifies the specific menu or command you are interested in. You can learn more about the EnableVSIPLogging registry value in the Dr. eX blog post entitled “Using EnableVSIPLogging to identify menus and commands with VS 2005 + SP1”.

The IVsProfferCommands interface is defined in the EnvDTE namespace, but is marked for “Microsoft Internal Use Only”. However, given the need for a solution to the problem defined above, and after consulting with the Visual Studio development team, Dr. eX, and the VS IDE development team feel  comfortable recommending this as a viable solution for both add-in and package developers.

The IVsProfferCommands interface can be retrieved by calling QueryService on the DTE object’s IOleServiceProvider interface. Note this IOleServiceProvider is the OLE/COM equivalent of the .Net IServiceProvider. However, these two interfaces are different, and you need to use the OLE/COM version of this interface to successfully retrieve the IVsProfferCommands service. This interface is defined in the Microsoft.VisualStudio.OLE.Interop.dll assembly that ships with the Visual Studio SDK. But you can readily define this interface yourself, without having to download the Visual Studio SDK.

For example:


[

  ComImport,

  Guid("6D5140C1-7436-11CE-8034-00AA006009FA"),

  InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)

]

internal interface IOleServiceProvider

{

   [PreserveSig]

   int QueryService([In]ref Guid guidService, [In]ref Guid riid,
      [MarshalAs(UnmanagedType.Interface)] out System.Object obj);

}

 

Then it is just a matter of calling QueryService to retrieve the IVsProfferCommands interface, and then invoking IVsProfferCommands.FindCommandBar to retrieve the CommandBar you are interested in.

For example:


private
CommandBar FindCommandBar(Guid guidCmdGroup, uint menuID)

{

   // Make sure the CommandBars collection is properly initialized, before we attempt to

   // use the IVsProfferCommands service.

   CommandBar menuBarCommandBar = ((CommandBars)_applicationObject.CommandBars)["MenuBar"];

 

   // Retrieve IVsProfferComands via DTE's IOleServiceProvider interface

   IOleServiceProvider sp = (IOleServiceProvider)_applicationObject;

   Guid guidSvc = typeof(IVsProfferCommands).GUID);

   Object objService;

   sp.QueryService(ref guidSvc, ref guidSvc, out objService);

   IVsProfferCommands vsProfferCmds = (IVsProfferCommands)objService;

   return vsProfferCmds.FindCommandBar(IntPtr.Zero, ref guidCmdGroup, menuID) as CommandBar;

}

 

An example add-in using this code is can be found on in the GotDotNet Code Gallery as FindCommandBar.zip.

Happy Coding,
Dr. eX.