Поделиться через


Динамическое добавление элементов меню

Элементы меню можно добавить во время выполнения, указав DynamicItemStart флаг команды в определении заполнителя в файле командной таблицы Visual Studio (VSCT), а затем определив (в коде) количество элементов меню для отображения и обработки команд. При загрузке VSPackage заполнитель заменяется динамическими элементами меню.

Visual Studio использует динамические списки в списке последних использованных (MRU), в котором отображаются имена документов, открытых недавно, и список Windows , в котором отображаются имена открытых окон. Флаг DynamicItemStart в определении команды указывает, что команда является заполнителем до открытия VSPackage. При открытии VSPackage заполнитель заменяется на 0 или более команд, созданных во время выполнения и добавленных в динамический список. Возможно, вы не сможете увидеть позицию в меню, где отображается динамический список, пока не откроется VSPackage. Чтобы заполнить динамический список, Visual Studio просит VSPackage найти команду с идентификатором, первые символы которого совпадают с идентификатором заполнителя. Когда Visual Studio находит соответствующую команду, она добавляет имя команды в динамический список. Затем он увеличивает идентификатор и ищет другую команду сопоставления, чтобы добавить в динамический список, пока не будет больше динамических команд.

В этом пошаговом руководстве показано, как задать проект запуска в решении Visual Studio с помощью команды на панели инструментов Обозреватель решений. В нем используется контроллер меню с динамическим раскрывающимся списком проектов в активном решении. Чтобы эта команда не отображалась, если решение открыто или если открытое решение имеет только один проект, VSPackage загружается только в том случае, если решение имеет несколько проектов.

Дополнительные сведения о VSCT-файлах см. в файлах командной таблицы Visual Studio (VSCT).

Создание расширения с помощью команды меню

  1. Создайте проект VSIX с именем DynamicMenuItems.

  2. Когда проект откроется, добавьте пользовательский шаблон элемента команды и назовите его DynamicMenu. Дополнительные сведения см. в разделе "Создание расширения" с помощью команды меню.

Настройка элементов в VSCT-файле

