Search code examples
c#visual-studio-extensionsvspackagevsct

Visual Studio menu item appearing multiple times


I've created a menu item for the extension I'm working on; however, it is showing up 4 times in the Tools menu instead of just once. Below is what I have, but I have been unable to figure out why the menu item is showing up more than once.

VSCT File

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

  <Commands package="guidTemplatePackPkg">
    <Groups>
      <Group guid="guidTemplatePackCmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
      </Group>
    </Groups>

    <Buttons>

      <Button guid="guidTemplatePackCmdSet" id="cmdidMyCommand" priority="0x2000" type="Button">
        <Parent guid="guidSHLMainMenu" id="IDG_VS_CTXT_PROJECT_ADD_REFERENCES" />
        <CommandFlag>DynamicVisibility</CommandFlag>
        <CommandFlag>DefaultInvisible</CommandFlag>
        <Strings>
          <CommandName>AddSideWaffleProject</CommandName>
          <ButtonText>Add Template Reference (SideWaffle project)</ButtonText>
        </Strings>
      </Button>
    </Buttons> 
  </Commands>

  <!-- SideWaffle Menu Options -->
  <Commands package="guidMenuOptionsPkg">
    <Groups>
      <Group guid="guidMenuOptionsCmdSet" id="SWMenuGroup" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
      </Group>
    </Groups>

    <Buttons>
      <Button guid="guidMenuOptionsCmdSet" id="cmdidOpenSWMenu" priority="0x0100" type="Button">
        <Parent guid="guidMenuOptionsCmdSet" id="SWMenuGroup" />
        <Icon guid="guidImages" id="bmpPic1" />
        <Strings>
          <ButtonText>SideWaffle Settings</ButtonText>
        </Strings>
      </Button>
    </Buttons>

    <Bitmaps>
      <Bitmap guid="guidImages" href="Resources\Images.png" usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"/>
    </Bitmaps>
  </Commands>
  <!-- End SideWaffle Menu Options -->

  <Symbols>
    <GuidSymbol name="guidTemplatePackPkg" value="{e6e2a48e-387d-4af2-9072-86a5276da6d4}" />

    <GuidSymbol name="guidTemplatePackCmdSet" value="{a94bef1a-053e-4066-a851-16e5f6c915f1}">
      <IDSymbol name="MyMenuGroup" value="0x1020" />
      <IDSymbol name="cmdidMyCommand" value="0x0100" />
    </GuidSymbol>

    <!-- SideWaffle Menu Options -->
    <GuidSymbol name="guidMenuOptionsPkg" value="{ee0cf212-810b-45a1-8c62-e10041913c94}" />
    <GuidSymbol name="guidMenuOptionsCmdSet" value="{c75eac28-63cd-4766-adb1-e655471525ea}">
      <IDSymbol name="SWMenuGroup" value="0x1020" />
      <IDSymbol name="cmdidOpenSWMenu" value="0x0100" />
    </GuidSymbol>

    <GuidSymbol name="guidImages" value="{e2bf6a31-afea-46fb-9397-0c2add3a59d8}" >
      <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>
    <!-- End SideWaffle Menu Options -->
  </Symbols>

</CommandTable>

TemplatePackPackage.cs

using System;
using System.Linq;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using System.Collections.Generic;
using EnvDTE;
using EnvDTE80;
using LigerShark.Templates.DynamicBuilder;
using TemplatePack.Tooling;

