Visual Studio の拡張機能でコンテキストメニュー上にコマンドを表示する方法

## はじめに

とあるツールを作成しているときに、Visual Studio のコンテキストメニュー上に表示する方法について調べたので、その方法についてメモを残しておきます。具体的には、以下のコンテキストメニュー上にカスタムコマンドを表示する方法について記載します。

- ソリューションのコンテキストメニュー
- プロジェクトのコンテキストメニュー
- ソリューション・エクスプローラー上のファイルのコンテキストメニュー
- コード・ウィンドウのコンテキストメニュー

## サンプルコード

サンプルコードは GitHub に公開しています。

- [VsExtensibility-ContextMenu](https://github.com/tafuji/VsExtensibility-ContextMenu)

## コンテキストメニューの表示方法

### Visual Studio Command Table ファイル

Visual Studio のコマンドのアイコン、コマンド表示位置等は、 Visual Studio Command Table (Vsct) ファイルで定義されます。

- [Visual Studio Command Table (.Vsct) Files](https://docs.microsoft.com/en-US/visualstudio/extensibility/internals/visual-studio-command-table-dot-vsct-files)

この Vsct ファイルを編集することで、カスタムコマンドをコンテキストメニュー上に表示させることができます。以下に示すのは、Visual Studio の新規項目の追加で、カスタムコマンドを追加したときの Vsct ファイルの一部です。

```xml
<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```要素をうまく活用すると、一つのコマンドに対して、二つのコマンドの表示位置が定義できるようになります。

```xml
<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 値がまとめられたものがあります。

- [GUIDs and IDs of Visual Studio Menus](https://docs.microsoft.com/en-US/visualstudio/extensibility/internals/guids-and-ids-of-visual-studio-menus)

このドキュメントでは、 GUID 値は、```guidSHLMainMenu``` と記載されています。ドキュメントに直接記載はないのですが、[ここ](https://docs.microsoft.com/en-US/visualstudio/extensibility/internals/ide-defined-commands-menus-and-groups)に記載のある 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``` を利用すればよいことがわかります。なお、この値を調べるに時に、以下のサイトも参考にしました。

- [Common Context Menu Constants](https://blogs.msdn.microsoft.com/martintracy/2006/05/16/common-context-menu-constants/)

### ツールを利用して GUID 値、id 値を調べる方法

ドキュメントに記載がないメニュー（例えば、動的に生成されるメニュー等）が持つ GUID 値、コマンドの ID を調べるには、[Extensibility Tools](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityTools) というツールを利用することをお勧めします。このツールを利用すると、regedit 等で[レジストリキーを編集する手間](https://visualstudioextensions.vlasovstudio.com/2017/06/29/changing-visual-studio-2017-private-registry-settings/)を省くことがでるからです。このツールをインストールした後、[View] メニューの [Enable VSIP Logging] を選択すると、Visual Studio を再起動するかどうかのダイアログが表示されます。

![EnableVSIPLogging](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/01-EnableVSIPLogging.png)

Visual studio の再起動後、GUID 値、コマンドの ID を調べたいメニューを [shift] キー、[ctrl] キーを同時に押して選択すると、ダイアログ上にメニューのデータ（GUID 値、コマンドの IDなど）が表示されるようになります。

![MenuData](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/02-GetMenuData_min.gif)

このデータは、[Ctrl + c] でコピーして、メモ帳などに張り付けることができます。

![NotePad](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/03-CopyMenuData.png)

これ以降は、各コンテキストメニュー上にコマンドを表示する方法について記載します。

## ソリューション

ソリューションのコンテキストメニューにコマンドを表示する方法を記載します。

![SolutionContextMenu](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/04-SolutionContextMenu.png)

### Vsct ファイル

GUID 値に ```guidSHLMainMenu``` 、id 値に ```IDM_VS_CTXT_SOLNNODE``` を設定します。

```xml
<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``` メソッド内で取得します。

```csharp
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;
}
```
以下は、コマンド実行時にソリューション名を表示するサンプルです。

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

## プロジェクト

### プロジェクトのコンテキストメニューにコマンドを表示する

プロジェクトのコンテキストメニューにカスタムコマンドを表示する方法を記載します。

![ProjectContextMenu](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/05-ProjectContextMenu.png)

#### Vsct ファイル

GUID 値に ```guidSHLMainMenu``` 、id 値に ```IDM_VS_CTXT_PROJNODE``` を設定します。

```xml
<CommandPlacements>
  <!-- Project Node -->
  <CommandPlacement guid="guidProjectContextMenuCommandPackageCmdSet" id="MyMenuGroup" priority="0xFFFF">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE" />
  </CommandPlacement>
</CommandPlacements>
```

#### コマンドの処理: 選択したプロジェクト名を表示する

選択したプロジェクトの取得には、```EnvDTE.DTE``` インタフェースの ```ActiveSolutionProjects``` プロパティを利用します。以下にサンプルを示します。

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

### 複数のプロジェクト選択時のコンテキストメニューにコマンドを表示する

複数のプロジェクトが選択されたときのコンテキストメニュー上にカスタムコマンドを表示する方法を記載します。

![MultiProject](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/06-MultiProjectContextMenu.png)

#### Vsct ファイル

GUID 値に ```guidSHLMainMenu``` 、id 値に ```IDM_VS_CTXT_XPROJ_MULTIPROJ``` を設定します。

```xml
<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``` プロパティから取得することができます。以下にサンプルを示します。

```csharp
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 アプリケーションプロジェクト）のコンテキストメニュー上にコマンドを表示する方法について記載します。

![PreviousWebProject](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/07-PreviousVersionWebProjectContextMenu.png)

#### Vsct ファイル

GUID 値に ```guidSHLMainMenu``` 、id 値に ```IDM_VS_CTXT_XPROJ_MULTIPROJ``` を設定します。

```xml
<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``` プロパティを取得することがポイントになります。

```csharp
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 プロジェクトのコンテキストメニュー上にコマンドを表示する方法について記載します。

![AspNetCoreWebProject](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/08-WebProjectContextMenu.png)

ASP.NET Core  の Web プロジェクトのコンテキストメニューの GUID 値、id 値は、一般的なクラスライブラリのコンテキストメニューの GUID 値、id 値と同じです。

- ASP.NET Core プロジェクトの場合

![AspNetCoreGuid](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/09-AspNetCoreProjectMenuData.png)

- クラスライブラリプロジェクトの場合

![ClassLibraryGuid](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/10-ClassLibraryProjectMenuData.png)

```Parent``` 要素の ```guid``` 属性と ```id``` 属性の値を指定するだけでは、クラスライブラリプロジェクトのコンテキストメニュー上にもコマンドが表示されてしまいます。従って、選択されたプロジェクトの種類を判定して、コマンドの表示・非表示を切り替える必要があります。具体的には、方法で表示・非表示を切り替えます。

- Vsct ファイルでコマンドの表示が動的であることを宣言する
- コマンドの処理で、プロジェクトの種類を判定し、コマンドの表示・非表示の設定を行う

#### Vsct ファイル

コマンドの表示が動的であることを宣言するために、以下の設定を行います。

- コマンドの ```Button``` 要素の子要素に ```CommandFlag``` 要素を利用して、以下の設定を行う
    - 要素の値に ```DynamicVisibility``` を設定し、動的に表示を切り替える設定を定義する
    - デフォルトでコマンドを非表示にするため、```DefaultInvisible``` に値を設定する

以下にサンプルを記載します。

```xml
<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``` クラスが利用されていが、これを利用しない。
- ```OleMenuCommand``` クラスの ```BeforeQueryStatus``` イベントハンドラを実装する
    - イベントハンドラ内で、プロジェクトの種別を判定し、表示・非表示の設定処理を行う

以下にサンプルを記載します。

```csharp
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``` 属性を付与しておきます。

```csharp
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string)]
// 省略
public sealed class VsPackage : AsyncPackage
{
  // 省略
}
```

### プロジェクトの種類の GUID 値を調べる方法

カスタムコマンドを見えるように設定する判定処理では、```Project.Kind``` の値を ASP.NET Core のプロジェクトの種類を表す GUID 値と比較しています。この GUID 値を調べる時にも、[Extensibility Tools](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityTools) が利用できます。プロジェクトを右クリックして、[Show Project Information] を実行します。

![ShowProjectInformation](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/11-ShowProjectInformation.png)

すると、テキストファイルにプロジェクトに関する情報が出力されます。テキストファイルの Kind の値がプロジェクトの種類を表す GUID 値になります。

![ProjectKind](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/afbf926f5c1624ba2db3f9f17f92dee908b7160b/12-ProjectGuidValue.png)

## ファイル

ソリューションエクスプローラー上でファイル（通常の ```*.cs``` と ```*.cshtml``` ファイル）が選択されたときのコンテキストメニュー上にカスタムコマンドを表示する方法を記載します。

![FileContextMenu](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/13-CsCodeItemContextMenu.png)

### Vsct ファイル

GUID 値に ```guidSHLMainMenu``` 、id 値に ```IDM_VS_CTXT_ITEMNODE``` を設定します。ASP.NET Core の ```cshtml``` ファイルのコンテキストメニューは、前述の GUID 値と id 値ではありませんでした。 [Extensibility Tools](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityTools) を利用して、コンテキストメニューの GUID 値と id 値を取得し、```GuidSymbol``` 要素に設定を加えています。

```xml
<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``` から始まるようです）

```csharp
EnvDTE.SelectedItem item = _dte.SelectedItems.Item(1);
string message = $"Selected File is {item.ProjectItem.Name}";
string title = "File Context Menu Command";
```

## コード・ウィンドウ

コード・ウィンドウのコンテキストメニュー上にコマンドを表示する方法を記載します。通常の ```*.cs``` と ```*.cshtml``` ファイルのコードウィンドウ上でコンテキストメニューを表示する方法を記載します。

![CSFileCodeWindowMenu](https://gist.githubusercontent.com/tafuji/47bce57b03d7d8123cb397c36e7b9b6f/raw/cac616bc730cfb1ec68fbda46ceff103360bce4f/14-CsCodeWindowContextMenu.png)

### Vsct ファイル

GUID 値に ```guidSHLMainMenu``` 、id 値に ```IDM_VS_CTXT_CODEWIN``` を設定します。ASP.NET Core の ```cshtml``` ファイルのコンテキストメニューは、前述の GUID 値と id 値ではありませんでした。 [Extensibility Tools](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityTools) を利用して、コンテキストメニューの GUID 値と id 値を取得し、```GuidSymbol``` 要素に設定を加えています。

```xml
<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``` プロパティを利用します。以下にサンプルを記載します。

```csharp
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](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityTools) を利用してみよう
- 動的にコンテキストメニューに表示するかどうかを判断する場合
    - ```CommandFlag``` 要素を利用する
    - ```OleMenuCommand``` クラスの ```BeforeQueryStatus``` イベントハンドラで表示・非表示の判定を行う
    - VsPackage の読み込みが遅延されないように、```ProvideAutoLoad``` 属性を付与する

## 参考資料

- [Visual Studio Command Table (.Vsct) Files](https://docs.microsoft.com/en-US/visualstudio/extensibility/internals/visual-studio-command-table-dot-vsct-files)
- [Common Context Menu Constants](https://blogs.msdn.microsoft.com/martintracy/2006/05/16/common-context-menu-constants/)
- [Extensibility Tools](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityTools)