zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Windows Azure Cloud Service (14) 使用Windows Azure诊断收集日志记录数据
    Windows Azure Cloud Service (13) 用Visual Studio 2010 将应用程序部署到Windows Azure平台
    Windows Azure Cloud Service (15) 多个VM Instance场景下如何处理ASP.NET Session
    Windows Azure Storage (5) Windows Azure Drive
    Windows Azure Storage (7) 使用工具管理Windows Azure Storage
    SQL Azure(二) SQL Azure vs SQL Server
    webbrowser的自动提交
    提取视频的背景声音的软件
    Listview列排序的bug原因
    两个奇怪的问题
  • 原文地址:https://www.cnblogs.com/craft0625/p/15014262.html
Copyright © 2011-2022 走看看