Чтобы создать контроллер меню с динамическими элементами меню на панели инструментов, укажите следующие элементы:

  • Две группы команд, одна из них содержит контроллер меню и другую, содержащую элементы меню в раскрывающемся списке.

  • Один элемент меню типа MenuController

  • Две кнопки, которые действуют в качестве заполнителя для элементов меню и другой, которые предоставляют значок и подсказку на панели инструментов.

  1. В DynamicMenuPackage.vsct определите идентификаторы команд. Перейдите в раздел "Символы" и замените элементы IDSymbol в блоке GUIDDynamicMenuPackageCmdSet GuidSymbol. Необходимо определить элементы IDSymbol для двух групп, контроллера меню, команды заполнителя и команды привязки.

    <GuidSymbol name="guidDynamicMenuPackageCmdSet" value="{ your GUID here }">
        <IDSymbol name="MyToolbarItemGroup" value="0x1020" />
        <IDSymbol name="MyMenuControllerGroup" value="0x1025" />
        <IDSymbol name="MyMenuController" value ="0x1030"/>
        <IDSymbol name="cmdidMyAnchorCommand" value="0x0103" />
        <!-- NOTE: The following command expands at run time to some number of ids.
         Try not to place command ids after it (e.g. 0x0105, 0x0106).
         If you must add a command id after it, make the gap very large (e.g. 0x200) -->
        <IDSymbol name="cmdidMyDynamicStartCommand" value="0x0104" />
    </GuidSymbol>
    
  2. В разделе "Группы" удалите существующие группы и добавьте две только что определенные группы:

    <Groups>
        <!-- The group that adds the MenuController on the Solution Explorer toolbar.
             The 0x4000 priority adds this group after the group that contains the
             Preview Selected Items button, which is normally at the far right of the toolbar. -->
        <Group guid="guidDynamicMenuPackageCmdSet" id="MyToolbarItemGroup" priority="0x4000" >
            <Parent guid="guidSHLMainMenu" id="IDM_VS_TOOL_PROJWIN" />
        </Group>
        <!-- The group for the items on the MenuController drop-down. It is added to the MenuController submenu. -->
        <Group guid="guidDynamicMenuPackageCmdSet" id="MyMenuControllerGroup" priority="0x4000" >
            <Parent guid="guidDynamicMenuPackageCmdSet" id="MyMenuController" />
        </Group>
    </Groups>
    

    Добавьте MenuController. Задайте флаг команды DynamicVisibility, так как он не всегда отображается. Элемент ButtonText не отображается.

    <Menus>
        <!-- The MenuController to display on the Solution Explorer toolbar.
             Place it in the ToolbarItemGroup.-->
        <Menu guid="guidDynamicMenuPackageCmdSet" id="MyMenuController" priority="0x1000" type="MenuController">
            <Parent guid="guidDynamicMenuPackageCmdSet" id="MyToolbarItemGroup" />
            <CommandFlag>DynamicVisibility</CommandFlag>
            <Strings>
               <ButtonText></ButtonText>
           </Strings>
        </Menu>
    </Menus>
    
  3. Добавьте две кнопки, один в качестве заполнителя для динамических элементов меню и один в качестве привязки для MenuController.

    Родитель кнопки заполнителя — MyMenuControllerGroup. Добавьте флаги команд DynamicItemStart, DynamicVisibility и TextChanges в заполнитель. Элемент ButtonText не отображается.

    Кнопка привязки содержит значок и текст подсказки. Родитель кнопки привязки также является MyMenuControllerGroup. Вы добавите флаг команды NoShowOnMenuController, чтобы убедиться, что кнопка не отображается в раскрывающемся списке контроллера меню и флаг команды FixMenuController, чтобы сделать ее постоянной привязкой.

    <!-- The placeholder for the dynamic items that expand to N items at run time. -->
    <Buttons>
        <Button guid="guidDynamicMenuPackageCmdSet" id="cmdidMyDynamicStartCommand" priority="0x1000" >
          <Parent guid="guidDynamicMenuPackageCmdSet" id="MyMenuControllerGroup" />
          <CommandFlag>DynamicItemStart</CommandFlag>
          <CommandFlag>DynamicVisibility</CommandFlag>
          <CommandFlag>TextChanges</CommandFlag>
          <!-- This text does not appear. -->
          <Strings>
            <ButtonText>Project</ButtonText>
          </Strings>
        </Button>
    
        <!-- The anchor item to supply the icon/tooltip for the MenuController -->
        <Button guid="guidDynamicMenuPackageCmdSet" id="cmdidMyAnchorCommand" priority="0x0000" >
          <Parent guid="guidDynamicMenuPackageCmdSet" id="MyMenuControllerGroup" />
          <!-- This is the icon that appears on the Solution Explorer toolbar. -->
          <Icon guid="guidImages" id="bmpPicArrows"/>
          <!-- Do not show on the menu controller's drop down list-->
          <CommandFlag>NoShowOnMenuController</CommandFlag>
          <!-- Become the permanent anchor item for the menu controller -->
          <CommandFlag>FixMenuController</CommandFlag>
          <!-- The text that appears in the tooltip.-->
          <Strings>
            <ButtonText>Set Startup Project</ButtonText>
          </Strings>
        </Button>
    </Buttons>
    
  4. Добавьте значок в проект (в папке resources ), а затем добавьте ссылку на нее в VSCT-файле . В этом пошаговом руководстве мы используем значок со стрелками, включенный в шаблон проекта.

  5. Добавьте раздел VisibilityConstraints за пределы раздела "Команды" непосредственно перед разделом "Символы". (Если вы добавите его после символов, вы можете получить предупреждение.) Этот раздел гарантирует, что контроллер меню отображается только при загрузке решения с несколькими проектами.

    <VisibilityConstraints>
         <!--Make the MenuController show up only when there is a solution with more than one project loaded-->
        <VisibilityItem guid="guidDynamicMenuPackageCmdSet" id="MyMenuController" context="UICONTEXT_SolutionHasMultipleProjects"/>
    </VisibilityConstraints>
    

Реализация команды динамического меню

