Walkthrough: Implementing a Shortcut Menu in a Tool Window

This walkthrough builds on Walkthrough: Using the Gradient Service in a Tool Window and shows how to add support for a shortcut menu so that the tool window button you create cycles through available gradients.

A shortcut menu is a menu that appears when a user right-clicks a graphical user interface (GUI) element such as a button, text box, or window background. Commands on a shortcut menu behave the same as commands on other menus or toolbars. To support a shortcut menu, specify it in the XML Command Table (.vsct) file and display it in response to the right-click of the mouse.

For more information about menus and the .vsct file, see Menus and Toolbars.

For more information about services in Visual Studio, see Services.

Note

Beginning with Visual Studio 2008 SDK, use XML Command Table (.vsct) files instead of command table configuration (.ctc) files to define how menus and commands appear in your VSPackages. For more information, see Visual Studio Command Table (.Vsct) Files.

Prerequisites

This walkthrough requires the Visual Studio SDK to be installed. The result of this walkthrough writes information to the experimental registry hive for Visual Studio.

Creating the Tool Window Shortcut Menu Package

To create the MyTWGradientPackage VSPackage

Specifying the Shortcut Menu

A shortcut menu as shown in this walkthrough lets the user select from a list of eight gradient types that are used to fill the background of the tool window. When you right-click in the tool window, the shortcut menu is displayed and shows the available gradient types. The current gradient type is shown by a check mark.

