Dynamic Menu Commands in Visual Studio Packages - Part 2

In this post I’ll be building on the principles of my last post with dynamic menu development in Visual Studio, so if you haven’t read that post please go back and check it out!

If you remember from last time, we were able to create a dynamic menu command without any lines of procedural code (other than those generated by the wizard). The result was a menu command that showed itself only when no solution was loaded in Visual Studio. Immediately a solution was loaded, the menu command disappeared. The limitations of this approach are that you’re reliant on built-in UI contexts, such as UICONTEXT_NoSolution. However, what I needed for my DBML Fixup project was a way to add menu commands when right clicking on .dbml files (LINQ to SQL Classes) in the Solution Explorer. The built-in UI contexts that we reviewed in the previous post are not enough to give us this functionality. In this tutorial, I take you through step-by-step on how to add this feature to your VS packages. We have to do a bit of digging though, so let’s get started!

The first thing we need to do is to create a new a menu command using our existing solution. Because we don’t have the wizard to help us this time, we’ll have to create our menu command from scratch.

Step 1: Creating the New Command

Our first step is to create the GUID:ID pair that Visual Studio will use to identify our new menu command. The GUID represents a set of commands (or command set) exposed by this package; one of these was created by the package wizard when the solution was generated. However, for this part of the tutorial, we’ll go ahead and create a new command set in the Guids.cs file. (We can get a new GUID from the Tools > Create GUID menu item.) Afterwards, my Guids.cs file looks like this:

static class GuidList

{

    public const string guidDynamicMenuDevelopmentPkgString = "d626ae3e-6eaa-414f-9a74-4f41fb902a23";

    public const string guidDynamicMenuDevelopmentCmdSetString = "a9d25ef1-3235-4a08-8c93-f26619635e91";

    public const string guidDynamicMenuDevelopmentCmdSetPart2String = "9d9046da-94f8-4fd0-8a00-92bf4f6defa8";

 

    public static readonly Guid guidDynamicMenuDevelopmentCmdSet = new Guid(guidDynamicMenuDevelopmentCmdSetString);

    public static readonly Guid guidDynamicMenuDevelopmentCmdSetPart2 = new Guid(guidDynamicMenuDevelopmentCmdSetPart2String);

};

Pardon the unoriginal name of "CmdSetPart2." The next step is to add an ID that maps to the specific menu command we are going to add. This can be changed in the PkgCmdID.cs file in our project. Here I will add the cmdidQueryStatusCommand with a value of 257:

static class PkgCmdIDList

{

    public const uint cmdidBuiltInUIContext =        0×100;

    public const uint cmdidQueryStatus      =        0×101;

};

So that was fairly simple, but obviously we haven’t registered it with Visual Studio, so there’s no way the Visual Studio shell will know about this menu command. To do so, we need to make some amendments to the .vsct file. The first of these changes is to identify the new menu command GUID and ID pair in the Symbols section, so that we can reference it elsewhere. This is also easy to do and requires a few lines of XML, which can be added as any first-level child of the Symbols node:

<GuidSymbol name="guidDynamicMenuDevelopmentCmdSetPart2" value="{9d9046da-94f8-4fd0-8a00-92bf4f6defa8}">

  <IDSymbol name="menuidQueryStatusGroup" value="0×1020"/>

  <IDSymbol name="cmdidQueryStatus" value="0×0101" />

</GuidSymbol>

As you can see, this information is based directly off the two previous steps. The "guidDynamicMenuDevelopmentCmdSetPart2" command set has the same GUID as in our GuidList class, and the "cmdidQueryStatus" menu command ID has the same value as in the PkgCmdIDList class.

Our objective is to show this menu command when we right click on DBML files in the designer; this means we’ll need to create a new Group element whose parent is the Solution Explorer’s context menu. Then we’ll need to create a Button element to put in that Group, which represents the menu command itself. This doesn’t seem like such a problem, until you try to define the Group’s parent…

<Group guid="guidDynamicMenuDevelopmentCmdSetPart2" id="menuidQueryStatusGroup" priority="0×001">

  <Parent guid="???" id="???"/>

</Group>