Вы создаете класс команд динамического меню, наследующий от OleMenuCommand. В этой реализации конструктор задает предикат, используемый для сопоставления команд. Чтобы задать MatchedCommandId свойство, идентифицирующее вызываемую команду, необходимо переопределить DynamicItemMatch метод, чтобы использовать этот предикат.

  1. Создайте файл класса C# с именем DynamicItemMenuCommand.cs и добавьте класс с именем DynamicItemMenuCommand, наследуемый отOleMenuCommand:

    class DynamicItemMenuCommand : OleMenuCommand
    {
    
    }
    
    
  2. Добавьте следующие директивы using:

    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.Shell.Interop;
    using System.ComponentModel.Design;
    
  3. Добавьте частное поле для хранения предиката соответствия:

    private Predicate<int> matches;
    
    
  4. Добавьте конструктор, наследуемый от OleMenuCommand конструктора, и задает обработчик команд и BeforeQueryStatus обработчик. Добавьте предикат для сопоставления команды:

    public DynamicItemMenuCommand(CommandID rootId, Predicate<int> matches, EventHandler invokeHandler, EventHandler beforeQueryStatusHandler)
        : base(invokeHandler, null /*changeHandler*/, beforeQueryStatusHandler, rootId)
    {
        if (matches == null)
        {
            throw new ArgumentNullException("matches");
        }
    
        this.matches = matches;
    }
    
  5. Переопределите DynamicItemMatch метод таким образом, чтобы он вызывает предикат совпадений и задает MatchedCommandId свойство:

    public override bool DynamicItemMatch(int cmdId)
    {
        // Call the supplied predicate to test whether the given cmdId is a match.
        // If it is, store the command id in MatchedCommandid
        // for use by any BeforeQueryStatus handlers, and then return that it is a match.
        // Otherwise clear any previously stored matched cmdId and return that it is not a match.
        if (this.matches(cmdId))
        {
            this.MatchedCommandId = cmdId;
            return true;
        }
    
        this.MatchedCommandId = 0;
        return false;
    }
    

Добавление команды

Конструктор DynamicMenu позволяет настроить команды меню, включая динамические меню и элементы меню.

  1. В DynamicMenuPackage.cs добавьте GUID набора команд и идентификатор команды:

    public const string guidDynamicMenuPackageCmdSet = "00000000-0000-0000-0000-00000000";  // get the GUID from the .vsct file
    public const uint cmdidMyCommand = 0x104;
    
  2. В файле DynamicMenu.cs добавьте следующие директивы using:

    using EnvDTE;
    using EnvDTE80;
    using System.ComponentModel.Design;
    
  3. В классе добавьте частное DynamicMenu поле dte2.

    private DTE2 dte2;
    
  4. Добавьте поле private rootItemId:

    private int rootItemId = 0;
    
  5. В конструкторе DynamicMenu добавьте команду меню. В следующем разделе мы определим обработчик команд, BeforeQueryStatus обработчик событий и предикат соответствия.

    private DynamicMenu(Package package)
    {
        if (package == null)
        {
            throw new ArgumentNullException(nameof(package));
        }
    
        this.package = package;
    
        OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
        if (commandService != null)
        {
            // Add the DynamicItemMenuCommand for the expansion of the root item into N items at run time.
            CommandID dynamicItemRootId = new CommandID(
                new Guid(DynamicMenuPackageGuids.guidDynamicMenuPackageCmdSet),
                (int)DynamicMenuPackageGuids.cmdidMyCommand);
            DynamicItemMenuCommand dynamicMenuCommand = new DynamicItemMenuCommand(
                dynamicItemRootId,
                IsValidDynamicItem,
                OnInvokedDynamicItem,
                OnBeforeQueryStatusDynamicItem);
                commandService.AddCommand(dynamicMenuCommand);
        }
    
        this.dte2 = (DTE2)this.ServiceProvider.GetService(typeof(DTE));
    }
    

Реализация обработчиков

