Condividi tramite


Aggiungere dinamicamente voci di menu

È possibile aggiungere voci di menu in fase di esecuzione specificando il DynamicItemStart flag di comando in una definizione del pulsante segnaposto nel file della tabella dei comandi (con estensione vsct) di Visual Studio, quindi definendo (nel codice) il numero di voci di menu da visualizzare e gestire i comandi. Quando il pacchetto VSPackage viene caricato, il segnaposto viene sostituito con le voci di menu dinamiche.

Visual Studio usa elenchi dinamici nell'elenco MRU (Most Recently Used ), che visualizza i nomi dei documenti aperti di recente e l'elenco di Windows , che visualizza i nomi delle finestre attualmente aperte. Il DynamicItemStart flag in una definizione di comando specifica che il comando è un segnaposto fino all'apertura del VSPackage. Quando il pacchetto VSPackage viene aperto, il segnaposto viene sostituito da 0 o più comandi creati in fase di esecuzione e aggiunti all'elenco dinamico. Potrebbe non essere possibile visualizzare la posizione nel menu in cui viene visualizzato l'elenco dinamico fino all'apertura del pacchetto VSPackage. Per popolare l'elenco dinamico, Visual Studio chiede al VSPackage di cercare un comando con un ID i cui primi caratteri sono uguali all'ID del segnaposto. Quando Visual Studio trova un comando corrispondente, aggiunge il nome del comando all'elenco dinamico. Incrementa quindi l'ID e cerca un altro comando corrispondente da aggiungere all'elenco dinamico fino a quando non sono presenti altri comandi dinamici.

Questa procedura dettagliata illustra come impostare il progetto di avvio in una soluzione di Visual Studio con un comando sulla barra degli strumenti Esplora soluzioni. Usa un controller di menu con un elenco a discesa dinamico dei progetti nella soluzione attiva. Per evitare che questo comando venga visualizzato quando non è aperta alcuna soluzione o quando la soluzione aperta ha un solo progetto, il pacchetto VSPackage viene caricato solo quando una soluzione ha più progetti.

Per altre informazioni sui file con estensione vsct, vedere File di tabella dei comandi di Visual Studio (con estensione vsct).

Creare un'estensione con un comando di menu

  1. Creare un progetto VSIX denominato DynamicMenuItems.

  2. Quando si apre il progetto, aggiungere un modello di elemento di comando personalizzato e denominarlo DynamicMenu. Per altre informazioni, vedere Creare un'estensione con un comando di menu.

Configurazione degli elementi nel file con estensione vsct

Per creare un controller di menu con voci di menu dinamiche su una barra degli strumenti, specificare gli elementi seguenti:

  • Due gruppi di comandi, uno che contiene il controller di menu e un altro che contiene le voci di menu nell'elenco a discesa

  • Un elemento di menu di tipo MenuController

  • Due pulsanti, uno che funge da segnaposto per le voci di menu e un altro che fornisce l'icona e la descrizione comando sulla barra degli strumenti.

  1. In DynamicMenuPackage.vsct definire gli ID comando. Passare alla sezione Symbols e sostituire gli elementi IDSymbol nel blocco guidDynamicMenuPackageCmdSet GuidSymbol. È necessario definire gli elementi IDSymbol per i due gruppi, il controller di menu, il comando segnaposto e il comando di ancoraggio.

    <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. Nella sezione Gruppi eliminare i gruppi esistenti e aggiungere i due gruppi appena definiti:

    <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>
    

    Aggiungere MenuController. Impostare il flag di comando DynamicVisibility, perché non è sempre visibile. ButtonText non viene visualizzato.

    <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. Aggiungere due pulsanti, uno come segnaposto per le voci di menu dinamiche e uno come ancoraggio per MenuController.

    L'elemento padre del pulsante segnaposto è MyMenuControllerGroup. Aggiungere i flag di comando DynamicItemStart, DynamicVisibility e TextChanges al pulsante segnaposto. ButtonText non viene visualizzato.

    Il pulsante di ancoraggio contiene l'icona e il testo della descrizione comando. Anche l'elemento padre del pulsante di ancoraggio è MyMenuControllerGroup. Aggiungi il flag di comando NoShowOnMenuController per assicurarti che il pulsante non venga effettivamente visualizzato nell'elenco a discesa del controller di menu e il flag di comando FixMenuController per renderlo l'ancoraggio permanente.

    <!-- 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. Aggiungere un'icona al progetto (nella cartella Risorse ) e quindi aggiungervi il riferimento nel file vsct . In questa procedura dettagliata viene usata l'icona Frecce inclusa nel modello di progetto.

  5. Aggiungere una sezione VisibilityConstraints all'esterno della sezione Comandi subito prima della sezione Simboli. È possibile che venga visualizzato un avviso se lo si aggiunge dopo i simboli. Questa sezione assicura che il controller di menu venga visualizzato solo quando viene caricata una soluzione con più progetti.

    <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>
    

