Walkthrough: Implementing a Context Menu in a Tool Window

注意

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 XML-Based Command Table Configuration (.vsct) Files.

This walkthrough builds on the Walkthrough: Using the Gradient Service in a Tool Window. This walkthrough guides you through the steps of adding support for a context menu so that the tool window button you create cycles through the available gradient types.

A context menu is a pop-up menu that appears when a user right-clicks a graphical user interface (GUI) element such as a button, text box or window background. Commands selected from a context menu behave the same as commands selected on a normal menu or toolbar. The way to support a context menu is specifying the menu in the Command Table Compiler (.ctc) file and displaying the context menu in response to a right-click of the mouse.

For more information on menus and the .ctc file, see Menus and Toolbars.

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

Prerequisites

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

Creating the Tool Window Context Menu Package

To create the MyTWGradientPackage VSPackage

Specifying the Context Menu

The context menu as shown in this walkthrough allows the user to select from a list of eight gradient types that are used to fill the tool window's background. When you right-click in the tool window, the context menu is displayed with the available gradient types. The current gradient type is shown with a checkmark.

To create the context menu

  1. In Solution Explorer, expand the CtcComponents folder in the MyTWGradientPackage project, right-click CommandIds.h, and then select Open to open it in a text editor.

  2. In CommandIds.h file, find the Menu IDs section and add the following line:

    #define MyContextMenu               0x1000
    
  3. In the Menu Group IDs section, add a definition for MyContextGroup after the existing group ID:

    #define MyContextGroup              0x1030
    
  4. In the Command IDs section, add the following lines after the existing command ID:

    #define cmdidGradientFileTab                    0x0102
    #define cmdidGradientPanelBackground            0x0103
    #define cmdidGradientShellBackground            0x0104
    #define cmdidGradientToolboxHeading             0x0105
    #define cmdidGradientToolTab                    0x0106
    #define cmdidGradientToolWindowActiveTitleBar   0x0107
    #define cmdidGradientToolWindowInactiveTitleBar 0x0108
    #define cmdidGradientToolWindowBackground       0x0109
    
  5. In the CtcComponents folder in the MyTWGradientPackage project, right-click MyTWGradientPackage.ctc and then select Open.

  6. In MyTWGradientPackage.ctc, find the MENUS_BEGIN section and insert the following lines.

        guidMyTWGradientPackageCmdSet:MyContextMenu,  // Menu ID
            guidMyTWGradientPackageCmdSet:0,          // Parent Group
            0x00,                                     // Priority
            CONTEXT,                                  // Type
            "MyContextMenu",                          // Menu name
            "MyContextMenu";                          // Menu text
    

    These lines define the context menu. A context menu does not have a parent menu so, by convention, the parent group is specified as the pair GUID:0, where GUID is the GUID of the VSPackage's command set.

  7. Find the NEWGROUPS_BEGIN section and insert the following after the existing group entry. These lines define the group that contains the context menu items and associates the group with the context menu itself.

        guidMyTWGradientPackageCmdSet:MyContextGroup,    // Group ID
            guidMyTWGradientPackageCmdSet:MyContextMenu, // Menu ID
            0x0000;                                      // Priority
    
  8. Find the BUTTONS_BEGIN section and insert the following lines after the existing button entry. These lines define the individual commands that appear on the context menu.

        guidMyTWGradientPackageCmdSet:cmdidGradientFileTab, // Command ID
            guidMyTWGradientPackageCmdSet:MyContextGroup,   // Parent group
            0x0000,                                         // Priority
            OI_NOID,                                        // Icon ID (in this case, no icon)  
            BUTTON,                                         // Type
            TextOnly,                                       // Flags
            "File Tab Gradient";                            // Button Text
    
        guidMyTWGradientPackageCmdSet:cmdidGradientPanelBackground,
            guidMyTWGradientPackageCmdSet:MyContextGroup,
            0x0000,
            OI_NOID,
            BUTTON,
            TextOnly,
            "Panel Background Gradient";
    
        guidMyTWGradientPackageCmdSet:cmdidGradientShellBackground,
            guidMyTWGradientPackageCmdSet:MyContextGroup,
            0x0000,
            OI_NOID,
            BUTTON,
            TextOnly,
            "Shell Background Gradient";
    
        guidMyTWGradientPackageCmdSet:cmdidGradientToolboxHeading,
            guidMyTWGradientPackageCmdSet:MyContextGroup,
            0x0000,
            OI_NOID,
            BUTTON,
            TextOnly,
            "Toolbox Heading Gradient";
    
        guidMyTWGradientPackageCmdSet:cmdidGradientToolTab,
            guidMyTWGradientPackageCmdSet:MyContextGroup,
            0x0000,
            OI_NOID,
            BUTTON,
            TextOnly,
            "Tool Tab Gradient";
    
        guidMyTWGradientPackageCmdSet:cmdidGradientToolWindowActiveTitleBar,
            guidMyTWGradientPackageCmdSet:MyContextGroup,
            0x0000,
            OI_NOID,
            BUTTON,
            TextOnly,
            "Tool Window Active Title Bar Gradient";
    
        guidMyTWGradientPackageCmdSet:cmdidGradientToolWindowInactiveTitleBar,
            guidMyTWGradientPackageCmdSet:MyContextGroup,
            0x0000,
            OI_NOID,
            BUTTON,
            TextOnly,
            "Tool Window Inactive Title Bar Gradient";
    
        guidMyTWGradientPackageCmdSet:cmdidGradientToolWindowBackground,
            guidMyTWGradientPackageCmdSet:MyContextGroup,
            0x0000,
            OI_NOID,
            BUTTON,
            TextOnly,
            "Tool Window Background Gradient";
    
  9. Save MyTWGradientPackage.ctc.

  10. On the Build menu,click Build Solution.

    This rebuilds the .ctc file with the changes. Correct any errors that may occur during building (the most common error is using the wrong case for a GUID label or a command ID; GUID labels and command IDs are always case-sensitive).

  11. Continue to the next procedure, Implementing the Context Menu.

