One of the primary ways you interact with a package-based extension is by issuing it a command (perhaps via a Visual Studio menu, or a toolbar button press event). A command, in the VSPackage environment, is nothing more than a message to the extension that triggers an action. For example, if we have an extension that computed the total lines of executable code within a code editor window, we would probably trigger that computation via a command. Or, if our extension was capable of printing out a file loaded into Visual Studio, we would initiate the printing via a print
command.
Listing 15.2 shows the command file code that was generated with our "Hello, World"
custom command project.
using System;
using System.ComponentModel.Design;
using System.Globalization;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
namespace HelloWorldExtension
{
/// <summary>
/// Command handler
/// </summary>
internal sealed class MyCommand1
{
/// <summary>
/// Command ID.
/// </summary>
public const int CommandId = 0x0100;
/// <summary>
/// Command menu group (command set GUID).
/// </summary>
public static readonly Guid MenuGroup =
new Guid("00527dbc-b05f-4ba5-b30b-b780795bbcf6");
/// <summary>
/// VS Package that provides this command, not null.
/// </summary>
private readonly Package package;
/// <summary>
/// Initializes a new instance of the <see cref="MyCommand1"/>
/// class.
/// Adds our command handlers for menu (commands must exist
/// in the command table file)
/// </summary>
/// <param name="package">Owner package, not null.</param>
private MyCommand1(Package package)
{
if (package == null)
{
throw new ArgumentNullException("package");
}
this.package = package;
OleMenuCommandService commandService =
this.ServiceProvider.GetService(
typeof(IMenuCommandService)) as OleMenuCommandService;
if (commandService != null)
{
CommandID menuCommandID =
new CommandID(MenuGroup, CommandId);
EventHandler eventHandler =
this.ShowMessageBox;
MenuCommand menuItem =
new MenuCommand(eventHandler, menuCommandID);
commandService.AddCommand(menuItem);
}
}
/// <summary>
/// Gets the instance of the command.
/// </summary>
public static MyCommand1 Instance
{
get;
private set;
}
/// <summary>
/// Gets the service provider from the owner package.
/// </summary>
private IServiceProvider ServiceProvider
{
get
{
return this.package;
}
}
/// <summary>
/// Initializes the singleton instance of the command.
/// </summary>
/// <param name="package">Owner package, not null.</param>
public static void Initialize(Package package)
{
Instance = new MyCommand1(package);
}
/// <summary>
/// Shows a message box when the menu item is clicked.
/// </summary>
/// <param name="sender">Event sender.</param>
/// <param name="e">Event args.</param>
private void ShowMessageBox(object sender, EventArgs e)
{
//Show a MessageBox to prove we were here
IVsUIShell uiShell =
(IVsUIShell)Package.GetGlobalService(typeof(SVsUIShell));
Guid clsid= Guid.Empty;
int result;
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(
uiShell.ShowMessageBox(
0,
ref clsid,
"MyCommand1Package",
"Hello, World!",
string.Empty,
0,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
OLEMSGICON.OLEMSGICON_INFO,
0, //false
out result));
}
}
}
Looking at Listing 15.2: within the constructor routine, there is a block of code that effectively links a callback event to our command, allowing us to respond when the command menu item is clicked.
First, a command handler is created via the OleMenuCommandService
. You can find more info on this object in MSDN, but it is essentially a managed class that shell extensions use to add menu command handlers and define “verbs” for those menu commands. Let’s look at the code:
//Add our command handlers for menu
//(commands must exist in the .vsct file)
OleMenuCommandService mcs = GetService(
typeof(IMenuCommandService)
) as OleMenuCommandService;
Assuming that we have managed to obtain a valid OleMenuCommandService
object, the routine then creates a reference to the menu command itself with the following code:
//Create the command for the menu item.
CommandID menuCommandID = new CommandID(
GuidList.guidMyFirstPackageCmdSet,
(int)PkgCmdIDList.cmdidMyCommand);
The CommandID
object is created using a GUID and an ID that uniquely represent that command. In other words, the GUID and ID used in conjunction are a key that uniquely identifies the command; the CommandID
object is best understood as a wrapper for that key.
To define a command for our package, we need to modify something called the Visual Studio Command Table (VSCT).
As we have already discussed, a command is nothing more than a trigger that causes the extension to do something. The VSCT shows a list of the commands supported by the extension and their corresponding definitions. Commands are fairly useless without a way to trigger them. The VSCT also contains information about how commands are exposed within the IDE (typically as a menu item or a toolbar button).
Physically, the VSCT is implemented as an XML file with a .vsct
extension. This file is created automatically when you use the package wizard to generate your project. Listing 15.3 contains the command table XML that was generated for us as a result of completing the package wizard.
<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!--This is the file that defines the actual layout and type
of the commands. It is divided in different sections
(e.g. command definition, command placement, ...), with
each defining a specific set of properties. See the
comment before each section for more details about how to
use it. -->
<!--The VSCT compiler (the tool that translates this file
into the binary format that Visual Studio will consume)
has the ability to run a preprocessor on the vsct file;
this preprocessor is (usually) the C++ preprocessor, so
it is possible to define includes and macros with the
same syntax used in C++ files. Using this ability of the
compiler here, we include files defining some of the
constants that we will use inside the file. -->
<!--This is the file that defines the IDs for all the commands
exposed by Visual Studio. -->
<Extern href="stdidcmd.h"/>
<!--This header contains the command ids for the menus provided
by the shell. -->
<Extern href="vsshlids.h"/>
<!--The Commands section is where the commands, menus, and menu
groups are defined. This section uses a Guid to identify
the package that provides the command defined inside it. -->
<Commands package="guidMyFirstPackagePkg">
<!--Inside this section we have different subsections: one
for the menus, another for the menu groups, one for the
buttons (the actual commands), one for the combos, and the last
one for the bitmaps used. Each element is identified by a
command id that is a unique pair of guid and numeric identifier;
the guid part of the identifier is usually called "command set"
and is used to group different commands inside a logically related
group; your package should define its own command set in order to
avoid collisions with command ids defined by other
packages. -->
<!--In this section you can define new menu groups. A menu group
is a container for other menus or buttons (commands);
from a visual point of view you can see the group as the
part of a menu contained between two lines. The parent of a
group must be a menu. -->
<Groups>
<Group guid="guidMyFirstPackageCmdSet" id="MyMenuGroup"
priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
</Group>
</Groups>
<!--Buttons section. -->
<!--This section defines the elements the user can interact with,
like a menu command or a button or a combo box in a toolbar. -->
<Buttons>
<!--To define a menu group you have to specify its ID, the parent
menu, and its display priority. The command is visible and enabled
by default. If you need to change the visibility, status, etc., you
can use the CommandFlag node.
You can add more than one CommandFlag node e.g.:
<CommandFlag>DefaultInvisible</CommandFlag>
<CommandFlag>DynamicVisibility</CommandFlag>
If you do not want an image next to your command, remove the Icon
node /> -->
<Button guid="guidMyFirstPackageCmdSet" id="cmdidMyCommand"
priority="0x0100" type="Button">
<Parent guid="guidMyFirstPackageCmdSet" id="MyMenuGroup" />
<Icon guid="guidImages" id="bmpPic1" />
<Strings>
<ButtonText>My First Package Command</ButtonText>
</Strings>
</Button>
</Buttons>
<!--The bitmaps section is used to define the bitmaps that are used for
the commands.-->
<Bitmaps>
<!--The bitmap id is defined in a way that is a little bit
different from the others: the declaration starts with a guid
for the bitmap strip, then there is the resource id of the
bitmap strip containing the bitmaps and then there are the
numeric ids of the elements used inside a button definition.
An important aspect of this declaration is that the element id
must be the actual index (1-based) of the bitmap inside the
bitmap strip. -->
<Bitmap guid="guidImages" href="ResourcesImages.png"
usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"/>
</Bitmaps>
</Commands>
<Symbols>
<!--This is the package guid. -->
<GuidSymbol name="guidMyFirstPackagePkg"
value="{4f99ea1f-b906-4e30-a40a-26f217a6b9ab}" />
<!--This is the guid used to group the menu commands together -->
<GuidSymbol name="guidMyFirstPackageCmdSet"
value="{25e4d809-1d51-4bc4-b35c-d54e82d71907}">
<IDSymbol name="MyMenuGroup" value="0x1020" />
<IDSymbol name="cmdidMyCommand" value="0x0100" />
</GuidSymbol>
<GuidSymbol name="guidImages"
value="{e1e9e76f-29b9-43b0-b2e2-80d7cdea6bc3}" >
<IDSymbol name="bmpPic1" value="1" />
<IDSymbol name="bmpPic2" value="2" />
<IDSymbol name="bmpPicSearch" value="3" />
<IDSymbol name="bmpPicX" value="4" />
<IDSymbol name="bmpPicArrows" value="5" />
<IDSymbol name="bmpPicStrikethrough" value="6" />
</GuidSymbol>
</Symbols>
</CommandTable>
The Symbols and the Commands nodes are the two important parts of the file to pay attention to. Within the Symbols node, we set up the unique globally unique identifiers (GUIDs) and IDs that are used to refer to various elements of our commands. Each of these is represented using an IDSymbol
element within the VSCT. In Listing 15.2, you can clearly see multiple IDSymbol
entries created for a variety of things including icons, menu groups, and actual commands.
The Commands node defines the commands themselves, including how they are displayed within the Visual Studio UI.
Because the Symbols node ends up defining the keys to our commands (and to other items referenced within the file), we have to start the processing of adding a command by first adding its compound key to the Symbols node.
Then we add corresponding entries into the Commands node to configure menus, buttons (which are best thought of as menu items), combos (combo boxes), bitmaps (icons to be associated with the command), and groups (logical groupings of different commands).
We’ll tie all this together in the sample extension project later in the chapter. For now, understand that adding a command to your extension will always involve the following steps:
1. Define the GUID/ID composite key for your command by adding an IDSymbol
element into the VSCT file. You will need a separate IDSymbol entry for every command, menu, group, and so on.
2. Define the UI for the command by adding an appropriate entry in the Commands node within the VSCT file. For example, to expose your command via a button (for example, a menu item or button on a toolbar), create a Button
element. If you also want to attach an icon to your command UI, you would define a Bitmap
element within the Bitmaps node.
3. Implement the code to execute the command. Add code to the MenuItemCallback
routine.