The Command User Interface
Visual Studio borrows its toolbar and menu system from the Microsoft Office suite of applications. The command bars provide a common user interface experience across all of the Office applications as well as Visual Studio .NET. Because the command bars also support an object model, these applications also share a common programming model for accessing the command bar structure.The main point of access to the command bar objects is through the DTE.CommandBars property. This property returns a Microsoft.Office.Core.CommandBars object, which is defined in the assembly Office.dll. The following macro code demonstrates retrieving this object:
Sub GetCommandBars()
Dim commandBars As Microsoft.Office.Core.CommandBars
commandBars = DTE.CommandBars
End Sub
The Command Bar Object Model
The command bar object model is arranged in a treelike hierarchy, in the same way as the Visual Studio .NET object model is. At the top of this tree is a collection of Microsoft.Office.Core.CommandBar objects that includes all the command bars and shortcut menus and the main menu bar. Each command bar contains a collection of controls that have the type Microsoft.Office.Core.CommandBarControl. Once a CommandBarControl is retrieved, it can be converted into one of three types. The first type, a CommandBarButton, is any item on a command bar that the user can click to perform an action; this is analogous to executing a Visual Studio .NET command. To get to a CommandBarButton object, a cast must be performed from the CommandBarControl object:
Sub GetCommandBarButton()
Dim commandBarBtn As Microsoft.Office.Core.CommandBarButton
Dim commandBarCtl As Microsoft.Office.Core.CommandBarControl
Dim commandBarCtls As Microsoft.Office.Core.CommandBarControls
'Find the View command bar
commandBarCtls = DTE.CommandBars.Item("View").Controls
'Retrieve the first control on the menu
commandBarCtl = commandBarCtls.Item(1)
'Convert the CommandBarControl to a CommandBarButton object
commandBarBtn = CType(commandBarCtl, _
Microsoft.Office.Core.CommandBarButton)
MsgBox(commandBarBtn.Caption)
End Sub
The object returned from the Controls collection can be converted into a CommandBarPopup if the item is the root node of a submenu. An example of this is the New item on the File menu; when the user hovers the mouse cursor over this menu, a submenu appears. You can also retrieve a CommandBarPopup when the item is on a split-button drop-down menu, such as the New Project | New Blank Solution button on the Standard command bar:
Sub GetCommandBarPopup()
Dim commandBar As Microsoft.Office.Core.CommandBar
Dim cmdBarControl As Microsoft.Office.Core.CommandBarControl
Dim cmdBarPopup As Microsoft.Office.Core.CommandBarPopup
'Find the "Standard" command bar
commandBar = DTE.CommandBars.Item("Standard")
'Find the first control on the command bar
cmdBarControl = commandBar.Controls.Item(1)
'Convert the CommandBarControl to a CommandBarPopup
cmdBarPopup = CType(cmdBarControl, _
Microsoft.Office.Core.CommandBarPopup)
MsgBox(cmdBarPopup.Controls.Item(1).Caption)
End Sub
A popup menu is itself a command bar. You can't cast directly to a CommandBar object on a popup menu, but this object does contain a CommandBar property, which returns a CommandBar object, which itself has a collection of controls (as you can see in the next-to-last line in the preceding macro code).
The Primary Command Bar
The DTE.CommandBars property returns the collection of all CommandBar objects available within Visual Studio .NET, but the most commonly used command bar is the main menu. Looking at the menu, you can see the File, Edit, and View items as well as a number of additional menu items; all of these are CommandBar objects within the DTE.CommandBars collection. But because there might be multiple items within the collection with the same name, indexing the collection using the name might not work. For example, there are multiple CommandBar objects with the title View, and you might not always get the one you want if you index the CommandBars collection with the string View. The following macro might return the View command bar for the SQL editor, a deployment project popup menu, or the View menu:
Sub GetView()
Dim cmdbars As Microsoft.Office.Core.CommandBars
Dim commandBar As Microsoft.Office.Core.CommandBar
cmdbars = DTE.CommandBars
commandBar = cmdbars.Item("View")
End Sub
To work around this, you can find the CommandBar object for the menu bar, called MenuBar, and then find the View submenu command bar:
Sub GetMenuCommandBar()
Dim commandBar As Microsoft.Office.Core.CommandBar
Dim cmdBarControl As Microsoft.Office.Core.CommandBarControl
Dim cmdBarPopupView As Microsoft.Office.Core.CommandBarPopup
Dim cmdBarView As Microsoft.Office.Core.CommandBar
'Retrieve the MenuBar command bar
commandBar = DTE.CommandBars.Item("MenuBar")
'Find the View menu
cmdBarControl = commandBar.Controls.Item("View")
'Convert to a CommandBarPopup
cmdBarPopupView = CType(cmdBarControl, _
Microsoft.Office.Core.CommandBarPopup)
'Get the CommandBar object for the view menu
cmdBarView = cmdBarPopupView.CommandBar
MsgBox(cmdBarView.Name)
End Sub
By default, if the Add-in Wizard generates an add-in and the option is selected to place an item on the Tools menu, code is generated to place a menu item on the Tools menu of the menu bar. If you want to move this command user interface to a different menu, you can simply change the string "Tools" to a different menu title; but be careful to select the correct menu. It's easy to make the mistake of selecting the wrong command bar, causing the command button to seemingly disappear because it was placed somewhere that you did not expect it to go.
Adding a New Command Bar User Interface
With a Command object in hand (found by either indexing the Commands collection or adding a new command) and after using the methods described earlier to find the proper command bar, you can add a new button to that command bar that invokes your command when clicked. You do this using the Command.AddControl method. When you add a command using the AddNamedCommand method, that command is persisted to disk and re-created automatically when Visual Studio .NET is next started. Likewise, when you place a control on a command bar using the AddControl method, that control and its placement are saved to disk and re-created when Visual Studio .NET is run. The first argument of the AddControl method is the CommandBar object that the button is to be placed on. The second argument defines the numerical position of the control in relation to the other controls on the command bar. (If this value is 1, the control will be the first item on the command bar, and if the value is 2, it will be the second item, and so forth.)You can hard-code an index to place the control, but the control might not appear where you think it should go in relation to other controls. The reason is that a command bar might have one or more separators (or lines drawn between two controls) that divide controls into logical groups. These groups are also controls on the command bar, and they should be counted when you calculate the position. Not only are group controls counted as items in the index, but so are controls that are not visible because the value vsCommandStatusInvisible is returned from your QueryStatus method. If the control to be added should be placed at the bottom or end of the command bar, you can use the Controls.Count property to determine the final position:
Sub AddControl()
Dim command As EnvDTE.Command
Dim commandBar As Microsoft.Office.Core.CommandBar
'Find the File.OpenFile command
command = DTE.Commands.Item("File.OpenFile")
'Find the Tools CommandBar
commandBar = DTE.CommandBars.Item("Tools")
'Add a control to the Tools menu that when
' clicked will invoke the File.OpenFile command
command.AddControl(commandBar, commandBar.Controls.Count + 1)
End Sub
Note that the index used doesn't fix a control to a particular position. If you add a control to position 1 and a second control is added to position 1, the first control is pushed into the second position.At times, it might make sense to create a new command bar to place your buttons on because the default set of command bars don't suit your needs. The command bar object model allows you to create new command bars, but creating one in this way might not achieve the desired effects. Command bars created in this way are created in a temporary state, which means that when you exit and restart Visual Studio .NET, the command bar will have been destroyed. Because the button user interface for commands persists across instances, you'll want your command bars to also persist across instances. The Visual Studio .NET object model lets you do this, using the Commands.AddCommandBar method, which has this signature:
public object AddCommandBar(string Name, EnvDTE.vsCommandBarType Type, _
Microsoft.Office.Core.CommandBar CommandBarParent = null, int Position = 1)
This method has the following arguments:
Name
The caption to display on the command bar.
Type
A value from the vsCommandBarType enumeration. If the value is vsCommandBarTypeToolbar, a command bar is created that can be docked to the top, left, bottom, or right of the Visual Studio .NET window. If the value is vsCommandBarTypeMenu, the command bar is added as a submenu to another command bar. If the value is vsCommandBarTypePopup, a shortcut menu is created.
CommandBarParent
If the value passed for the Type parameter is vsCommandBarTypeToolbar or vsCommandBarTypePopup, this value should be null or Nothing (depending on the language used). If the value passed to the Type parameter is vsCommandBarTypeMenu, the new menu should be rooted on the command bar object.
Position
This value is necessary only if the Type parameter is set to vsCommandBarTypeMenu. It defines the location on the parent command bar where the new menu command is placed. It has the same meaning as the Position parameter of the AddControl method.
How the newly created command bar is shown to the user depends on the type of command bar that's created. If the command bar type is a new menu, the menu item is hidden from the user until the command bar for that menu item is populated with buttons. If the command bar created is a new toolbar, the Visible property of the returned CommandBar object should be set to True. If a popup menu is created, you can show the menu to the user using the CommandBar.ShowPopup method, which takes two arguments, the x and y coordinates of the top left of where the popup menu should appear.
Using Custom Bitmaps
Visual Studio .NET has a number of predefined bitmaps that you can place on menu items and command bar buttons, but they might not always meet your needs. To use your own bitmap for the image on a button, you must register for your add-in a satellite DLL containing the bitmap in its resources (Chapter 6 has more information on creating satellite DLLs), and you must change the call to Commands.AddNamedCommand so Visual Studio .NET can find your bitmap. First, you should set the AddNamedCommand method's MSOButton parameter to false to tell Visual Studio .NET that the bitmap isn't among the default, built-in pictures but is in the satellite DLL. Second, you should change the Bitmap parameter to the resource ID of the bitmap in your satellite DLL.The bitmap must have a specific format to be usable by Visual Studio .NET. It must be 16 pixels high and 16 pixels wide and must be saved so that it has 16 colors. Visual Studio .NET can also draw the picture so that a portion of it shows as transparent. To enable this, you must make the transparent area have the RGB (red, green, blue) color value of 0, 254, 0. (Note that this color isn't the lime green color displayed in the color palette of the Visual Studio .NET image editor or the Paint application in Windows.)Creating the bitmap so it has the correct size and color depth can be complicated. The CustomBitmap sample shows how this is done. The setup project has been modified to install the satellite DLL containing the custom bitmap into the correct place and to properly register the satellite DLL. When you install the sample using the setup project, you might find that the bitmap that appears on the button for the command on the Tools menu is blank. The reason is that the bitmap file used (in the file CustomBitmap\ CustomBitmapUI\untitled.bmp) is a blank template that uses the transparency color; you can use it as a starting point for creating your own custom bitmaps. The sample also shows how we modified the call to the AddNamedCommand method to reference the custom bitmap. As you can see in the following code, we changed the MSOButton argument from true to false and the Bitmap argument from 59 to 1. (1 is the resource identifier for the bitmap in the satellite DLL.)
Command command = commands.AddNamedCommand(addInInstance, "CustomBitmap",
"CustomBitmap", "Executes the command for CustomBitmap", false, 1,
ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported +
(int)vsCommandStatus.vsCommandStatusEnabled);