Implementing the Context Menu

This section modifies the MyControl class to associate a command handler with the individual commands in the context 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 of 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.

Implementing the Context Menu

  1. In Solution Explorer, right-click PkgCmdID.cs and 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 were defined in PkgCmd.h with the exception of the MyContextGroup ID which is not needed outside of the .ctc file.

  3. Click Save All on the File menu to save your changes.

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

  5. At the top of MyControl.cs, add the following using statements just after the using Microsoft.VisualStudio.OLE.Interop line:

    using Microsoft.VisualStudio.Shell;
    using System.ComponentModel.Design;
    
  6. At the top of the MyControl class, before the backgroundGradient member, 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 by adding the following lines 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.guidMyTWGradientPackageCmdSet,
                                                                       (int)PkgCmdIDList.cmdidGradientFileTab)),
                                         "File Tab"),
                        new GradientMenuItem(__GRADIENTTYPE.VSGRADIENT_PANEL_BACKGROUND,
                                         new MenuCommand(gradientSetHandler,
                                                         new CommandID(GuidList.guidMyTWGradientPackageCmdSet,
                                                                       (int)PkgCmdIDList.cmdidGradientPanelBackground)),
                                         "Panel Background"),
                        new GradientMenuItem(__GRADIENTTYPE.VSGRADIENT_SHELLBACKGROUND,
                                         new MenuCommand(gradientSetHandler,
                                                         new CommandID(GuidList.guidMyTWGradientPackageCmdSet,
                                                                      (int)PkgCmdIDList.cmdidGradientShellBackground)),
                                         "Shell Background"),
                        new GradientMenuItem(__GRADIENTTYPE.VSGRADIENT_TOOLBOX_HEADING,
                                         new MenuCommand(gradientSetHandler,
                                                         new CommandID(GuidList.guidMyTWGradientPackageCmdSet,
                                                                      (int)PkgCmdIDList.cmdidGradientToolboxHeading)),
                                         "Toolbox Heading"),
                        new GradientMenuItem(__GRADIENTTYPE.VSGRADIENT_TOOLTAB,
                                         new MenuCommand(gradientSetHandler,
                                                         new CommandID(GuidList.guidMyTWGradientPackageCmdSet,
                                                                      (int)PkgCmdIDList.cmdidGradientToolTab)),
                                         "Tool Tab"),
                        new GradientMenuItem(__GRADIENTTYPE.VSGRADIENT_TOOLWIN_ACTIVE_TITLE_BAR,
                                         new MenuCommand(gradientSetHandler,
                                                         new CommandID(GuidList.guidMyTWGradientPackageCmdSet,
                                                                      (int)PkgCmdIDList.cmdidGradientToolWindowActiveTitleBar)),
                                         "Tool Window Active Title Bar"),
                        new GradientMenuItem(__GRADIENTTYPE.VSGRADIENT_TOOLWIN_INACTIVE_TITLE_BAR,
                                         new MenuCommand(gradientSetHandler,
                                                         new CommandID(GuidList.guidMyTWGradientPackageCmdSet,
                                                                      (int)PkgCmdIDList.cmdidGradientToolWindowInactiveTitleBar)),
                                         "Tool Window Inactive Title Bar"),
                        new GradientMenuItem(__GRADIENTTYPE.VSGRADIENT_TOOLWIN_BACKGROUND,
                                         new MenuCommand(gradientSetHandler,
                                                         new CommandID(GuidList.guidMyTWGradientPackageCmdSet,
                                                                      (int)PkgCmdIDList.cmdidGradientToolWindowBackground)),
                                         "Tool Window Background"),
                };
    
                if (null != this.menuCommandList)
                {
                    // Initialize the button text to show the current gradient type.
                    UpdateCurrentGradient(this.currentGradientCommand);
                }
    

    These lines creates the menu command list that associates each menu command with a gradient type and associates each menu command with a command handler. This allows the menu command objects to be easily accessible so 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 Visual Studio can automatically handle the context menu and route the commands to your 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 menu item is selected from the context menu.

            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 then...
                    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 modify it to look like the following. This step changes the hard-coded gradient type to one 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 select View Designer.

    This opens MyControl in the designer and allows event handlers to be added.

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

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

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

    This displays the anchor dialog box.

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

    The anchor property value should now read None.

  17. Click on the background just outside 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 available for the control.

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

    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. Add the following lines to the MyControl_MouseClick method.

                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.guidMyTWGradientPackageCmdSet,
                                                         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 is how the context menu displays in response to a right-click in the tool window. Note how the event position is converted from being relative to the control to the screen coordinates instead (which is required for the ShowContextMenu method).

  21. Save the file and continue to the next procedure, 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 through the available gradient types each time the button is clicked on. The name of the gradient type is also displayed in the button.