To create the shortcut menu

  1. In Solution Explorer, double-click the MyTWGradientPackage.vsct file to open it in the editor.

  2. In the <Symbols> section, in the <GuidSymbol> node named "guidTWGradientPackageCmdSet", declare your shortcut menu, shortcut menu group, and eight menu options, as follows.

    <IDSymbol name="MyContextMenu" value="0x1000"/>
    <IDSymbol name="MyContextGroup" value="0x1030"/>
    <IDSymbol name="cmdidGradientFileTab" value="0x0102"/>
    <IDSymbol name="cmdidGradientPanelBackground" value="0x0103"/>
    <IDSymbol name="cmdidGradientShellBackground" value="0x0104"/>
    <IDSymbol name="cmdidGradientToolboxHeading" value="0x0105"/>
    <IDSymbol name="cmdidGradientToolTab" value="0x0106"/>
    <IDSymbol name="cmdidGradientToolWindowActiveTitleBar" 
          value="0x0107"/>
    <IDSymbol name="cmdidGradientToolWindowInactiveTitleBar" 
          value="0x0108"/>
    <IDSymbol name="cmdidGradientToolWindowBackground" value="0x0109"/>
    
  3. Just before the <Groups> section, create a <Menus> section and then define your shortcut menu in it.

    <Menus>
      <Menu guid="guidTWGradientPackageCmdSet" id="MyContextMenu" 
          priority="0x0000" type="Context">
        <Parent guid="guidTWGradientPackageCmdSet" id="0"/>
        <Strings>
        <ButtonText>MyContextMenu</ButtonText>
        <MenuText>MyContextMenu</MenuText>
        </Strings>
      </Menu>
    </Menus>
    

    A shortcut menu does not have a parent menu. By convention, the parent group is specified as the pair GUID:0, where GUID is the GUID of the command set of the VSPackage.

  4. In the <Groups> section, define the group that contains the shortcut menu items and associate the group with the shortcut menu.

    <Group guid="guidTWGradientPackageCmdSet" id="MyContextGroup"
          priority="0x0000">
      <Parent guid="guidTWGradientPackageCmdSet" id="MyContextMenu"/>
    </Group>
    
  5. In the <Buttons> section, define the individual commands that will appear on the shortcut menu.

    <Button guid="guidTWGradientPackageCmdSet" id="cmdidGradientFileTab"
          priority="0x0000" type="Button">
      <Parent guid="guidTWGradientPackageCmdSet" id="MyContextGroup"/>
      <CommandFlag>TextOnly</CommandFlag>
      <Strings>
        <ButtonText>File Tab Gradient</ButtonText>
      </Strings>
    </Button>
    <Button guid="guidTWGradientPackageCmdSet"
          id="cmdidGradientPanelBackground" priority="0x0000"
          type="Button">
      <Parent guid="guidTWGradientPackageCmdSet" id="MyContextGroup"/>
      <CommandFlag>TextOnly</CommandFlag>
      <Strings>
        <ButtonText>Panel Background Gradient</ButtonText>
      </Strings>
    </Button>
    <Button guid="guidTWGradientPackageCmdSet"
          id="cmdidGradientShellBackground" priority="0x0000"
          type="Button">
      <Parent guid="guidTWGradientPackageCmdSet" id="MyContextGroup"/>
      <CommandFlag>TextOnly</CommandFlag>
      <Strings>
        <ButtonText>Shell Background Gradient</ButtonText>
      </Strings>
    </Button>
    <Button guid="guidTWGradientPackageCmdSet"
          id="cmdidGradientToolboxHeading" priority="0x0000"
          type="Button">
      <Parent guid="guidTWGradientPackageCmdSet" id="MyContextGroup"/>
      <CommandFlag>TextOnly</CommandFlag>
      <Strings>
        <ButtonText>Toolbox Heading Gradient</ButtonText>
      </Strings>
    </Button>
    <Button guid="guidTWGradientPackageCmdSet" id="cmdidGradientToolTab"
          priority="0x0000" type="Button">
      <Parent guid="guidTWGradientPackageCmdSet" id="MyContextGroup"/>
      <CommandFlag>TextOnly</CommandFlag>
      <Strings>
        <ButtonText>Tool Tab Gradient</ButtonText>
      </Strings>
    </Button>
    <Button guid="guidTWGradientPackageCmdSet"
          id="cmdidGradientToolWindowActiveTitleBar" priority="0x0000"
          type="Button">
      <Parent guid="guidTWGradientPackageCmdSet" id="MyContextGroup"/>
      <CommandFlag>TextOnly</CommandFlag>
      <Strings>
        <ButtonText>Tool Window Active Title Bar Gradient</ButtonText>
      </Strings>
    </Button>
    <Button guid="guidTWGradientPackageCmdSet"
          id="cmdidGradientToolWindowInactiveTitleBar" priority="0x0000"
          type="Button">
      <Parent guid="guidTWGradientPackageCmdSet" id="MyContextGroup"/>
      <CommandFlag>TextOnly</CommandFlag>
      <Strings>
        <ButtonText>Tool Window Inactive Title Bar Gradient</ButtonText>
      </Strings>
    </Button>
    <Button guid="guidTWGradientPackageCmdSet"
          id="cmdidGradientToolWindowBackground" priority="0x0000"
          type="Button">
      <Parent guid="guidTWGradientPackageCmdSet" id="MyContextGroup"/>
      <CommandFlag>TextOnly</CommandFlag>
      <Strings>
        <ButtonText>Tool Window Background Gradient</ButtonText>
      </Strings>
    </Button>
    
  6. Save MyTWGradientPackage.vsct.

  7. On the Build menu,click Build Solution.

    This rebuilds the .vsct file with the changes. Correct any errors that may occur during building. (The most common error is caused by a case mismatch in a GUID label or a command ID. GUID labels and command IDs are always case-sensitive.)

  8. Continue to the next section, "Implementing the Shortcut Menu."

Implementing the Shortcut Menu

This section modifies the MyControl class to associate a command handler with the individual commands on the shortcut menu. A menu command list is created to contain the association between a particular menu command and its corresponding gradient type. In this walkthrough, all the commands go through a single command handler, which uses the menu command list to set the appropriate gradient type. Implementing the gradient is described in a later procedure.

