Dotneteers.net
All for .net, .net for all!

LearnVSXNow! #31 - Merging Package Menus with VSCT

Working on my demo VSPackages for the VSXtra presentation on the VSX Developers Conference I ran into a small issue. I have about a dozen of packages to demo. These packages have been created by the VSPackage wizard and so the commands to access their functionality can be found in the Tools and in the View|Other Windows menus. What is more, the VSPackage wizard uses the same dummy icons for the package functions. Having dozen of packages it is not easy to find them during the demo and even their icons are boring... I have changed the icons to predefined Office icons, but it did not solve the problem that my demo commands were scattered around.

Why we need to merge package menus?

I decided to merge my demo packages into a common top level menu where demos can be grouped according to their topic. I imagined a menu like this:

I selected the first package to create this menu structure by changing its VSCT file. I used the method for the top level menu and the submenus as I described in Part #14 and #18. The core of the solution was something like this:

<?xml version="1.0" encoding="utf-8"?>

<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/...">

  <Extern href="stdidcmd.h"/>

  <Extern href="vsshlids.h"/>

  <Extern href="msobtnid.h"/>

 

  <Commands package="MyPackage">

    <Menus>

      <!-- Top level "VSXtra" menu -->

      <Menu guid="guidVSXtraCmdSet" id="VSXtraMainMenu" priority="0x800" 

        type="Menu">

        <Parent guid="guidSHLMainMenu" id="IDG_VS_MM_BUILDDEBUGRUN" />

        <CommandFlag>AlwaysCreate</CommandFlag>

        <Strings>

          <ButtonText>VSXtra</ButtonText>

          <CommandName>VSXtra</CommandName>

        </Strings>

      </Menu>

 

      <!-- "Package Samples" submenu placed into the "VSXtra" menu -->

      <Menu guid="guidVSXtraCmdSet" id="PackageSubMenu" priority="0x100"

        type="Menu">

        <Parent guid="guidVSXtraCmdSet" id="TopLevelMenuGroup" />

        <Strings>

          <ButtonText>Package Samples</ButtonText>

          <CommandName>Package Samples</CommandName>

        </Strings>

      </Menu>

      <!-- Other submenus omitted -->

    </Menus>

   

    <Groups>

      <!-- Logical group for submenus of the "VSXtra" top level menu -->

      <Group guid="guidVSXtraCmdSet" id="TopLevelMenuGroup" priority="0x0100">

        <Parent guid="guidVSXtraCmdSet" id="VSXtraMainMenu"/>

      </Group>

 

      <!-- Logical group for the "Package Samples" submenus -->

      <Group guid="guidVSXtraCmdSet" id="PackageMenuGroup" priority="0x0100">

        <Parent guid="guidVSXtraCmdSet" id="PackageSubMenu"/>

      </Group>

     

      <!-- Other submenu groups omitted -->

 

      <!-- Menug group for my package’s commands -->

      <Group guid=" guidMyPackageCmdSet " id="MyMenuGroup" priority="0x0100">

        <Parent guid="guidVSXtraCmdSet" id="PackageSubMenu"/>

      </Group>

 

    </Groups>

 

    <Buttons>

      <Button guid="guidMyPackageCmdSet" id="cmdidDisplayMyMessage"

        priority="0x0100" type="Button">

        <Parent guid="guidMyPackageCmdSet" id="MyMenuGroup" />

        <Icon guid="guidOfficeIcon" id="msotcidNewMessage" />

        <Strings>

          <CommandName>MyCommand</CommandName>

          <ButtonText>My Command</ButtonText>

        </Strings>

      </Button>

    </Buttons>   

  </Commands>

 

  <Symbols>

    <GuidSymbol name="MyPackage" value="..." />

 

    <!-- Symbols for the VSXtra menus -->

    <GuidSymbol name="guidVSXtraCmdSet" value="...">

      <IDSymbol name="VSXtraMainMenu" value="0x1000" />

      <IDSymbol name="TopLevelMenuGroup" value="0x1001" />

      <IDSymbol name="PackageSubMenu" value="0x1002" />

      <IDSymbol name="PackageMenuGroup" value="0x1102" />

      <!-- Other symbols omitted -->

    </GuidSymbol>

  </Symbols>

 

</CommandTable>

