Defining and Reacting to Commands

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.

LISTING 15.2 Command Class Generated by the Custom Command Project Item


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).

Editing the 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.

LISTING 15.3 A VSCT File Generated by 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset