共用方式為


以動態方式新增功能表項目

您可以在執行階段新增功能表項目,方法是在 Visual Studio 命令資料表 (.vsct) 檔案的預留位置按鈕定義上指定 DynamicItemStart 命令旗標,然後定義功能表項目的數目,以顯示及處理命令。 載入 VSPackage 時,預留位置會取代為動態功能表項目。

Visual Studio 會在 [最近使用] (MRU) 清單中使用動態清單,其中顯示最近開啟的檔名稱,以及顯示目前開啟之視窗名稱的 [視窗] 清單。 命令定義上的 DynamicItemStart 旗標會指定命令是預留位置,直到開啟 VSPackage 為止。 開啟 VSPackage 時,預留位置會取代為執行階段建立的 0 或多個命令,並新增至動態清單。 在開啟 VSPackage 之前,您可能無法在動態清單出現所在的功能表上看到位置。 若要填入動態清單,Visual Studio 會要求 VSPackage 尋找識別碼與預留位置識別碼相同的命令。 當 Visual Studio 找到相符的命令時,它會將命令的名稱新增至動態清單。 然後,它會遞增識別碼,並尋找另一個相符的命令來新增至動態清單,直到沒有其他動態命令為止。

本逐步解說示範如何使用解決方案總管工具列上的命令,在 Visual Studio 解決方案中設定啟動專案。 它會使用具有使用中解決方案中項目動態下拉式清單的功能表控制器。 若要讓此命令在未開啟解決方案或開啟的解決方案只有一個項目時顯示,VSPackage 只有在解決方案有多個專案時才會載入。

如需 .vsct 檔案的詳細資訊,請參閱 Visual Studio 命令資料表 (.vsct) 檔案

使用功能表命令建立擴充功能

  1. 建立名為 DynamicMenuItems 的 VSIX 應用程式專案。

  2. 當專案開啟時,新增自訂命令項目範本並將其命名為 DynamicMenu。 如需詳細資訊,請參閱使用功能表命令建立擴充功能

.vsct 檔案中設定元素

若要在工具列上建立具有動態功能表項目的功能表控制器,您可以指定下列元素:

  • 兩個命令群組,一個包含功能表控制器,另一個包含下拉式清單中的功能表項目

  • 類型為 MenuController 的一個功能表元素

  • 兩個按鈕,一個做為功能表項目的預留位置,另一個按鈕提供圖示和工具列上的工具提示。

  1. DynamicMenuPackage.vsct 中,定義命令識別碼。 移至 [符號] 區段,並取代 guidDynamicMenuPackageCmdSet GuidSymbol 區塊中的 IDSymbol 元素。 您必須為兩個群組、功能表控制器、預留位置命令和錨點命令定義 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 的動態功能表命令類別。 在此實作中,建構函式會指定要用於比對命令的述詞。 您必須覆寫 DynamicItemMatch 方法,才能使用此述詞來設定 MatchedCommandId 屬性,以識別要叫用的命令。

  1. 建立名為 DynamicItemMenuCommand.cs 的新 C# 類別檔案,並新增繼承自 OleMenuCommand 名為 DynamicItemMenuCommand 的類別:

    class DynamicItemMenuCommand : OleMenuCommand
    {
    
    }
    
    
  2. 使用指示詞新增以下項目:

    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 EnvDTE;
    using EnvDTE80;
    using System.ComponentModel.Design;
    
  3. DynamicMenu 類別中 ,新增 dte2 私人欄位。

    private DTE2 dte2;
    
  4. 新增私人 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 設定為載入

因為除非使用中解決方案有多個專案,否則 [設定啟動專案] 命令沒有意義,因此您只能在該案例中將 VSPackage 設定為自動載入。 您可以將 ProvideAutoLoadAttributeSolutionHasMultipleProjects UI 內容搭配使用。 在 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. 當您關閉解決方案,或開啟只有一個專案的方案時,工具列圖示應該會消失。