namespace TemplatePack
{
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(GuidList.guidTemplatePackPkgString)]
    [ProvideAutoLoad(UIContextGuids80.SolutionExists)]
    public sealed class TemplatePackPackage : Package
    {
        private DTE2 _dte;

        protected override void Initialize()
        {
            base.Initialize();
            _dte = GetService(typeof(DTE)) as DTE2;

            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (null != mcs)
            {
                CommandID cmdId = new CommandID(GuidList.guidTemplatePackCmdSet, (int)PkgCmdIDList.cmdidMyCommand);
                OleMenuCommand button = new OleMenuCommand(ButtonClicked, cmdId);
                button.BeforeQueryStatus += button_BeforeQueryStatus;
                mcs.AddCommand(button);
            }

            /*if(Environment.GetEnvironmentVariable("SideWaffleEnableDynamicTemplates") != null)*/{
                try {
                    new DynamicTemplateBuilder().ProcessTemplates();
                }
                catch (Exception ex) {
                    // todo: replace with logging or something
                    System.Windows.MessageBox.Show(ex.ToString());
                }
            }
        }

        void button_BeforeQueryStatus(object sender, EventArgs e)
        {
            var button = (OleMenuCommand)sender;
            var project = GetSelectedProjects().ElementAt(0);

            // TODO: We should only show this if the target project has the TemplateBuilder NuGet pkg installed
            //       or something similar to that.
            button.Visible = true;
            // button.Visible = project.IsWebProject();
        }

        private void ButtonClicked(object sender, EventArgs e)
        {
            Project currentProject = GetSelectedProjects().ElementAt(0);
            var projects = _dte.Solution.GetAllProjects();
            var names = from p in projects
                        where p != currentProject
                        select p.Name;

            ProjectSelector selector = new ProjectSelector(names);
            bool? isSelected = selector.ShowDialog();

            if (isSelected.HasValue && isSelected.Value)
            {
                // need to save everything because we will directly write to the project file in the creator
                _dte.ExecuteCommand("File.SaveAll");

                TemplateReferenceCreator creator = new TemplateReferenceCreator();
                var selectedProject = projects.First(p => p.Name == selector.SelectedProjectName);
                creator.AddTemplateReference(currentProject, selectedProject);
            }
        }

        public IEnumerable<Project> GetSelectedProjects()
        {
            var items = (Array)_dte.ToolWindows.SolutionExplorer.SelectedItems;
            foreach (UIHierarchyItem selItem in items)
            {
                var item = selItem.Object as Project;
                if (item != null)
                {
                    yield return item;
                }
            }
        }
    }

    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(GuidList.guidMenuOptionsPkgString)]
    public sealed class MenuOptionsPackage : Package
    {     
        // Overridden Package Implementation
        #region Package Members

        protected override void Initialize()
        {
            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.guidMenuOptionsCmdSet, (int)PkgCmdIDList.cmdidMyCommand);
                MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
                mcs.AddCommand( menuItem );
            }
        }
        #endregion

        private void MenuItemCallback(object sender, EventArgs e)
        {
            // Here is where our UI (i.e. user control) will go to do all the settings.
            var window = new SettingsForm();
            window.Show();
        }
    }
}

PackageConstants.cs

using System;

namespace TemplatePack
{
    static class GuidList
    {
        public const string guidTemplatePackPkgString = "e6e2a48e-387d-4af2-9072-86a5276da6d4";
        public const string guidTemplatePackCmdSetString = "a94bef1a-053e-4066-a851-16e5f6c915f1";

        public static readonly Guid guidTemplatePackCmdSet = new Guid(guidTemplatePackCmdSetString);

        // SideWaffle Remote Source Settings
        public const string guidMenuOptionsPkgString = "ee0cf212-810b-45a1-8c62-e10041913c94";
        public const string guidMenuOptionsCmdSetString = "c75eac28-63cd-4766-adb1-e655471525ea";

        public static readonly Guid guidMenuOptionsCmdSet = new Guid(guidMenuOptionsCmdSetString);
    }

    static class PkgCmdIDList
    {
        public const uint cmdidMyCommand = 0x100;
        public const uint SWMenuGroup = 0x100;
    };
}

I just can't seem to figure out what I might be doing wrong. Any suggestions?


Solution

  • ...what I might be doing wrong.

    You're essentially trying to bundle two packages (TemplatePackPackage & MenuOptionsPackage) into a single package, which messes up your .pkgdef file, which I believe is the cause of your issue.

    If you comment out the MenuOptionsPackage class, then you should see only one menu item - as intended.

    Any suggestions?

    1. Expose all your functionality through single Package class.
    2. Create two different extension projects each containing corresponding Package. If you need to access one through the other you can provide and consume services.