For the sake of those not remembering how to create top level menus and submenus, here I summarize the essence of the solution:

I use two menu items, one for the top level menu and a second for the submenu. The top level menu is placed to the VS main menu level by using the (guidSHLMainMenu, IDG_VS_MM_BUILDDEBUGRUN) Parent binding. The submenu cannot be bound to the top level menu, it is parented in the TopLevelMenuGroup and this group is bound to the main menu item.

Because I visually want to indicate the command belonging to a demo package, I decided to put package commands into a separate logical group (even if the package has only one command to show). In this way Visual Studio uses menu separator lines for groups and we can visually see cohesive commands. So, I created a group (MyMenuGroup in the sample above), bound this group to the corresponding submenu. All the package commands are parented in this menu group.

This concept worked correctly for the first package. However, when I created the menu structure for the next package (I used exactly the same symbol values for the common menu items) I discovered that the VSXtra top menu was duplicated with exactly the same items. Here is a picture about what I experienced:

The reason for the duplication is that two separate package owns the same top menu structure.

The solution for this issue would be to create the main menu structure only by one package and all the other packages would merge their menu items into the appropriate menu group.

Creating a Placeholder Package

As the first step, I created a “menu-only” package named VSXtraMenuPlaceHolder with the VSPackage wizard to set up the VSCT description for the menu structure I want the merge the commands of other packages into. I started the VSPackage wizard and created a package with a simple menu command. This setting had the wizard create a VSCT file. I used the VSXtra runtime for the package, so the next step was to add a reference for VSXtra. Then I changed the package class to its final code:

using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Shell;

using VSXtra;

 

namespace DeepDiver.VSXtraMenuPlaceHolder

{

  [PackageRegistration(UseManagedResourcesOnly = true)]

  [DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\9.0")]

  [InstalledProductRegistration(false, "#110", "#112", "1.0",

    IconResourceID = 400)]

  [ProvideLoadKey("Standard", "1.0", "VSXtraMenuPlaceHolder", "DeepDiver", 1)]

  [ProvideMenuResource(1000, 1)]

  [Guid(GuidList.guidVSXtraMenuPlaceHolderPkgString)]

  public sealed class VSXtraMenuPlaceHolderPackage : PackageBase

  {

  }

}

I set up the VSCT file of the package:

<?xml version="1.0" encoding="utf-8"?>

<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/...">

  <Extern href="stdidcmd.h"/>

  <Extern href="vsshlids.h"/>

  <Extern href="msobtnid.h"/>

 

  <Commands package="guidVSXtraMenuPlaceHolderPkg">

    <Menus>

      <!-- Top level "VSXtra" menu -->

      <Menu guid="guidVSXtraCmdSet" id="VSXtraMainMenu" priority="0x800" 

        type="Menu">

        <Parent guid="guidSHLMainMenu" id="IDG_VS_MM_BUILDDEBUGRUN" />

        <CommandFlag>AlwaysCreate</CommandFlag>

        <Strings>

          <ButtonText>VSXtra</ButtonText>

          <CommandName>VSXtra</CommandName>

        </Strings>

      </Menu>

 

      <!-- "Package Samples" submenu placed into the "VSXtra" menu -->

      <Menu guid="guidVSXtraCmdSet" id="PackageSubMenu" priority="0x100"

        type="Menu">

        <Parent guid="guidVSXtraCmdSet" id="TopLevelMenuGroup" />

        <Strings>

          <ButtonText>Package Samples</ButtonText>

          <CommandName>Package Samples</CommandName>

        </Strings>

      </Menu>

 

      <!-- "Menu Samples" submenu placed into the "VSXtra" menu -->

      <Menu guid="guidVSXtraCmdSet" id="MenusSubMenu" priority="0x200"

        type="Menu">

        <Parent guid="guidVSXtraCmdSet" id="TopLevelMenuGroup" />

        <Strings>

          <ButtonText>Menu Samples</ButtonText>

          <CommandName>Menu Samples</CommandName>

        </Strings>

      </Menu>

 

      <!-- "Output Window Samples" submenu placed into the "VSXtra" menu -->

      <Menu guid="guidVSXtraCmdSet" id="OutputWindowSubMenu" priority="0x200"

        type="Menu">

        <Parent guid="guidVSXtraCmdSet" id="TopLevelMenuGroup" />

        <Strings>

