• Visual Studio 扩展入门(四)菜单篇 上


    IDE 菜单栏包含 " 文件"、" 编辑"、" 视图"、" 窗口" 和 " 帮助" 等菜单类别。用户扩展Visual Studio的菜单建议参考官方准则说明:Visual Studio 的菜单和命令
    扩展的菜单在项目的 .vsct 文件中声明。

    从 Visual Studio 2019 开始,由扩展提供的顶级菜单放置在 " 扩展 " 菜单下。

    一、示例一:在 IDE 菜单栏上创建新菜单

    1、创建菜单命令

    1. 创建 VSIX 项目模板,并命名为TopLevelMenu。
    2. 通过" Visual c # 项> Extensibility(扩展性) > Command(命令)",添加自定义命令 TopLevelMenuCommand.cs。

    此时解决方案目录如下:
    image.png
    2、通过修改_.vsct_ 文件创建新菜单
     节点内包含多个节点,找到name属性为"guidTopLevelMenuPackageCmdSet "的节点,添加元素,如下:

    <IDSymbol name="TopLevelMenu" value="0x1021"/>
    

    在<Commands>节点之内,<Groups>节点之前创建<Menus>节点,并添加<Menu>节点,如下:

        <Menus>
    	    <Menu guid="guidTopLevelMenuPackageCmdSet" id="TopLevelMenu" priority="0x700" type="Menu">
    		    <Parent guid="guidSHLMainMenu" id="IDG_VS_MM_TOOLSADDINS" />
    		    <Strings>
    			    <ButtonText>Test Menu</ButtonText>
    		    </Strings>
    	    </Menu>
        </Menus>
    

    在 <Groups> 部分中,找到 <Group> 并将元素更改 <Parent> 为指向刚刚添加的菜单:

          <Group guid="guidTopLevelMenuPackageCmdSet" id="MyMenuGroup" priority="0x0600">
    	      <Parent guid="guidTopLevelMenuPackageCmdSet" id="TopLevelMenu"/>
          </Group>
    

    在 <Buttons> 部分中,找到 <Button> 节点。 然后,在 <Strings> 节点中,将 <ButtonText> 元素更改为 调用 TopLevelMenuCommand:

    <ButtonText>调用 TopLevelMenuCommand</ButtonText>
    

    调试效果如下:
    image.png
    点击命令效果:
    image.png

    二、示例二:向菜单中添加子菜单

    此示例基于示例一,在示例一的基础上继续扩展。
    1、通过修改.vsct 文件创建子菜单
    打开TopLevelMenuPackage. vsct。
    继续找到name属性为"guidTopLevelMenuPackageCmdSet "的<GuidSymbol>节点,添加<IDSymbol>元素,如下:

    			<IDSymbol name="SubMenu" value="0x1100"/>
    			<IDSymbol name="SubMenuGroup" value="0x1150"/>
    			<IDSymbol name="cmdidTestSubCommand" value="0x0105"/>
    

    将新创建的子菜单添加到 <Menus> 部分:

    			<Menu guid="guidTopLevelMenuPackageCmdSet" id="SubMenu" priority="0x0100" type="Menu">
    				<Parent guid="guidTopLevelMenuPackageCmdSet" id="MyMenuGroup"/>
    				<Strings>
    					<ButtonText>Sub Menu</ButtonText>
    					<CommandName>Sub Menu</CommandName>
    				</Strings>
    			</Menu>
    

    将定义的菜单组添加到 <Groups> 部分,并使其成为子菜单的子菜单。

    			<Group guid="guidTopLevelMenuPackageCmdSet" id="SubMenuGroup" priority="0x0000">
    				<Parent guid="guidTopLevelMenuPackageCmdSet" id="SubMenu"/>
    			</Group>
    

    向元素<Buttons>部分添加一个新 <Button>  ,以将定义的命令cmdidTestSubCommand成为子菜单上的一项。

    			<Button guid="guidTopLevelMenuPackageCmdSet" id="cmdidTestSubCommand" priority="0x0000" type="Button">
    				<Parent guid="guidTopLevelMenuPackageCmdSet" id="SubMenuGroup" />
    				<Icon guid="guidImages" id="bmpPic2" />
    				<Strings>
    					<CommandName>cmdidTestSubCommand</CommandName>
    					<ButtonText>调用Sub Command</ButtonText>
    				</Strings>
    			</Button>
    

    运行调试,效果如下:
    image.png
    2、添加命令
    打开自定义命令TopLevelMenuCommand.cs,添加命令定义:

    public const int cmdidTestSubCmd = 0x0105;
    

    在构造方法,为子菜单添命令。

    CommandID subCommandID = new CommandID(CommandSet, cmdidTestSubCmd);
    MenuCommand subItem = new MenuCommand(new EventHandler(SubItemCallback), subCommandID);
    commandService.AddCommand(subItem);
    

    添加事件回调:

    private void SubItemCallback(object sender, EventArgs e)
    {
        ThreadHelper.ThrowIfNotOnUIThread();
        string message = string.Format(CultureInfo.CurrentCulture, "Inside {0}.SubItemCallback()", this.GetType().FullName);
        string title = "CmdIdTestSubCmd";
    
        // Show a message box to prove we were here
        VsShellUtilities.ShowMessageBox(
            this.package,
            message,
            title,
            OLEMSGICON.OLEMSGICON_INFO,
            OLEMSGBUTTON.OLEMSGBUTTON_OK,
            OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
    }
    

    运行调试,点击子菜单命令效果如下:
    image.png

    三、示例三:创建动态项列表命令


    动态菜单列表以菜单上的占位符开头。 每次显示菜单时,IDE请求VSPackage,显示在占位符中的所有命令 。 动态列表可以出现在菜单上的任何位置。 但是,动态列表通常存储在子菜单上或菜单底部。如窗口菜单底部动态显示当前打开窗口:
    image.png
    通过使用这些设计模式,可以在不影响菜单上其他命令的位置的情况下,使命令的动态列表展开和收缩。
    从技术上讲,动态列表还可以应用于工具栏。 但是,官方不建议这种用法,因为工具栏应保持不变,除非用户执行特定步骤来更改它。

    在本示例中,动态 MRU 列表显示在现有子菜单的底部,并与子菜单的其余部分分隔开。此示例基于示例二,在示例二的基础上继续扩展。
    1、通过修改_.vsct_ 文件创建动态项占位
    在 Symbols 部分中,在 GuidSymbol 名为 guidTestCommandPackageCmdSet 的节点中,添加 MRUListGroup 组和命令的符号 cmdidMRUList ,如下所示:

    			<IDSymbol name="MRUListGroup" value="0x1200"/>
    			<IDSymbol name="cmdidMRUList" value="0x0200"/>
    

    在Groups元素部分,添加声明的组:

    			<Group guid="guidTopLevelMenuPackageCmdSet" id="MRUListGroup" priority="0x0100">
    				<Parent guid="guidTopLevelMenuPackageCmdSet" id="SubMenu"/>
    			</Group>
    

    添加动态命令“查看当前选择脚本”占位按钮,DynamicItemStart标志允许动态生成命令:

    			<Button guid="guidTopLevelMenuPackageCmdSet" id="cmdidMRUList" type="Button" priority="0x0100">
    				<Parent guid="guidTopLevelMenuPackageCmdSet" id="MRUListGroup" />
    				<CommandFlag>DynamicItemStart</CommandFlag>
    				<Strings>
    					<CommandName>cmdidMRUList</CommandName>
    					<ButtonText>查看当前选择脚本</ButtonText>
    				</Strings>
    			</Button>
    

    运行调试,效果如下:
    image.png
    2、填充 MRU 列表
    添加命名空间:

    using System.Collections;
    

    添加命令定义:

    public const uint cmdidMRUList = 0x200;
    

    在构造函数内添加以下代码:

    this.InitMRUMenu(commandService);
    

    添加以下代码:
    InitMRUMenu():初始化 MRU list 菜单命令
    InitializeMRUList():初始化在 MRU 列表中显示的项的字符串列表。通过mc.Visible = false;可以将命令设为不可见。
    BeforeQueryStatus为显示菜单命令之前调用的事件处理。

            #region InitializeMRU
    
            private int numMRUItems = 4;//生成项数
            private int baseMRUID = (int)cmdidMRUList;//初始占位符ID
            private ArrayList mruList;//命令名称字符串
    
            /// <summary>
            /// 初始化在 MRU 列表中显示的项的字符串列表
            /// </summary>
            private void InitializeMRUList()
            {
                if (null != mruList) return;
    
                mruList = new ArrayList();
    
                for (int i = 0; i < this.numMRUItems; i++) mruList.Add(string.Format(CultureInfo.CurrentCulture, "C# Script {0}.cs", i + 1));
            }
            /// <summary>
            /// 初始化 MRU list 菜单命令
            /// </summary>
            /// <param name="mcs"></param>
            private void InitMRUMenu(OleMenuCommandService mcs)
            {
                InitializeMRUList();
    
                for (int i = 0; i < this.numMRUItems; i++)
                {
                    var cmdID = new CommandID(CommandSet, this.baseMRUID + i);//赋值ID
                    var mc = new OleMenuCommand(OnMRUExec, cmdID);
                    mc.BeforeQueryStatus += OnMRUQueryStatus;//当客户端请求命令的状态时调用,准备打卡
                    mcs.AddCommand(mc);
                }
    
            }
            /// <summary>
            /// 显示回调
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void OnMRUQueryStatus(object sender, EventArgs e)
            {
                OleMenuCommand menuCommand = sender as OleMenuCommand;
                if (null == menuCommand) return;
    
                int MRUItemIndex = menuCommand.CommandID.ID - this.baseMRUID;
                if (MRUItemIndex >= 0 && MRUItemIndex < this.mruList.Count)
                {
                    menuCommand.Text = this.mruList[MRUItemIndex] as string;
                }
    
            }
            /// <summary>
            /// 触发处理命令回调
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void OnMRUExec(object sender, EventArgs e)
            {
                var menuCommand = sender as OleMenuCommand;
                if (null == menuCommand) return;
    
                int MRUItemIndex = menuCommand.CommandID.ID - this.baseMRUID;
    
                if (MRUItemIndex >= 0 && MRUItemIndex < this.mruList.Count)
                {
                    string selection = this.mruList[MRUItemIndex] as string;
                    //向后排序
                    for (int i = MRUItemIndex; i > 0; i--)
                    {
                        this.mruList[i] = this.mruList[i - 1];
                    }
                    this.mruList[0] = selection;
    
                    System.Windows.Forms.MessageBox.Show(string.Format(CultureInfo.CurrentCulture, "打开脚本 {0}", selection));
                }
    
            }
            #endregion
    

    运行调试效果,选中当前“查看当前选择脚本”,效果如下:
    image.png
    再次打开列表,选中C# Scripts 2.cs:
    image.png
    发现列表更新,点击“查看当前选择脚本”,弹出“打开C# Scripts 2.cs”,效果如下:
    image.png
    image.png

    四、示例四:动态更改菜单命令的文本

    在示例三中使用了BeforeQueryStatus事件接口和OleMenuCommand,通过示例四继续加深对这两个部分的理解。为了避免和其他菜单命令混淆,进行清理卸载扩展插件,并创建新的Visx工程。


    1、创建菜单命令

    1. 创建 VSIX 项目模板,并命名为MenuText。
    2. 通过" Visual c # 项> Extensibility(扩展性) > Command(命令)",添加自定义命令 ChangeMenuText.cs

    此时解决方案目录如下:
    image.png

    2、修改.vsct文件,添加标志
    添加动态命令“Invoke ChangeMenuText”占位按钮,并添加添加<CommandFlag>元素,TextChanges标志命令文本可修改:

          <Button guid="guidMenuTextPackageCmdSet" id="ChangeMenuTextId" priority="0x0100" type="Button">
            <Parent guid="guidMenuTextPackageCmdSet" id="MyMenuGroup" />
            <Icon guid="guidImages" id="bmpPic1" />
            <CommandFlag>TextChanges</CommandFlag>
            <Strings>
              <ButtonText>Invoke ChangeMenuText</ButtonText>
            </Strings>
          </Button>
    

    3、修改_ChangeMenuText.cs_添加事件
    修改ChangeMenuText构造方法,使用OleMenuCommand命令替代MenuCommand,并注册BeforeQueryStatus的事件处理。代码如下:

            private ChangeMenuText(AsyncPackage package, OleMenuCommandService commandService)
            {
                this.package = package ?? throw new ArgumentNullException(nameof(package));
                commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
    
                var menuCommandID = new CommandID(CommandSet, CommandId);
                var menuItem = new OleMenuCommand(this.Execute, menuCommandID);
                menuItem.BeforeQueryStatus += OnBeforeQueryStatus;
                commandService.AddCommand(menuItem);
            }
    

    添加OnBeforeQueryStatus实现:

            private void OnBeforeQueryStatus(object sender, EventArgs e)
            {
                var myCommand = sender as OleMenuCommand;
                if (null != myCommand)
                {
                    myCommand.Text = "New Text";
                }
            }
    
    • OleMenuCommand继承自MenuCommand,并包含BeforeQueryStatus事件处理。对BeforeQueryStatus,官方文档的说明只有一句话:“当客户端请求命令时调用”。但在事件内加入弹窗或断点调试时发现,请求命令时机并不是特表明确,不像Execute()方法一样,点击即刻触发。我通过反编译Microsoft.VisualStudio.Shell.15.0.dll程序集查找到相关信息:

    image.png
    BeforeQueryStatus事件处理和OleStatus有关,当调用OleStatus的get访问器就会触发。

    运行调试效果,效果如下:
    image.png
    点击"Invoke ChangeMenuText"按钮,弹出默认提示框后,发现按钮名称改变:
    image.png

    五、示例五:更改菜单命令按钮的外观

    在示例四的基础上,添加更改菜单命令按钮外观的功能。

    1、修改ChangeMenuText.cs

    修改Execute命令执行方法,当命令按钮文本变成“New Text”时,触发命令按钮后,按钮不可选中:

            private void Execute(object sender, EventArgs e)
            {
                ThreadHelper.ThrowIfNotOnUIThread();
                var command = sender as OleMenuCommand;
                if (command.Text == "New Text") ChangeMyCommand(command.CommandID.ID, false);
            }
            public bool ChangeMyCommand(int cmdID, bool enableCmd)
            {
                bool cmdUpdated = false;
                var mcs = this.package.GetService<IMenuCommandService, OleMenuCommandService>();
                var newCmdID = new CommandID(CommandSet, cmdID);
                MenuCommand mc = mcs.FindCommand(newCmdID);
                if (mc != null)
                {
                    mc.Enabled = enableCmd;
                    //mc.Checked = true;
                    cmdUpdated = true;
                }
                return cmdUpdated;
            }
    
    • 通过package.GetService<IMenuCommandService, OleMenuCommandService>():获取当前包的菜单服务。
    • 通过调用服务FindCommand:通过CommandID查找到菜单命令。
    • 通过Enabled属性修改命令按钮不可用。通过Visible可设为不可见。通过Checked可设置选中状态。

    上述代码也可以直接简写为:

            private void Execute(object sender, EventArgs e)
            {
                ThreadHelper.ThrowIfNotOnUIThread();
                var command = sender as OleMenuCommand;
                if (command.Text == "New Text")
                {
                    MenuCommand mc = package.GetService<IMenuCommandService, OleMenuCommandService>().FindCommand(command.CommandID);
                    if (mc != null) mc.Enabled = false;
                }
            }
    

    运行调试效果,效果如下:
    image.png
    点击"Invoke ChangeMenuText"按钮,弹出默认提示框后,发现按钮名称改变:
    image.png
    再次点击按钮,按钮不可用:
    image.png

    博客的示例源码:https://github.com/21thCenturyBoy/VSIX_HelloWorld

  • 相关阅读:
    Linux下安装nginx
    MySQL基础
    win10 安装MySQL 5.7.27
    Java IO之File
    java并发编程之ThreadLocal
    原生JS 的60秒倒计时!
    vueX 配合路由导航配置动态路由
    JS获取 当前时间是本年的第几天? 第几周?
    vue书写echarts 100px大小问题
    VUE 父组件与子组件双向数据绑定的方法 做弹框绑定列表页数据的举例
  • 原文地址:https://www.cnblogs.com/craft0625/p/15014262.html
Copyright © 2020-2023  润新知