To implement the shortcut menu

  1. In Solution Explorer, right-click PkgCmdID.cs and then click Open to open it in a text editor.

  2. Add the following lines after the definition for cmdidMyTool.

    public const uint cmdidGradientFileTab = 0x0102;
    public const uint cmdidGradientPanelBackground = 0x0103;
    public const uint cmdidGradientShellBackground = 0x0104;
    public const uint cmdidGradientToolboxHeading = 0x0105;
    public const uint cmdidGradientToolTab = 0x0106;
    public const uint cmdidGradientToolWindowActiveTitleBar = 0x0107;
    public const uint cmdidGradientToolWindowInactiveTitleBar = 0x0108;
    public const uint cmdidGradientToolWindowBackground = 0x0109;
    
    public const int MyContextMenu = 0x1000;
    

    These are the same command IDs that are defined in the <Symbols> section of MyTWGradientPackage.vsct file. Notice that the context group is not included here because it is only required in the .vsct file.

  3. On the File menu, click Save All to save your changes.

  4. In Solution Explorer, right-click MyControl.cs and then click View Code to open the source code in the text editor.

  5. At the top of MyControl.cs, add the following using statements:

    using Microsoft.VisualStudio.Shell;
    using System.ComponentModel.Design;
    
  6. At the top of the MyControl class, before the backgroundGradient member and the MyControl constructor, add the following private structure definition. This structure is later used in a list to associate a menu command with a gradient type.

    struct GradientMenuItem
    {
        public __GRADIENTTYPE type;
        public MenuCommand    menuCommand;
        public string       name;
    
        public GradientMenuItem(__GRADIENTTYPE type,
              MenuCommand menuCommand,
              string name)
        {
         this.type      = type;
         this.menuCommand = menuCommand;
         this.name      = name;
        }
    }
    
  7. Add the following members just after the backgroundGradient member.

    private int currentGradientCommand;
    private GradientMenuItem[] menuCommandList;
    private bool menuCommandsConnected;
    

    These variables store the current gradient, currently selected gradient menu item, and the list of menu items (which is created later in the constructor).

  8. Modify the MyControl constructor to populate the menu command list with GradientMenuItem objects after the call to the InitializeComponent method.

    EventHandler gradientSetHandler = new EventHandler(
        this.SetGradientStyle);
    
    menuCommandList = new GradientMenuItem[] {
      new GradientMenuItem(
        __GRADIENTTYPE.VSGRADIENT_FILETAB,
        new MenuCommand(gradientSetHandler,
          new CommandID(
            GuidList.guidTWGradientPackageCmdSet,
             (int)PkgCmdIDList.cmdidGradientFileTab)),
        "File Tab"),
      new GradientMenuItem(
        __GRADIENTTYPE.VSGRADIENT_PANEL_BACKGROUND,
        new MenuCommand(gradientSetHandler,
          new CommandID(
            GuidList.guidTWGradientPackageCmdSet,
            (int)PkgCmdIDList.cmdidGradientPanelBackground)),
        "Panel Background"),
      new GradientMenuItem(__GRADIENTTYPE.VSGRADIENT_SHELLBACKGROUND,
        new MenuCommand(gradientSetHandler,
          new CommandID(GuidList.guidTWGradientPackageCmdSet,
            (int)PkgCmdIDList.cmdidGradientShellBackground)),
        "Shell Background"),
      new GradientMenuItem(__GRADIENTTYPE.VSGRADIENT_TOOLBOX_HEADING,
        new MenuCommand(gradientSetHandler,
          new CommandID(GuidList.guidTWGradientPackageCmdSet,
            (int)PkgCmdIDList.cmdidGradientToolboxHeading)),
        "Toolbox Heading"),
      new GradientMenuItem(__GRADIENTTYPE.VSGRADIENT_TOOLTAB,
        new MenuCommand(gradientSetHandler,
          new CommandID(GuidList.guidTWGradientPackageCmdSet,
            (int)PkgCmdIDList.cmdidGradientToolTab)),
        "Tool Tab"),
      new GradientMenuItem(
        __GRADIENTTYPE.VSGRADIENT_TOOLWIN_ACTIVE_TITLE_BAR,
        new MenuCommand(gradientSetHandler,
          new CommandID(GuidList.guidTWGradientPackageCmdSet,
            (int)PkgCmdIDList.cmdidGradientToolWindowActiveTitleBar)),
        "Tool Window Active Title Bar"),
      new GradientMenuItem(
        __GRADIENTTYPE.VSGRADIENT_TOOLWIN_INACTIVE_TITLE_BAR,
        new MenuCommand(gradientSetHandler,
          new CommandID(GuidList.guidTWGradientPackageCmdSet,
            (int)PkgCmdIDList.cmdidGradientToolWindowInactiveTitleBar)),
        "Tool Window Inactive Title Bar"),
      new GradientMenuItem(
        __GRADIENTTYPE.VSGRADIENT_TOOLWIN_BACKGROUND,
        new MenuCommand(gradientSetHandler,
          new CommandID(GuidList.guidTWGradientPackageCmdSet,
            (int)PkgCmdIDList.cmdidGradientToolWindowBackground)),
        "Tool Window Background"),
    };
    
    if (null != this.menuCommandList)
    {
        // Initialize the button text to show the current gradient type.
        UpdateCurrentGradient(this.currentGradientCommand);
    }
    

    The menu command list now associates each menu command with a gradient type and a command handler. This makes the menu command objects readily accessible so that their checked status can be updated.

  9. Add the AddMenuCommands method after the constructor.

    private void AddMenuCommands()
    {
        // If we have a menu command list and we haven't yet added our
       // commands to the command chain then...
        if (null != this.menuCommandList 
        && !this.menuCommandsConnected)
        {
            OleMenuCommandService mcs = 
                  this.GetService(typeof(IMenuCommandService))
                  as OleMenuCommandService;
            if (null != mcs)
            {
                int count = this.menuCommandList.Length;
                for (int i = 0; i < count; i+)
                {
                    mcs.AddCommand(this.menuCommandList[i].menuCommand);
                }
            }
           this.menuCommandsConnected = true;
        }
    }
    

    The AddMenuCommands method informs Visual Studio about the menu commands so that Visual Studio can automatically handle the shortcut menu and route the commands to the command handler (which is added in a later procedure). The steps performed by this method cannot be done in the constructor because the base GetService method is not yet available in the constructor.

  10. Add the SetGradientStyle event handler to the end of the MyControl class. This method is called whenever a command on the shortcut menu is clicked.

    public void SetGradientStyle(object sender, EventArgs e)
    {
        MenuCommand command = sender as MenuCommand;
        if (null != command)
        {
            uint cmdID = (uint)command.CommandID.ID;
            int newGradient = this.currentGradientCommand;
            int count = this.menuCommandList.Length;
    
            // Figure out which command was selected.
            for (int i = 0; i < count; i+)
            {
                if (this.menuCommandList[i].menuCommand.CommandID.ID 
                      == cmdID)
                {
                    newGradient = i;
                    break;
                }
            }
    
            // If the user has selected a different gradient style...
            if (this.currentGradientCommand != newGradient)
            {
                // Clear the check on the current selection.
                this.menuCommandList[this.currentGradientCommand].menuCommand.Checked = false;
                UpdateCurrentGradient(newGradient);
            }
        }
    }
    
  11. Find the SetBackgroundGradient method and replace it with the following code. Doing this changes the hard-coded gradient type to one that is associated with the currently selected gradient type.

    private void SetBackgroundGradient()
    {
        this.backgroundGradient =
              GetGradient(this.menuCommandList[this.currentGradientCommand].type);
    }
    
  12. In Solution Explorer, right-click MyControl.cs and then click View Designer.

    Doing this opens MyControl in the designer so that event handlers can be added.

  13. If the Properties window for the MyControl form is not already open, press F4 to open it.

  14. Click the Click Me! button to display its properties.

  15. In the Properties window, find the Anchor property in the Layout category and then click the drop-down arrow on the property value.

    Doing this displays the anchor dialog box.

  16. Click each of the four gray bars to clear each anchor selection, and then click the Anchor property name to close the anchor dialog box.

    The anchor property value should now be None.

  17. Click the background just outside of the Click Me! button to select the control itself.

  18. In the Properties window, click the Events button (the yellow lightning bolt icon) to open the list of events that are available for the control.

  19. In the Action event category, double-click the MouseClick action.

    Doing this automatically generates a handler for a mouse-click event in the control. The editor should also automatically display the new handler call, MyControl_MouseClick.

  20. Fill in the MyControl_MouseClick method, as follows.

    private void MyControl_MouseClick(object sender, MouseEventArgs e)
    {
        if (MouseButtons.Right == e.Button)
        {
            // Make sure menu commands are connected to Visual Studio.
            AddMenuCommands();
            OleMenuCommandService menuService = 
                  this.GetService(typeof(IMenuCommandService))
                  as OleMenuCommandService;
            if (null != menuService)
            {
                CommandID menuID = new CommandID(
                      GuidList.guidTWGradientPackageCmdSet,
                      PkgCmdIDList.MyContextMenu);
                try
                {
                    // Convert client coords to screen coords.
                    Point p = this.PointToScreen(new Point(e.X, e.Y));
                    menuService.ShowContextMenu(menuID, p.X, p.Y);
                }
                catch (Exception exp)
                {
                    System.Diagnostics.Trace.WriteLine(exp.Message);
                }
            }
        }
    }
    

    This enables the shortcut menu to be displayed in response to a right-click in the tool window. Notice that the event position is converted from being relative to the control to being relative to the screen coordinates instead; this is required for the ShowContextMenu method.

  21. Save the file and then continue to the next section, "Changing the Button Action and Building the Solution."

