Visual Studio の拡張機能でコンテキストメニュー上にコマンドを表示する方法
はじめに
とあるツールを作成しているときに、Visual Studio のコンテキストメニュー上に表示する方法について調べたので、その方法についてメモを残しておきます。具体的には、以下のコンテキストメニュー上にカスタムコマンドを表示する方法について記載します。
- ソリューションのコンテキストメニュー
- プロジェクトのコンテキストメニュー
- ソリューション・エクスプローラー上のファイルのコンテキストメニュー
- コード・ウィンドウのコンテキストメニュー
サンプルコード
サンプルコードは GitHub に公開しています。
コンテキストメニューの表示方法
Visual Studio Command Table ファイル
Visual Studio のコマンドのアイコン、コマンド表示位置等は、 Visual Studio Command Table (Vsct) ファイルで定義されます。
この Vsct ファイルを編集することで、カスタムコマンドをコンテキストメニュー上に表示させることができます。以下に示すのは、Visual Studio の新規項目の追加で、カスタムコマンドを追加したときの Vsct ファイルの一部です。
<Groups> <Group guid="guidFileContextMenuCommandCmdSet" id="MyMenuGroup" priority="0x0600"> <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/> </Group> </Groups>
この Parent
要素の guid
属性と id
属性の値で、カスタムメニューコマンドの表示位置が決定されます。このまま、Parent
要素の属性を編集してもよいのですが、コマンドの表示位置が一つに限定されてしまうので、CommandPlacements
要素内に、コマンドの表示位置を定義していくことをお勧めします。
以下は、コマンドの表示位置を CommandReplacements
要素内に定義した場合の例です。このように、CommandPlacements
要素をうまく活用すると、一つのコマンドに対して、二つのコマンドの表示位置が定義できるようになります。
<Groups> <Group guid="guidFileContextMenuCommandCmdSet" id="MyMenuGroup" priority="0x0600" /> </Groups> <!-- 中略 --> <CommandPlacements> <!-- File Context Menu --> <CommandPlacement guid="guidFileContextMenuCommandCmdSet" id="MyMenuGroup" priority="0xFFFF"> <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE" /> </CommandPlacement> <!-- Web File Context Menu --> <CommandPlacement guid="guidFileContextMenuCommandCmdSet" id="MyMenuGroup" priority="0xFFFF"> <Parent guid="guidCSHTMLFileContextMenu" id="CSHTMLFileContextMenu" /> </CommandPlacement> </CommandPlacements>
では、guid
属性、 id
属性の値をどのように指定すればよいかですが、以下の二つの方法があります。
- Micorsoft のドキュメントに記載されていないかを調べる
- ツールを利用して GUID 値、id 値を調べる
Micorsoft のドキュメントに記載されていないかを調べる
Microsoft のドキュメントには、Visual Studi のメニューの GUID 値、id 値がまとめられたものがあります。
このドキュメントでは、 GUID 値は、guidSHLMainMenu
と記載されています。ドキュメントに直接記載はないのですが、ここに記載のある Vsct フィアルを検索すると、コンテキストメニューの id 値を調べることができます。主なコンテキストメニューの id 値を以下の表に記載します。
id | 説明 |
---|---|
IDM_VS_CTXT_SOLNNODE | ソリューションのコンテキストメニュー |
IDM_VS_CTXT_PROJNODE | プロジェクトのコンテキストメニュー |
IDM_VS_CTXT_XPROJ_MULTIPROJ | 複数のプロジェクトを選択したときのコンテキストメニュー |
IDM_VS_CTXT_WEBPROJECT | Web プロジェクト(以前のバージョン) |
IDM_VS_CTXT_ITEMNODE | ソリューションエクスローラーで、プロジェクトの項目を選択したときのコンテキストメニュー |
IDM_VS_CTXT_CODEWIN | コード・ウィンドウのコンテキストメニュー |
この表によれば、ソリューションのコンテキストメニュー上にコマンドを表示するには、GUID 値に guidSHLMainMenu
を指定、id 値に IDM_VS_CTXT_SOLNNODE
を利用すればよいことがわかります。なお、この値を調べるに時に、以下のサイトも参考にしました。
ツールを利用して GUID 値、id 値を調べる方法
ドキュメントに記載がないメニュー(例えば、動的に生成されるメニュー等)が持つ GUID 値、コマンドの ID を調べるには、Extensibility Tools というツールを利用することをお勧めします。このツールを利用すると、regedit 等でレジストリキーを編集する手間を省くことがでるからです。このツールをインストールした後、[View] メニューの [Enable VSIP Logging] を選択すると、Visual Studio を再起動するかどうかのダイアログが表示されます。
Visual studio の再起動後、GUID 値、コマンドの ID を調べたいメニューを [shift] キー、[ctrl] キーを同時に押して選択すると、ダイアログ上にメニューのデータ(GUID 値、コマンドの IDなど)が表示されるようになります。
このデータは、[Ctrl + c] でコピーして、メモ帳などに張り付けることができます。
これ以降は、各コンテキストメニュー上にコマンドを表示する方法について記載します。
ソリューション
ソリューションのコンテキストメニューにコマンドを表示する方法を記載します。
Vsct ファイル
GUID 値に guidSHLMainMenu
、id 値に IDM_VS_CTXT_SOLNNODE
を設定します。
<CommandPlacements> <!-- Solution Context Menu --> <CommandPlacement guid="guidSolutionContextMenuCommandPackageCmdSet" id="MyMenuGroup" priority="0xFFFF"> <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_SOLNNODE" /> </CommandPlacement> </CommandPlacements>
コマンドの処理: ソリューション名を表示する
サンプルでは、ソリューション名をダイアログに表示しています。ソリューション名の取得には、EnvDTE.DTE
インタフェースを利用しています。EnvDTE.DTE
オブジェクトの取得処理は、カスタムコマンド作成時に自動生成されたコマンドのクラスの InitializeAsync
メソッド内で取得します。
private EnvDTE.DTE _dte; public static async Task InitializeAsync(AsyncPackage package) { // Verify the current thread is the UI thread - the call to AddCommand in SolutionContextMenuCommand's constructor requires // the UI thread. ThreadHelper.ThrowIfNotOnUIThread(); OleMenuCommandService commandService = await package.GetServiceAsync((typeof(IMenuCommandService))) as OleMenuCommandService; Instance = new SolutionContextMenuCommand(package, commandService); Instance._dte = await package.GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE.DTE; }
以下は、コマンド実行時にソリューション名を表示するサンプルです。
private void Execute(object sender, EventArgs e) { ThreadHelper.ThrowIfNotOnUIThread(); if (_dte == null) return; EnvDTE.Solution solution = _dte.Solution; string message = $"Selected Solution is {solution.FullName}"; string title = "Solution Context Menu Command"; // Show a message box VsShellUtilities.ShowMessageBox( this.package, message, title, OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); }
プロジェクト
プロジェクトのコンテキストメニューにコマンドを表示する
プロジェクトのコンテキストメニューにカスタムコマンドを表示する方法を記載します。
Vsct ファイル
GUID 値に guidSHLMainMenu
、id 値に IDM_VS_CTXT_PROJNODE
を設定します。
<CommandPlacements> <!-- Project Node --> <CommandPlacement guid="guidProjectContextMenuCommandPackageCmdSet" id="MyMenuGroup" priority="0xFFFF"> <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE" /> </CommandPlacement> </CommandPlacements>
コマンドの処理: 選択したプロジェクト名を表示する
選択したプロジェクトの取得には、EnvDTE.DTE
インタフェースの ActiveSolutionProjects
プロパティを利用します。以下にサンプルを示します。
private void Execute(object sender, EventArgs e) { ThreadHelper.ThrowIfNotOnUIThread(); if (_dte == null) return; EnvDTE.Solution solution = _dte.Solution; EnvDTE.Project project = (EnvDTE.Project)((object[])_dte.ActiveSolutionProjects)[0]; string message = $"Selected project is {project.Name}"; string title = "Project Context Menu Command"; // Show a message box VsShellUtilities.ShowMessageBox( this.package, message, title, OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); }
複数のプロジェクト選択時のコンテキストメニューにコマンドを表示する
複数のプロジェクトが選択されたときのコンテキストメニュー上にカスタムコマンドを表示する方法を記載します。
Vsct ファイル
GUID 値に guidSHLMainMenu
、id 値に IDM_VS_CTXT_XPROJ_MULTIPROJ
を設定します。
<CommandPlacements> <!-- Multi Projects Selected --> <CommandPlacement guid="guidMultiProjectsContextMenuCommandPackageCmdSet" id="MyMenuGroup" priority="0xFFFF"> <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_XPROJ_MULTIPROJ" /> </CommandPlacement> </CommandPlacements>
コマンドの処理: 選択したすべてのプロジェクト名を表示する
選択されたプロジェクトは、EnvDTE.DTE
の ActiveSolutionProjects
プロパティから取得することができます。以下にサンプルを示します。
private void Execute(object sender, EventArgs e) { ThreadHelper.ThrowIfNotOnUIThread(); if (_dte == null) return; EnvDTE.Solution solution = _dte.Solution; object[] projects = ((object[])_dte.ActiveSolutionProjects); var builder = new StringBuilder(); foreach (var item in projects) { EnvDTE.Project project = (EnvDTE.Project)item; builder.Append($"Project name: {project.Name}"); builder.Append(Environment.NewLine); } string message = builder.ToString(); string title = "Multi Projects Context Menu Command"; // Show a message box VsShellUtilities.ShowMessageBox( this.package, message, title, OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); }
特定のプロジェクトのコンテキストメニューにコマンドを表示する
ここでは、ASP.NET の Web アプリケーションプロジェクト(Visual Studio 2017 の プロジェクト作成では、以前のバージョンの Web アプリケーションプロジェクト)のコンテキストメニュー上にコマンドを表示する方法について記載します。
Vsct ファイル
GUID 値に guidSHLMainMenu
、id 値に IDM_VS_CTXT_XPROJ_MULTIPROJ
を設定します。
<CommandPlacements> <!-- Web Project (Previous Versions) Context Menu--> <CommandPlacement guid="guidPreviousVersionWebProjectContextMenuCommandCmdSet" id="MyMenuGroup" priority="0xFFFF"> <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_WEBPROJECT" /> </CommandPlacement> </CommandPlacements>
コマンドの処理: 選択されたプロジェクト名を表示する
選択されたプロジェクト名を取得する方法は、"プロジェクトのコンテキストメニューにコマンドを表示する" と同様です。ActiveSolutionProjects
プロパティを取得することがポイントになります。
EnvDTE.Solution solution = _dte.Solution; EnvDTE.Project project = (EnvDTE.Project)((object[])_dte.ActiveSolutionProjects)[0]; string message = $"Selected project is {project.Name}";
選択されたプロジェクトの種類によって動的にコマンドを表示する方法
ここでは、ASP.NET Core の Web プロジェクトのコンテキストメニュー上にコマンドを表示する方法について記載します。
ASP.NET Core の Web プロジェクトのコンテキストメニューの GUID 値、id 値は、一般的なクラスライブラリのコンテキストメニューの GUID 値、id 値と同じです。
- ASP.NET Core プロジェクトの場合
- クラスライブラリプロジェクトの場合
Parent
要素の guid
属性と id
属性の値を指定するだけでは、クラスライブラリプロジェクトのコンテキストメニュー上にもコマンドが表示されてしまいます。従って、選択されたプロジェクトの種類を判定して、コマンドの表示・非表示を切り替える必要があります。具体的には、方法で表示・非表示を切り替えます。
- Vsct ファイルでコマンドの表示が動的であることを宣言する
- コマンドの処理で、プロジェクトの種類を判定し、コマンドの表示・非表示の設定を行う
Vsct ファイル
コマンドの表示が動的であることを宣言するために、以下の設定を行います。
- コマンドの
Button
要素の子要素にCommandFlag
要素を利用して、以下の設定を行う- 要素の値に
DynamicVisibility
を設定し、動的に表示を切り替える設定を定義する - デフォルトでコマンドを非表示にするため、
DefaultInvisible
に値を設定する
- 要素の値に
以下にサンプルを記載します。
<Button guid="guidWebProjectContextMenuCommandPackageCmdSet" id="cmdidWebProjectContextMenuCommand" priority="0x0100" type="Button"> <Parent guid="guidWebProjectContextMenuCommandPackageCmdSet" id="MyMenuGroup" /> <Icon guid="guidImages" id="bmpPic1" /> <!-- Dynamic Visibility --> <CommandFlag>DefaultInvisible</CommandFlag> <CommandFlag>DynamicVisibility</CommandFlag> <Strings> <ButtonText>Invoke Web Project Context Menu Command</ButtonText> </Strings> </Button> <!-- 中略 --> <CommandPlacements> <!-- Web Project Context Menu--> <CommandPlacement guid="guidWebProjectContextMenuCommandPackageCmdSet" id="MyMenuGroup" priority="0xFFFF"> <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE" /> </CommandPlacement> </CommandPlacements>
コマンドの処理
OleMenuCommand
クラスを利用する- Visual Studio により自動生成されたコードでは、
MenuCommand
クラスが利用されていが、これを利用しない。
- Visual Studio により自動生成されたコードでは、
OleMenuCommand
クラスのBeforeQueryStatus
イベントハンドラを実装する- イベントハンドラ内で、プロジェクトの種別を判定し、表示・非表示の設定処理を行う
以下にサンプルを記載します。
private WebProjectContextMenuCommand(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); // Instantiate OleMenuCommand object var menuItem = new OleMenuCommand(this.Execute, menuCommandID); // Add event handler menuItem.BeforeQueryStatus += new EventHandler(BeforeQueryStatus); commandService.AddCommand(menuItem); } private void BeforeQueryStatus(object sender, EventArgs e) { if (_dte == null) return; EnvDTE.Solution solution = _dte.Solution; EnvDTE.Project project = (EnvDTE.Project)((object[])_dte.ActiveSolutionProjects)[0]; var cmd = (OleMenuCommand)sender; // プロジェクトが ASP.NET Core のプロジェクトの場合はコマンドを見えるようにする cmd.Visible = project.Kind == "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"; }
また、拡張機能の読み込みが遅延するのを避けるために、ProvideAutoLoad
属性を付与しておきます。
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string)] // 省略 public sealed class VsPackage : AsyncPackage { // 省略 }
プロジェクトの種類の GUID 値を調べる方法
カスタムコマンドを見えるように設定する判定処理では、Project.Kind
の値を ASP.NET Core のプロジェクトの種類を表す GUID 値と比較しています。この GUID 値を調べる時にも、Extensibility Tools が利用できます。プロジェクトを右クリックして、[Show Project Information] を実行します。
すると、テキストファイルにプロジェクトに関する情報が出力されます。テキストファイルの Kind の値がプロジェクトの種類を表す GUID 値になります。
ファイル
ソリューションエクスプローラー上でファイル(通常の *.cs
と *.cshtml
ファイル)が選択されたときのコンテキストメニュー上にカスタムコマンドを表示する方法を記載します。
Vsct ファイル
GUID 値に guidSHLMainMenu
、id 値に IDM_VS_CTXT_ITEMNODE
を設定します。ASP.NET Core の cshtml
ファイルのコンテキストメニューは、前述の GUID 値と id 値ではありませんでした。 Extensibility Tools を利用して、コンテキストメニューの GUID 値と id 値を取得し、GuidSymbol
要素に設定を加えています。
<CommandPlacements> <!-- File Context Menu --> <CommandPlacement guid="guidFileContextMenuCommandCmdSet" id="MyMenuGroup" priority="0xFFFF"> <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE" /> </CommandPlacement> <!-- Web File Context Menu --> <CommandPlacement guid="guidFileContextMenuCommandCmdSet" id="MyMenuGroup" priority="0xFFFF"> <Parent guid="guidCSHTMLFileContextMenu" id="CSHTMLFileContextMenu" /> </CommandPlacement> </CommandPlacements> <GuidSymbol name="guidCSHTMLFileContextMenu" value="{D309F791-903F-11D0-9EFC-00A0C911004F}"> <IDSymbol value="1138" name="CSHTMLFileContextMenu" /> </GuidSymbol>
コマンドの処理
選択されたファイルを取得するには、EnvDTE.DTE
の SelectedItems
コレクションを取得し、Item
プロパティにインデックスを指定します。(インデックスは、0
ではなく 1
から始まるようです)
EnvDTE.SelectedItem item = _dte.SelectedItems.Item(1); string message = $"Selected File is {item.ProjectItem.Name}"; string title = "File Context Menu Command";
コード・ウィンドウ
コード・ウィンドウのコンテキストメニュー上にコマンドを表示する方法を記載します。通常の *.cs
と *.cshtml
ファイルのコードウィンドウ上でコンテキストメニューを表示する方法を記載します。
Vsct ファイル
GUID 値に guidSHLMainMenu
、id 値に IDM_VS_CTXT_CODEWIN
を設定します。ASP.NET Core の cshtml
ファイルのコンテキストメニューは、前述の GUID 値と id 値ではありませんでした。 Extensibility Tools を利用して、コンテキストメニューの GUID 値と id 値を取得し、GuidSymbol
要素に設定を加えています。
<CommandPlacements> <!-- Web File Code Window Context Menu --> <CommandPlacement guid="guidCodeWindowContextMenuCommandCmdSet" id="MyMenuGroup" priority="0xFFFF"> <Parent guid="guidCSHTMLContextMenu" id="CSHTMLContextMenu" /> </CommandPlacement> <!-- Code Windows Context Menu --> <CommandPlacement guid="guidCodeWindowContextMenuCommandCmdSet" id="MyMenuGroup" priority="0xFFF"> <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN" /> </CommandPlacement> </CommandPlacements> <GuidSymbol name="guidCSHTMLContextMenu" value="{78F03954-2FB8-4087-8CE7-59D71710B3BB}"> <IDSymbol value="1" name="CSHTMLContextMenu" /> </GuidSymbol>
コマンドの処理
コードウィンドウで表示されたコードを取得するには、EnvDTE.DTE
の ActiveDocument
プロパティを利用します。以下にサンプルを記載します。
private void Execute(object sender, EventArgs e) { ThreadHelper.ThrowIfNotOnUIThread(); EnvDTE.Document doc = _dte.ActiveDocument; string message = $"Active window is {doc.FullName}"; string title = "Code Window Context Menu Command"; // Show a message box VsShellUtilities.ShowMessageBox( this.package, message, title, OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); }
まとめ
- Visual Studio のカスタムコマンドの表示位置は、Vsct ファイルで設定できる
- Vsct ファイルの、
CommandPlacement
要素内にコマンドの位置を定義した方がよい Parent
要素のguid
属性、id
属性に適切な値を設定する- コンテキストメニューの GUID 値、id 値が分からない場合は、Extensibility Tools を利用してみよう
- Vsct ファイルの、
- 動的にコンテキストメニューに表示するかどうかを判断する場合
CommandFlag
要素を利用するOleMenuCommand
クラスのBeforeQueryStatus
イベントハンドラで表示・非表示の判定を行う- VsPackage の読み込みが遅延されないように、
ProvideAutoLoad
属性を付与する