To implement the new button click action

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

                // 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. Select Build Solution from the Build menu to compile the code. Make sure there are no errors. This step registers the VSPackage and its tool window with Visual Studio.

  4. Continue on to the procedure, 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 for this walkthrough.

To test the tool window features

  1. Open an instance of the experimental Visual Studio with one of the following steps:

    • From the Visual Studio command prompt, type devenv /rootsuffix exp.

    • From the Start Menu, select the experimental Visual Studio shortcut; for example, Visual Studio 2008 Experimental. This shortcut is added when the Visual Studio SDK is installed.

    • Press F5 or select Start from the Debug menu (this runs the experimental Visual Studio under the debugger and allows debugging of your VSPackage).

  2. In the experimental Visual Studio, select My Gradient Tool Window from the Other Windows submenu on the View menu. This displays your tool window. The button in the tool window should display "File Tab" and the background should be a gradient going from light gray at the top to medium gray at the bottom.

  3. Click on 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 in the tool window background, away from the button. A context menu appears listing all the gradient types. The currently selected gradient type must have a check mark next to it.

  5. Select a gradient type from the context menu. The context 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 make any changes to the context menu by modifying the PkgCmd.ctc file, you must right-click on the MyTWGradientPackageUI project and select Rebuild from the context menu. This forces the .ctc file to not only be recompiled but the satellite DLL to be relinked with any changes made to the .ctc file.

See Also

Concepts

Menu and Toolbar Command Walkthroughs

Walkthrough: Using the Gradient Service in a Tool Window

Menus and Toolbars

Services

VSPackages