Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VS 2015 dynamic menu commands don't work as advertised

I'm trying to create dynamic menu items in a VS 2015 extension. The best I can do is to leave the placeholder command visible and active. Here's the most minimal example I could create starting with the VS wizards (sorry for the length, but it really is the most minimal example I could create):

Command1Package.cs:

using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;

namespace minimal {
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(Command1Package.PackageGuidString)]
    [ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string)]
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
    public sealed class Command1Package : Package {
        public const string PackageGuidString = "3e88287b-7b79-403d-ae8d-3329af218869";
        public Command1Package() {
            }

        #region Package Members

        protected override void Initialize() {
            Debug.WriteLine("minimal package instantiated");
            Command1.Initialize(this, GetService(typeof(IMenuCommandService)) as OleMenuCommandService);
            base.Initialize();
            }

        #endregion
        }
    }

Command1.cs:

using Microsoft.VisualStudio.Shell;
using System;
using System.ComponentModel.Design;
using System.Diagnostics;

namespace minimal {
    internal sealed class Command1 {
        public static readonly Guid CommandSet = new Guid("c1388361-6429-452c-8ba0-580d292ef0ca");
        private Command1() {
            }

        public static void Initialize(Package package, OleMenuCommandService mcs) {
            AddOneCommand(mcs, 0x0100, "Renamed placeholder command");  // the placeholder
            AddOneCommand(mcs, 0x0101, "Dynamic command 1"); // a dynamic command
            AddOneCommand(mcs, 0x0102, "Dynamic command 2");
            }

        internal static void AddOneCommand(OleMenuCommandService mcs, uint cmdid, string cmdname) {
            Debug.WriteLine("AddOneCommand" + cmdid.ToString("X4"));
            CommandID id = new CommandID(CommandSet, (int) cmdid);
            OleMenuCommand mc = new OleMenuCommand(new EventHandler(MenuItemCallback), id, cmdname);
            mc.BeforeQueryStatus += OnBeforeQueryStatus;
            mcs.AddCommand(mc);
            }

        private static void OnBeforeQueryStatus(object sender, EventArgs e) {
            OleMenuCommand mc = sender as OleMenuCommand;
            Debug.WriteLine("OnBeforeQueryStatus called for " + mc.CommandID.ToString());
            }

        private static void MenuItemCallback(object sender, EventArgs e) {
            OleMenuCommand mc = sender as OleMenuCommand;
            System.Windows.Forms.MessageBox.Show("MenuItemCallback called for " + mc.CommandID.ToString());
            }
        }
    }

Command1Package.vsct:

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

        <Groups>
            <Group guid="guidCommand1PackageCmdSet" id="MyMenuGroup" priority="0x0600">
                <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
            </Group>
            <Group guid="guidCommand1PackageCmdSet" id="MyMenuSubgroup" priority="0x0100">
                <Parent guid="guidCommand1PackageCmdSet" id="SubMenu"/>
            </Group>
        </Groups>

        <Menus>
            <Menu guid="guidCommand1PackageCmdSet" id="SubMenu" priority="0x0100" type="Menu">
                <Parent guid="guidCommand1PackageCmdSet" id="MyMenuGroup"/>
                <Strings>
                    <ButtonText>Minimal commands</ButtonText>
                    <CommandName>MinimalCommands</CommandName>
                </Strings>
            </Menu>
        </Menus>

        <Buttons>
            <Button guid="guidCommand1PackageCmdSet" id="Command1Id" priority="0x0100" type="Button">
                <Parent guid="guidCommand1PackageCmdSet" id="MyMenuSubgroup" />
                <CommandFlag>DynamicItemStart</CommandFlag>
                <CommandFlag>TextChanges</CommandFlag>
                <CommandFlag>DynamicVisibility</CommandFlag>
                <Strings>
                    <ButtonText>Invoke Command1</ButtonText>
                    <CommandName>Command1</CommandName>
                </Strings>
            </Button>
        </Buttons>

    </Commands>

    <Symbols>
        <GuidSymbol name="guidCommand1Package" value="{3e88287b-7b79-403d-ae8d-3329af218869}" />
        <GuidSymbol name="guidCommand1PackageCmdSet" value="{c1388361-6429-452c-8ba0-580d292ef0ca}">
            <IDSymbol name="MyMenuGroup" value="0x1020" />
            <IDSymbol name="MyMenuSubgroup" value="0x1021"/>
            <IDSymbol name="SubMenu" value="0x200"/>
            <IDSymbol name="Command1Id" value="0x0100" />
        </GuidSymbol>
    </Symbols>
</CommandTable>

Some observations:

  1. Without the [ProvideAutoLoad] attribute on the package, the framework doesn't instantiate the package until after you execute the placeholder command, which is the only item on the submenu at that point. It seems like the code generated by the VS wizard should do this for you, since this attribute (among many others) isn't mentioned in the help entry for the Package class.
  2. I really wanted the placeholder not to appear on the menu. But marking it invisible (in OnBeforeQueryStatus) made the entire submenu vanish. The best I could do was to change the name of this command. The help entry for "How to: Dynamically Add Menu Items" doesn't mention this. (Don't bother reading the entry for VS 2015, because the example code is needlessly complex and doesn't show how to do the one simple thing of adding a menu command dynamically. The VS 2012 example is much easier to follow.)
  3. <rant>Dang, but this is so much easier in MFC! GetMenu(), GetSubMenu(), etc. I thought .net was supposed to be easier than traditional Win32 programming.</rant>
  4. Is there a better way to add dynamic items to a menu?
like image 625
Walter Oney Avatar asked Nov 22 '22 05:11

Walter Oney


1 Answers

Yes, it is much harder than in MFC.

  1. [ProvideAutoLoad] is indeed required.

  2. You should not add the placeholder command. Start from

    AddOneCommand(mcs, 0x0100, "Dynamic command 1");

Usually you add <CommandFlag>DefaultInvisible</CommandFlag> to the DynamicItemStart command and set menuCommand.Text and menuCommand.Visible in the OnBeforeQueryStatus handler.

like image 107
Sergey Vlasov Avatar answered Jan 20 '23 11:01

Sergey Vlasov