          <ButtonText>Output Window Samples</ButtonText>

          <CommandName>Output Window Samples</CommandName>

        </Strings>

      </Menu>

 

      <!-- "Tool Window Samples" submenu placed into the "VSXtra" menu -->

      <Menu guid="guidVSXtraCmdSet" id="ToolWindowSubMenu" priority="0x200"

        type="Menu">

        <Parent guid="guidVSXtraCmdSet" id="TopLevelMenuGroup" />

        <Strings>

          <ButtonText>Tool Window Samples</ButtonText>

          <CommandName>Tool Window Samples</CommandName>

        </Strings>

      </Menu>

    </Menus>

   

    <Groups>

      <!-- Logical group for holding the submenus of the "VSXtra" -->

      <Group guid="guidVSXtraCmdSet" id="TopLevelMenuGroup" priority="0x0100">

        <Parent guid="guidVSXtraCmdSet" id="VSXtraMainMenu"/>

      </Group>

 

      <!-- Logical group for holding items in the "Package Samples" submenus -->

      <Group guid="guidVSXtraCmdSet" id="PackageMenuGroup" priority="0x0100">

        <Parent guid="guidVSXtraCmdSet" id="PackageSubMenu"/>

      </Group>

 

      <!-- Logical group for items in the "Menu Samples" submenu -->

      <Group guid="guidVSXtraCmdSet" id="MenusMenuGroup" priority="0x0200">

        <Parent guid="guidVSXtraCmdSet" id="MenusSubMenu"/>

      </Group>

 

      <!-- Logical group for items the "Output Window Samples" submenu -->

      <Group guid="guidVSXtraCmdSet" id="OutputWindowMenuGroup" priority="0x0200">

        <Parent guid="guidVSXtraCmdSet" id="OutputWindowSubMenu"/>

      </Group>

 

      <!-- Logical group for items in the "Tool Window Samples" submenus -->

      <Group guid="guidVSXtraCmdSet" id="ToolWindowMenuGroup" priority="0x0200">

        <Parent guid="guidVSXtraCmdSet" id="ToolWindowSubMenu"/>

      </Group>

    </Groups>

   

  </Commands>

 

  <Symbols>

    <GuidSymbol name="guidVSXtraMenuPlaceHolderPkg" value="..." />

 

    <!-- Symbols for the VSXtra menus -->

    <GuidSymbol name="guidVSXtraCmdSet" value="...">

      <IDSymbol name="VSXtraMainMenu" value="0x1000" />

      <IDSymbol name="TopLevelMenuGroup" value="0x1001" />

      <IDSymbol name="PackageSubMenu" value="0x1002" />

      <IDSymbol name="MenusSubMenu" value="0x1003" />

      <IDSymbol name="OutputWindowSubMenu" value="0x1004" />

      <IDSymbol name="ToolWindowSubMenu" value="0x1005" />

      <IDSymbol name="PackageMenuGroup" value="0x1102" />

      <IDSymbol name="MenusMenuGroup" value="0x1103" />

      <IDSymbol name="OutputWindowMenuGroup" value="0x1104" />

      <IDSymbol name="ToolWindowMenuGroup" value="0x1105" />

    </GuidSymbol>

  </Symbols>

 

</CommandTable>

When you run this package, you cannot see the VSXtra menu because that does not contain any command yet. In order to see the items, we must merge other packages’ menu commands into the menu.

Merging Menu items from Other Packages

I took the first package to merge into the common VSXtra menu. The package had the following VSCT file:

<?xml version="1.0" encoding="utf-8"?>

<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/...">

  <Extern href="stdidcmd.h"/>

  <Extern href="vsshlids.h"/>

  <Extern href="msobtnid.h"/>

 

  <Commands package="guidOutputVsRegistryPkg">

    <Groups>

      <Group guid="guidOutputVsRegistryCmdSet" id="MyMenuGroup" priority="0x100">

        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>

      </Group>

    </Groups>

 

    <Buttons>

      <Button guid="guidOutputVsRegistryCmdSet" id="cmdidDisplayRegistryValues"

        priority="0x0100" type="Button">

        <Parent guid="guidOutputVsRegistryCmdSet" id="MyMenuGroup" />

        <Icon guid="guidOfficeIcon" id="msotcidTable" />

        <Strings>

