【WPF】右クリックメニュー(ContextMenu)の表示制御と選択処理

# はじめに
前回は、ボタンにイメージを追加したカスタムコントロールの作成を行なった。

https://qiita.com/yaju/items/7d50b7c3ae4c4e5cc734

今回は、WineskinServerで動作するランチャー作成として参考にしている「CLaunch」のように右クリックメニュー(ContextMenu)を付けたい。

# メニュー内容
ランチャー用なので起動するプログラムとアイコンを登録する必要がある。
今回は右クリックメニューとして下記の３つを用意して、有効／無効で表示制御する程度にしておく。

* 登録
* 削除
* プロパティ

まだ未登録の場合、「登録」のみ有効表示とし、登録済みの場合、「登録」は無効表示にして「削除」と「プロパティ」は有効表示にする。

## 実装
今回は、XAMLは使用しないでコーディングで書いてみました。

```cs
ContextMenu contextMenu = new ContextMenu();
MenuItem menuItem1 = new MenuItem();
MenuItem menuItem2 = new MenuItem();
MenuItem menuItem3 = new MenuItem();
menuItem1.Header = "登録";
menuItem1.Name = "Register";
menuItem1.Click += new RoutedEventHandler(MenuItem_Click);
menuItem2.Header = "削除";
menuItem2.Name = "Remove";
menuItem2.Click += new RoutedEventHandler(MenuItem_Click);
menuItem3.Header = "プロパティ";
menuItem3.Name = "Property";
menuItem3.Click += new RoutedEventHandler(MenuItem_Click);
contextMenu.Items.Add(menuItem1);
contextMenu.Items.Add(menuItem2);
contextMenu.Items.Add(menuItem3);
```

## XAMLによる実装
【2022/07/19追記】
XAMLを使用した場合の実装も書いてみました。
コーディングによる実装より、XAMLにした方がスマートですね。

```xml
<Window.Resources>
    <ContextMenu x:Key="PopupMenu">
        <MenuItem Header="登録" Name="Register" Click="MenuItem_Click"/>
        <MenuItem Header="削除" Name="Remove" Click="MenuItem_Click"/>
        <MenuItem Header="プロパティ" Name="Property" Click="MenuItem_Click"/>
    </ContextMenu>
</Window.Resources>
```

`FindResource`メソッドを使うことで、XAMLに定義したリソースにアクセスできます。

```cs
MyControl.ContextMenu = (ContextMenu)FindResource("PopupMenu"); 
```

# ボタンに右クリックメニューの追加
今回は、先頭だけサクラエディタ用のアイコンを登録しました。
最終的には設定ファイルで制御するのですが、それはまだ次回とします。

## 実装
ボタンコントロールの`ContextMenu`プロパティにを`contextMenu`インスタンスをセットする。

```cs
gridMain.GridRowCount = 2;
gridMain.GridColumnCount = 4;
gridMain.ShowGridLines = true;

int count = 1;
for (int i = 0; i < 2; i++)
{
    for (int j = 0; j < 4; j++)
    {
        ImageButton MyControl = new ImageButton();
        MyControl.Name = "Button" + count.ToString();
        if(count == 1)
        {
            MyControl.ImageSource = new BitmapImage(new Uri("/Images/my_appicon.ico", UriKind.Relative));
            MyControl.Content = "サクラエディタ";
            MyControl.ImageHeight = 48;
            MyControl.ImageWidth = 48;
        }
        MyControl.Click += new RoutedEventHandler(Button_Click);
        MyControl.ContextMenuOpening += ContextMenu_Click;
        MyControl.ContextMenu = contextMenu;
        Grid.SetColumn(MyControl, j);
        Grid.SetRow(MyControl, i);
        gridMain.Children.Add(MyControl);
        count++;
    }
}
```

![スクリーンショット 2022-07-18 1.09.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/1dbcc475-ce63-c6e9-4d06-00026e3b4bfa.png)

# メニューの表示前制御
開かれる前に編集したい場合、`ContextMenuOpening`イベントを利用する。
未登録の場合は「登録」のみ有効表示とし、登録済みの場合は「登録」は無効表示にして「削除」と「プロパティ」は有効表示にする。

参照:[【WPF】ContextMenuに表示される一部項目を、場合に応じて選択できなくする](https://kanchi0914.hatenablog.com/entry/2019/04/19/094850)

## 実装
ボタンコントロールに`ContextMenuOpening`イベントを割り当てる。

```cs
MyControl.ContextMenuOpening += ContextMenu_Click;
```

```cs
private void ContextMenu_Click(object sender, ContextMenuEventArgs e)
{
    if (e.Source is Button)
    {
        Button b = (Button)e.Source;
        ContextMenu menu = b.ContextMenu;

        foreach(MenuItem item in menu.Items)
        {
            switch(item.Name)
            {
                case "Register":
                    item.IsEnabled = (b.Content == null);
                    break;
                default:
                    item.IsEnabled = (b.Content != null);
                    break;
            }
        }
    }
}
```

![スクリーンショット 2022-07-18 1.15.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/63b46715-bcc1-3eb9-de24-3708e84840e9.png) ![スクリーンショット 2022-07-18 1.12.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/6a8a5977-facd-0696-ee13-472106fff50b.png)

# メニューのクリック処理
ランチャーには複数のボタンがありますが、`ContextMenu`は1種類を使い回しています。
メニュー項目がクリックされた場合に コンテキストメニューが開かれた元のコントロールを知る必要があります。

コンテキストメニューが開かれた元のコントロールの取得方法を示す。
参照：[コンテキストメニューが開かれたコントロールを取得する](http://main.tinyjoker.net/Tech/CSharp/WPF/%A5%B3%A5%F3%A5%C6%A5%AD%A5%B9%A5%C8%A5%E1%A5%CB%A5%E5%A1%BC%A4%AC%B3%AB%A4%AB%A4%EC%A4%BF%A5%B3%A5%F3%A5%C8%A5%ED%A1%BC%A5%EB%A4%F2%BC%E8%C6%C0%A4%B9%A4%EB.html)

`ContextMenu`には`PlacementTarget`プロパティがある。これは`ContextMenu`が表示される際の表示位置の基準となるオブジェクトを示すプロパティである。
`ContextMenu`を開く際にこのプロパティを意図的に設定しなければ、右クリックしたコントロールが格納される仕組みとなっている。この`ContextMenu.PlacementTarget`プロパティが開かれた元のコントロールということになる。

## 実装
今回は、ボタン名＋メニュー名をメッセージダイアログで表示する。
実際には、メニュー内容の処理を記述する。

```cs
menuItem1.Click += new RoutedEventHandler(MenuItem_Click);
menuItem2.Click += new RoutedEventHandler(MenuItem_Click);
menuItem3.Click += new RoutedEventHandler(MenuItem_Click);
```

メニューのイベントを１つにまとめて処理しています。

```cs
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
    MenuItem m = (MenuItem)sender;

    var target = ((e.Source as MenuItem).Parent as ContextMenu).PlacementTarget;
    if (target is Button)
    {
        Button b = (Button)target;
        MessageBox.Show(b.Name + m.Header.ToString());
    }
}
```

2番目のボタンで右クリックメニューで「登録」をクリックした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/c54ebca1-b46b-d1c1-4524-18be838ceb38.png)

# 最後に
次はメニュー内容の実装処理として、登録画面やプロパティ画面などを作成して行きます。