Changing the Button Action and Building the Solution

This section modifies the button event handler to cycle to the next available gradient each time the button is clicked. The name of the gradient is also displayed on the button.

To implement the new button click action

  1. In MyControl.cs, replace the contents of the button1_Click method with the following code:

    // Clear the check on the old style.
    this.menuCommandList[
          this.currentGradientCommand].menuCommand.Checked = false;
    this.currentGradientCommand++;
    if (this.currentGradientCommand >= this.menuCommandList.Length)
    {
        this.currentGradientCommand = 0;
    }
    UpdateCurrentGradient(this.currentGradientCommand);
    
  2. Add the UpdateCurrentGradient method after the SetBackgroundGradient method.

    private void UpdateCurrentGradient(int whichGradient)
    {
        this.currentGradientCommand = whichGradient;
        this.button1.Text = this.menuCommandList[
              this.currentGradientCommand].name;
        // Set the check on the new selection.
        this.menuCommandList[
              this.currentGradientCommand].menuCommand.Checked = true;
        // Create the gradient to use for drawing the background.
        SetBackgroundGradient();
        // Invalidate control window to force a redraw.
        this.Invalidate();
    }
    

    This method updates the button text, sets which menu item is currently selected, and changes the background gradient to the specified gradient type.

  3. On the Build menu, click Build Solution to compile the code. Doing this also registers the VSPackage and its tool window with Visual Studio. Make sure there are no errors.

  4. Continue to the next section, "Testing the Tool Window Features."