Implementare il comando di menu dinamico

Si crea una classe di comando di menu dinamica che eredita da OleMenuCommand. In questa implementazione il costruttore specifica un predicato da usare per i comandi corrispondenti. È necessario eseguire l'override del DynamicItemMatch metodo per utilizzare questo predicato per impostare la MatchedCommandId proprietà , che identifica il comando da richiamare.

  1. Creare un nuovo file di classe C# denominato DynamicItemMenuCommand.cs e aggiungere una classe denominata DynamicItemMenuCommand che eredita da OleMenuCommand:

    class DynamicItemMenuCommand : OleMenuCommand
    {
    
    }
    
    
  2. Aggiungere le direttive using seguenti:

    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.Shell.Interop;
    using System.ComponentModel.Design;
    
  3. Aggiungere un campo privato per archiviare il predicato di corrispondenza:

    private Predicate<int> matches;
    
    
  4. Aggiungere un costruttore che eredita dal OleMenuCommand costruttore e specifica un gestore comandi e un BeforeQueryStatus gestore. Aggiungere un predicato per la corrispondenza del comando:

    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. Eseguire l'override del DynamicItemMatch metodo in modo che chiami il predicato corrispondente e imposti la MatchedCommandId proprietà :

    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;
    }
    

Aggiungere il comando

Il costruttore DynamicMenu consente di configurare i comandi di menu, inclusi menu dinamici e voci di menu.

  1. In DynamicMenuPackage.cs aggiungere il GUID del set di comandi e l'ID comando:

    public const string guidDynamicMenuPackageCmdSet = "00000000-0000-0000-0000-00000000";  // get the GUID from the .vsct file
    public const uint cmdidMyCommand = 0x104;
    
  2. Nel file DynamicMenu.cs aggiungere le direttive using seguenti:

    using EnvDTE;
    using EnvDTE80;
    using System.ComponentModel.Design;
    
  3. DynamicMenu Nella classe aggiungere un campo privato dte2.

    private DTE2 dte2;
    
  4. Aggiungere un campo rootItemId privato:

    private int rootItemId = 0;
    
  5. Nel costruttore DynamicMenu aggiungere il comando di menu. Nella sezione successiva si definirà il gestore dei comandi, il BeforeQueryStatus gestore eventi e il predicato di corrispondenza.

    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));
    }
    

Implementare i gestori

Per implementare voci di menu dinamiche in un controller di menu, è necessario gestire il comando quando si fa clic su un elemento dinamico. È inoltre necessario implementare la logica che imposta lo stato della voce di menu. Aggiungere i gestori alla DynamicMenu classe .

  1. Per implementare il comando Imposta progetto di avvio, aggiungere il gestore eventi OnInvokedDynamicItem . Cerca il progetto il cui nome corrisponde al testo del comando richiamato e lo imposta come progetto di avvio impostando il StartupProjects percorso assoluto nella proprietà .

    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. Aggiungere il OnBeforeQueryStatusDynamicItem gestore eventi. Questo è il gestore chiamato prima di un QueryStatus evento. Determina se la voce di menu è una voce "reale", ovvero non l'elemento segnaposto e se l'elemento è già selezionato (ovvero che il progetto è già impostato come progetto di avvio).

    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;
    }
    

Implementare il predicato di corrispondenza dell'ID comando

Implementare ora il predicato di corrispondenza. È necessario determinare due elementi: innanzitutto, se l'ID comando è valido (è maggiore o uguale all'ID comando dichiarato) e secondo, se specifica un progetto possibile (è minore del numero di progetti nella soluzione).

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);
}

Impostare VSPackage per il caricamento solo quando una soluzione ha più progetti

Poiché il comando Imposta progetto di avvio non ha senso a meno che la soluzione attiva non abbia più di un progetto, è possibile impostare il pacchetto VSPackage su caricamento automatico solo in questo caso. Si usa ProvideAutoLoadAttribute insieme al contesto SolutionHasMultipleProjectsdell'interfaccia utente . Nel file DynamicMenuPackage.cs aggiungere gli attributi seguenti alla classe 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
{}

Testare il comando set startup project

È ora possibile testare il codice.

  1. Compilare il progetto e avviare il debug. Verrà visualizzata l'istanza sperimentale.

  2. Nell'istanza sperimentale aprire una soluzione con più progetti.

    Verrà visualizzata l'icona a forma di freccia sulla barra degli strumenti Esplora soluzioni. Quando lo si espande, verranno visualizzate voci di menu che rappresentano i diversi progetti nella soluzione.

  3. Quando si controlla uno dei progetti, diventa il progetto di avvio.

  4. Quando si chiude la soluzione o si apre una soluzione con un solo progetto, l'icona della barra degli strumenti dovrebbe scomparire.