What goes in the place of the question marks? Well, we might be able to find it in one of the copious header files that sits in the Visual Studio SDK’s VisualStudioIntegration\Common\Inc directory, but I’m willing to bet that there’s a faster way. And it turns out there is. Looking at Dr. eX’s blog entry on VSIPLogging reveals that a simple registry key will allow you see to the GUID:ID pair of almost any menu command in Visual Studio, including those developed by third parties. Changing this setting in the registry and relaunching Visual Studio allows us to check what the GUID and the ID we need are; as it turns out, holding the Ctrl+Shift keys down and right clicking on a project item in the Solution Explorer gives us the information we need:

image

We can use this information to fill in the Parent’s details, but we’ll need to register the GUID and CmdID in the GuidSymbols element in our .vsct file first.

<GuidSymbol name="guidSolutionExplorerMenu" value="{D309F791-903F-11D0-9EFC-00A0C911004F}">

  <IDSymbol name="menuidSolutionExplorerMenu" value="1072" />

</GuidSymbol>

<Group guid="guidDynamicMenuDevelopmentCmdSetPart2" id="menuidQueryStatusGroup" priority="0×01">

  <Parent guid="{D309F791-903F-11D0-9EFC-00A0C911004F}" id="1072"/>

</Group>

The Button element isn’t too exciting by comparison; in fact it doesn’t differ much from the Button element from the last tutorial. I include it here for reference:

<Button guid="guidDynamicMenuDevelopmentCmdSetPart2" id="cmdidQueryStatus" priority="0×0" type="Button">

  <Parent guid="guidDynamicMenuDevelopmentCmdSetPart2" id="menuidQueryStatusGroup"/>

  <Icon guid="guidImages" id="bmpPic2"/>

  <CommandFlag>DynamicVisibility</CommandFlag>

  <Strings>

    <CommandName>cmdidQueryStatus</CommandName>

    <ButtonText>Query Status</ButtonText>

  </Strings>

</Button>

OK, that’s it for the .vsct edits. Notice that our Button element still has the "DynamicVisibility" flag, but we have not specified a visibility constraint; this is because we will handle the changes ourselves in procedural code. The final thing we have to do to finish the "plumbing" work by creating our menu command and an event handler to handle its invocation. I’ll add this code in the DynamicMenuDevelopmentPackage.cs file in the Initialize method. If you navigate there, you’ll see the wizard-generated code for the first menu command. It’s only a few lines of code to hook everything together with our second menu command:

protected override void Initialize()

{

    Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));

    base.Initialize();

 

    // Add our command handlers for menu (commands must exist in the .vsct file)

    OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;

    if ( null != mcs )

    {

        // Create the command for the menu item.

        CommandID menuCommandID = new CommandID(GuidList.guidDynamicMenuDevelopmentCmdSet, (int)PkgCmdIDList.cmdidBuiltInUIContext);

        MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );

        mcs.AddCommand( menuItem );

 

        // Create the command for the query status menu item.

        CommandID queryStatusCommandID = new CommandID(GuidList.guidDynamicMenuDevelopmentCmdSetPart2, (int)PkgCmdIDList.cmdidQueryStatus);

        OleMenuCommand queryStatusMenuCommand = new OleMenuCommand(MenuItemCallback, queryStatusCommandID);

        mcs.AddCommand(queryStatusMenuCommand);

    }

}

One thing to note here is that in contrast to the first menu command, we are creating an OleMenuCommand, not a regular MenuCommand. The reasons will become apparent later, but for now, let’s press F5 to see what we’ve got. Open an existing solution (or create a new one if it’s easier to do so) and right click on a project item (not a folder, project, or solution node, though) and you will see our "Query Status" command:

image

Step 2: Making It Dynamic

As expected, though, it shows up regardless of the selected item in the project, so we haven’t reached the end yet! Going back to the code in DynamicMenuDevelopmentPackage, we discover a use for the OleMenuCommand vs. the MenuCommand. The OleMenuCommand exposes an event called BeforeQueryStatus, which is raised right before Visual Studio asks what the state of a particular menu item is. In this case, it’s a perfect place to edit the state of our menu command based on the current selection in the Solution Explorer. Adding the following event handler to the BeforeQueryStatus event of that OleMenuCommand helps us achieve our desired goal:

private void queryStatusMenuCommand_BeforeQueryStatus(object sender, EventArgs e)