Чтобы реализовать динамические элементы меню в контроллере меню, необходимо выполнить команду при щелчке динамического элемента. Кроме того, необходимо реализовать логику, которая задает состояние элемента меню. Добавьте обработчики в DynamicMenu класс.

  1. Чтобы реализовать команду Set Startup Project , добавьте обработчик событий OnInvokedDynamicItem . Он ищет проект, имя которого совпадает с текстом вызываемой команды, и задает его в качестве запускаемого проекта, задав абсолютный путь в свойстве StartupProjects .

    private void OnInvokedDynamicItem(object sender, EventArgs args)
    {
        DynamicItemMenuCommand invokedCommand = (DynamicItemMenuCommand)sender;
        // If the command is already checked, we don't need to do anything
        if (invokedCommand.Checked)
            return;
    
        // Find the project that corresponds to the command text and set it as the startup project
        var projects = dte2.Solution.Projects;
        foreach (Project proj in projects)
        {
            if (invokedCommand.Text.Equals(proj.Name))
            {
                dte2.Solution.SolutionBuild.StartupProjects = proj.FullName;
                return;
            }
        }
    }
    
  2. OnBeforeQueryStatusDynamicItem Добавьте обработчик событий. Это обработчик, который вызывается перед событием QueryStatus . Он определяет, является ли элемент меню "реальным" элементом, т. е. не элементом заполнителя, а элементом уже проверка (то есть проект уже задан в качестве запускаемого проекта).

    private void OnBeforeQueryStatusDynamicItem(object sender, EventArgs args)
    {
        DynamicItemMenuCommand matchedCommand = (DynamicItemMenuCommand)sender;
        matchedCommand.Enabled = true;
        matchedCommand.Visible = true;
    
        // Find out whether the command ID is 0, which is the ID of the root item.
        // If it is the root item, it matches the constructed DynamicItemMenuCommand,
         // and IsValidDynamicItem won't be called.
        bool isRootItem = (matchedCommand.MatchedCommandId == 0);
    
        // The index is set to 1 rather than 0 because the Solution.Projects collection is 1-based.
        int indexForDisplay = (isRootItem ? 1 : (matchedCommand.MatchedCommandId - (int) DynamicMenuPackageGuids.cmdidMyCommand) + 1);
    
        matchedCommand.Text = dte2.Solution.Projects.Item(indexForDisplay).Name;
    
        Array startupProjects = (Array)dte2.Solution.SolutionBuild.StartupProjects;
        string startupProject = System.IO.Path.GetFileNameWithoutExtension((string)startupProjects.GetValue(0));
    
        // Check the command if it isn't checked already selected
        matchedCommand.Checked = (matchedCommand.Text == startupProject);
    
        // Clear the ID because we are done with this item.
        matchedCommand.MatchedCommandId = 0;
    }
    

Реализация предиката сопоставления идентификатора команды

Теперь реализуйте предикат соответствия. Необходимо определить две вещи: во-первых, является ли идентификатор команды допустимым (он больше или равен объявленному идентификатору команды), а во-вторых, указывает ли он возможный проект (меньше количества проектов в решении).

private bool IsValidDynamicItem(int commandId)
{
    // The match is valid if the command ID is >= the id of our root dynamic start item
    // and the command ID minus the ID of our root dynamic start item
    // is less than or equal to the number of projects in the solution.
    return (commandId >= (int)DynamicMenuPackageGuids.cmdidMyCommand) && ((commandId - (int)DynamicMenuPackageGuids.cmdidMyCommand) < dte2.Solution.Projects.Count);
}

Установка VSPackage для загрузки только в том случае, если решение имеет несколько проектов

Так как команда Set Startup Project не имеет смысла, если активное решение не имеет нескольких проектов, вы можете настроить пакет VSPackage для автоматической загрузки только в этом случае. Вы используете ProvideAutoLoadAttribute вместе с контекстом SolutionHasMultipleProjectsпользовательского интерфейса. В файле DynamicMenuPackage.cs добавьте следующие атрибуты в класс DynamicMenuPackage:

[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
[ProvideMenuResource("Menus.ctmenu", 1)]
[ProvideAutoLoad(UIContextGuids.SolutionHasMultipleProjects)]
[Guid(DynamicMenuPackage.PackageGuidString)]
public sealed class DynamicMenuItemsPackage : Package
{}

Проверка команды запуска проекта набора

Теперь можно протестировать код.

  1. Выполните сборку решения и запустите отладку. Должен появиться экспериментальный экземпляр.

  2. В экспериментальном экземпляре откройте решение с несколькими проектами.

    На панели инструментов Обозреватель решений появится значок со стрелкой. При его развертывании элементы меню, представляющие различные проекты в решении, должны отображаться.

  3. При проверка одного из проектов он становится запускаемым проектом.

  4. При закрытии решения или открытии решения, имеющего только один проект, значок панели инструментов должен исчезнуть.