          <CommandName>cmdidDisplayRegistryValues</CommandName>

          <ButtonText>Display Local Registry</ButtonText>

        </Strings>

      </Button>

    </Buttons>

  </Commands>

 

  <Symbols>

    <GuidSymbol name="guidOutputVsRegistryPkg" value="..." />

 

    <GuidSymbol name="guidOutputVsRegistryCmdSet" value="...">

      <IDSymbol name="MyMenuGroup" value="0x1020" />

      <IDSymbol name="cmdidDisplayRegistryValues" value="0x0100" />

    </GuidSymbol>

  </Symbols>

 

</CommandTable>

The only package command here is put into a group parented in the Tools menu. In order to move it to the VSXtra top level menu, we have to change the VSCT file with the following steps:

—  Step 1: The symbols related to the corresponding VSXtra submenu should be imported into the Symbols section. Importing actually means copying the appropriate symbols from the VSXtraMenuPlaceHolder package’s VSCT file and paste into this VSCT.

—  Step 2: The group parented in the Tools menu should be redirected to the VSXtra menu.

Here is the VSCT file after the changes:

<?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">

  <Extern href="stdidcmd.h"/>

  <Extern href="vsshlids.h"/>

  <Extern href="msobtnid.h"/>

 

  <Commands package="guidOutputVsRegistryPkg">

    <Groups>

      <Group guid="guidOutputVsRegistryCmdSet" id="MyMenuGroup" priority="0x100">

        <Parent guid="guidVSXtraCmdSet" id="OutputWindowSubMenu"/>

      </Group>

    </Groups>

 

    <Buttons>

      <Button guid="guidOutputVsRegistryCmdSet" id="cmdidDisplayRegistryValues"

        priority="0x0100" type="Button">

        <Parent guid="guidOutputVsRegistryCmdSet" id="MyMenuGroup" />

        <Icon guid="guidOfficeIcon" id="msotcidTable" />

        <Strings>

          <CommandName>cmdidDisplayRegistryValues</CommandName>

          <ButtonText>Display Local Registry</ButtonText>

        </Strings>

      </Button>

    </Buttons>

  </Commands>

 

  <Symbols>

    <GuidSymbol name="guidOutputVsRegistryPkg" value="..." />

    <GuidSymbol name="guidOutputVsRegistryCmdSet" value="...">

      <IDSymbol name="MyMenuGroup" value="0x1020" />

      <IDSymbol name="cmdidDisplayRegistryValues" value="0x0100" />

    </GuidSymbol>

 

    <!-- Symbols for the VSXtra menus -->

    <GuidSymbol name="guidVSXtraCmdSet" value="...">

      <IDSymbol name="OutputWindowSubMenu" value="0x1004" />

    </GuidSymbol>

  </Symbols>

</CommandTable>

I have changed all my demo packages’ VSCT files according to this pattern and I achieved the result I have imagined.

There is one more subtle thing you should care about if you do the same with your packages: The placeholder package should be built together your package. If you partition your demo packages into more than one solution, do not forget to add the placeholder package’s project to all of your solutions with the Add Existing Project function.

When you download the VSXtra source code, you can examine all details.


Posted Sep 06 2008, 03:56 PM by inovak
Filed under:

Comments

Visual Studio Hacks wrote Visual Studio Links #72
on Mon, Sep 8 2008 13:27

My latest in a series of the weekly, or more often, summary of interesting links I come across related to Visual Studio. Channel 9 Stuff: Project Rosetta This Week on C9- Dynamic Silverlight, VSTS, buying Caio, and Mech Wars Greg Duncan posted a link

Faxedhead wrote re: LearnVSXNow! #31 - Merging Package Menus with VSCT
on Thu, Oct 7 2010 23:20

Can this also be achieved using something like:

{Extern href="..\mainpackage\mainpackage.vsct"}

Rather than copying and pasting the symbols?

Spingbridge topsoil supplier wrote re: LearnVSXNow! #31 - Merging Package Menus with VSCT
on Sat, Feb 16 2013 12:11

Hey, thanks for the blog.Thanks Again. Really Cool.

buy clomid wrote re: LearnVSXNow! #31 - Merging Package Menus with VSCT
on Thu, Feb 28 2013 22:49

TeHMjQ Really enjoyed this post.Really thank you! Fantastic.