{

    OleMenuCommand menuCommand = sender as OleMenuCommand;

    if (menuCommand != null)

    {

        IntPtr hierarchyPtr, selectionContainerPtr;

        uint projectItemId;

        IVsMultiItemSelect mis;

        IVsMonitorSelection monitorSelection = (IVsMonitorSelection)Package.GetGlobalService(typeof(SVsShellMonitorSelection));

        monitorSelection.GetCurrentSelection(out hierarchyPtr, out projectItemId, out mis, out selectionContainerPtr);

 

        IVsHierarchy hierarchy = Marshal.GetTypedObjectForIUnknown(hierarchyPtr, typeof(IVsHierarchy)) as IVsHierarchy;

        if (hierarchy != null)

        {

            object value;

            hierarchy.GetProperty(projectItemId, (int)__VSHPROPID.VSHPROPID_Name,

>out
value);

 

            if (value != null && value.ToString().EndsWith(".dbml"))

            {

                menuCommand.Visible = true;

            }

            else

            {

                menuCommand.Visible = false;

            }

        }

    }

}

I won’t explain all the code here, but the gist is that we are getting the name of the current selection, checking if it ends with the ".dbml" extension, and dynamically setting the visibility on our menu command based on that result. Unfortunately, this is still not enough to correctly implement our dynamic menu command, due to the fact that Visual Studio loads packages on demand. To make a long story short, the code that we just wrote to handle dynamic menu item visibility will be called only when the package is loaded, and the package is loaded only when some UI element exposed by the package is clicked, which invokes some piece of code in the underlying package. This does not really help us when all the package exposes is two menu commands!

This is a problem that I encountered recently, and consequently I am open to any suggestions. My solution has been to put the ProvideAutoLoadAttribute on the class, which tells Visual Studio to load the package automatically when it encounters a certain UI context. In this circumstance, the context "UICONTEXT_SolutionExists" (defined in vsshlids.h) suits our purposes perfectly. We’ll have to specify the GUID itself instead of its alias, however, like this:

[ProvideAutoLoad("{f1536ef8-92ec-443c-9ed7-fdadf150da82}")]

public sealed class DynamicMenuDevelopmentPackage : Package

{

    // …

}

And that’s it! Press F5 and open a solution to see for yourself. The "Query Status" command will appear only when right-clicking on .dbml files in the Solution Explorer.

Associated Solution

Other Articles in the Series

6 Responses to “Dynamic Menu Commands in Visual Studio Packages - Part 2”

  1. DiveDeeper Says:

    I like this article putting together the pieces of elements for dynamic menus. It seems that we are going along the same track. I have just discovered ProvideAutoLoad attribute (due to the search for hooking solution events) on the day you have published your blog. Thanks for this great article!

  2. David DeWinter » Blog Archive » Dynamic Menu Commands in Visual Studio Packages - Part 3 Says:

    [...] Part 2 - Discusses the use of the BeforyQueryStatus event to provide more flexibility than built-in UI contexts. [...]

  3. LearnVSXNow! #18 - Advanced VSCT concepts - DiveDeeper blog Says:

    [...] Before going again into the structure of .vsct files I would like to turn your attention to David DeWinter’s blog. David wrote two great articles on creating dynamic menus in VS. You can find them here: Part #1 and Part #2. [...]

  4. peter Says:

    Hi
    I was very interested in your excellent article.
    However, I have been having some issues with the second part, that is adding the context menu item for
    a particular file type.

    From the article:
    Piece of cake! We can now change the Group element’s parent to look like this, and we’re well on our way: (It is probably a better idea to put the GUID and ID inside the Symbols element so it can re-referenced more easily.)

    I get an error that complains about the Parent guid and id.
    Am I missing something obvious?

    Regards

  5. David DeWinter Says:

    Peter, please accept my apologies. You need to reference the GUID and CmdID from a GuidSymbol element at the bottom of the VSCT file. I’ve updated my post to reflect that.

    The associated solution (link at the bottom of the post) also demonstrates this.

  6. Peter Says:

    Hi
    Thanks, I saw it in the sample project.
    Very nice post by the way.
    It has been of immeasurable help to me.
    Regards.

Leave a Reply