Testing the Tool Window Features

This section takes you through several steps to demonstrate the features that have been added in this walkthrough.

To test the tool window features

  1. Press F5 to open an instance of the experimental Visual Studio.

  2. In the experimental Visual Studio, on the View menu, click Other Windows and then click Gradient Tool Window. Doing this displays your tool window. The button in the tool window should display "File Tab" and the window background should be a gradient that goes from light blue at the top to medium blue at the bottom.

  3. Click the button to change the gradient. The name on the button changes to the new gradient type and the background of the tool window is redrawn in the new gradient.

  4. Right-click the tool window background, away from the button. A shortcut menu appears and lists all the gradient types. The currently selected gradient type should have a check mark next to it.

  5. Click a gradient type on the shortcut menu. The shortcut menu disappears, the text on the button changes to the selected gradient type, and the tool window background is changed to the selected gradient.

Next Steps

If you change the shortcut menu by modifying the PkgCmd.vsct file, you must rebuild the MyTWGradientPackageUI project. Doing this forces the .vsct file to be recompiled and the satellite DLL to be re-linked with the changes that you made to the .vsct file.

See Also

Tasks

Walkthrough: Using the Gradient Service in a Tool Window

Other Resources

Menu and Toolbar Command Walkthroughs

Menus and Toolbars

Services

VSPackages