自作アプリケーションに色々な機能を付ける場合、アイコンボタンを用意することが多いですが、アイコンボタンは画面に場所を取るのでそんなに多くは配置できません。
その点、コンテキストメニューは右クリックした時のみ表示され、また階層化もできるので、場所も取らず多くの機能を盛り込むことが出来ます。
今回は、WPFで右クリックによるコンテキストメニューの設定方法と、メニューが選択された際のイベントの扱い方について解説したいと思います。
コンテキストメニューとは
コンテキストメニューとは、ウィンドウやコントロールにマウスを当てて右クリックしたときに表示されるメニューのことです。
下記のサンプルはWPFのDataGridに対してコンテキストメニューを付けた場合の動作例です。

コンテキストメニューの使い方
まず、コンテキストメニューの表示方法について解説したいと思います。
最初に概要を図を使って説明し、次に具体的なソースコードを紹介します。
コンテキストメニューの表示方法
まず、コンテキストメニューは何らかの親(コントロール又はWindow)に所属させる必要があります。
その為、少し回りくどい記述になりますが、下記の様に親コントロールのプロパティに対して、ContextMenuクラスをセットし、その中に個々のメニューとなるMenuItemを入れていくことになります。
個々のメニューに表示させたいメニュー名は、MenuItemのHeaderプロパティにセットして下さい。

ラベルやボタンにコンテキストメニューを付けるケースは少ないかもしれませんが、仮にラベルにコンテキストメニューを付けたい場合は次の様になります。
<Label Content="ラベル" Width="100" Height="20">
<Label.ContextMenu>
<ContextMenu>
<MenuItem Header="メニュー名1"/>
<MenuItem Header="メニュー名2"/>
</ContextMenu>
</Label.ContextMenu>
</Label>
メニューの階層化(入れ子)
メニューを階層化したい場合は、MenuItemの中にMenuItemを入れ子にします。
入れ子は何階層でも可能ですが、あまり階層を深くすると選択するのが面倒になるので、2~3個程度に留めておくのが無難です。

先ほど例に出したラベルに対して2階層のメニューを付けるには、次の様になります。
<Label Content="ラベル" Width="100" Height="20">
<Label.ContextMenu>
<ContextMenu>
<MenuItem Header="メニュー名1"/>
<MenuItem Header="子メニュー名1"/>
<MenuItem Header="子メニュー名2"/>
<MenuItem Header="メニュー名2"/>
</ContextMenu>
</Label.ContextMenu>
</Label>
サンプル画面のXAML
以上の事を念頭に、先ほどのサンプル画面のコンテキストメニューを実現するXAMLは次の通りです。
<Window x:Class="ContextMenuTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ContextMenuTest"
mc:Ignorable="d"
Title="MainWindow" Height="338.309" Width="456.835">
<Grid>
<DataGrid HorizontalAlignment="Left" Height="110" Margin="71,31,0,0" VerticalAlignment="Top" Width="320">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="入力" />
<MenuItem Header="出力">
<MenuItem Header="クリップボード" />
<MenuItem Header="ファイル" />
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
</Window>
選択されたメニューの取得方法
メニューが選択されたことを知るには、大きく分けて2通りの方法があります。
1つは RoutedCommand 等を使ってC#のソースコード上にコマンド受け付けようのメソッドを用意しておき、選択されたメニューに対応するCommand をXAMLに記述する方法、もう1つはクリックイベントを使う方法です。
Command を使う方法はXAMLとC#のソースの結びつきが弱くなるため、メニューの機能をボタンに置き換えたりする場合などが柔軟に対応できますが、記述量が多くなって面倒なので、ここではクリックイベントを使う方法を採用します。
メニューごとにクリックイベントを用意する
次の様に1つ1つのMenuItemにイベントハンドラを用意し、クリック時にそれを呼ぶようにすることで、メニューが選択されたことを簡単に知ることができます。
ただ、全てのItemMenuに名前を付与する必要があることと、メニューの数が多くなるほどイベントハンドラの数が増え、ソースコードが複雑になるという課題はあるものの、一番単純な方法です。
<ContextMenu>
<MenuItem x:Name="Input" Header="入力" Click="Input_Click" />
<MenuItem x:Name ="Output" Header="出力">
<MenuItem x:Name="Clipboard" Header="クリップボード" Click="Clipboard_Click"/>
<MenuItem x:Name="File" Header="ファイル" Click="Filet_Click"/>
</MenuItem>
</ContextMenu
1つのイベントハンドラでまとめて受ける
メニューの数が多くイベントハンドラを沢山作りたくない場合、全てのMenuItem に同じイベントハンドラを指定し、1か所で処理をまかなうという方法があります。
この場合、個々のメニューに対して名前を付ける必要はありません。
この方法は、イベントハンドラ側で MenuItemの Header を取得し、その中身で処理を切り分けますので、個々のメニューが識別できるようなメニュー名にすることと、メニュー名を不用意に変更するとイベントハンドラ側で識別できなくなってしまうという課題はあります。
<ContextMenu>
<MenuItem Header="入力" Click="MenuItem_Click" />
<MenuItem Header="出力">
<MenuItem Header="クリップボード" Click="MenuItem_Click"/>
<MenuItem Header="ファイル" Click="MenuItem_Click"/>
</MenuItem>
</ContextMenu
イベントハンドラ側のソースコードは次の様に記述できます。
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var item = (MenuItem)sender;
switch(item.Header.ToString())
{
case "入力" : 処理1; bareak;
case "クリップボード" : 処理2; break;
case "ファイル" : 処理3; break;
}
}
それでは、第1階層は異なるが、第2階層は同じメニュー名にしたいとう場合はどうすればいいでしょう?

例えば、
MenuItemには Parent プロパティがあり、これを MenuItem に型変換(キャスト)することで、親(1つ上の階層)のMenuItemが取得できますので、次の様に書くことが出来ます。
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var item = (MenuItem)sender;
var parent = item.Parent;
switch(parent.Header.ToString() + item.Header.ToString())
{
case "入力実行する" : 処理1; bareak;
case "入力実行しない" : 処理2; break;
case "出力実行する" : 処理3; break;
case "出力実行しない" : 処理3; break;
}
}
親階層のMenuItemのクリックイベントを使ってメニューを判定する
メニューが階層になっている場合、親のメニューにクリックのイベントハンドラを指定しておけば、 RoutedEventArgs e の OriginalSource プロパティを使って、選択された MenuItem を取り出すことが出来ます。

この方法は、ItemMenuを入れ子にして、ドロップダウン式に選択するようなメニューの場合、その親のItemMenuにクリックイベントを指定するだけで済むため、クリックイベントの数を大幅に減らすことができます。
メニューにアイコンを入れる
ItemMenuにアイコンを表示するには、MenuItemの Icon プロパティにImageを設定する必要があります。

具体的には、アイコンを表示しない場合とする場合では、次の様に MenuItem の記述が変わります。
//アイコンの表示が無い場合の MenuItem
<MenuItem Header="入力" />
//アイコンを表示する場合の MenuItem
<MenuItem Header="入力">
<MenuItem.Icon>
<Image Source="Image/InputPinTool_16x.png"/>
</MenuItem.Icon>
</MenuItem>
表示するアイコンのイメージはどこに置いても良いのですが、次の様にプロジェクト内にImage(もしくはImages)という名前のフォルダを作成し、そこに png 型式のイメージファイルを置き、それを参照するケースが多いようです。
イメージファイルの保存先や参照方法についての詳細については、こちらに詳しく記載しています。
この記事では Image フォルダ直下にイメージファイルが置かれていることを想定して解説します。
次の画像はDataGridのコンテキストメニューにアイコンを表示した例です。

では、実際のXAMLを見ていきましょう。
Imageタグの Source にイメージファイルのパスを指定する必要がありますが、今回はプロジェクトフォルダ内に作ったImageフォルダにイメージファイルがコピーされているので、相対指定で<Image Source="Image/InputPinTool_16x.png"/> と記述しています。
イメージファイルの頭に "Image/" を記述するところがポイントです。
<DataGrid HorizontalAlignment="Left" Height="110" Margin="71,31,0,0" VerticalAlignment="Top" Width="320">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem x:Name="Input" Header="入力" Click="MenuItem_Click">
<MenuItem.Icon>
<Image Source="Image/InputPinTool_16x.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name ="Output" Header="出力">
<MenuItem.Icon>
<Image Source="Image/Output_16xLG.png"/>
</MenuItem.Icon>
<MenuItem x:Name="Clipboard" Header="クリップボード"/>
<MenuItem x:Name="File" Header="ファイル" />
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
メニューに任意のコントロールを入れる
コンテキストメニューには任意のコントロールを入れる事も可能です。
この例では、第1階層目にテキストボックスとボタンを、第2階層目にテキストブロック、テキストボックス、ボタンを入れています。

第1階層目には通常 MenuItem を置きますが、任意のコントロールを置く事も可能です。
MenuItemの代わりに、テキストボックスやボタンを置けば、文字列の入力やボタンクリックが行えるようになります。

例えば、第1階層目にテキストボックス、ボタンを表示させたい場合、次の様になります。
<ContextMenu>
<TextBox Text="abcdefgh"/>
<Button Content="クリア"/>
<MenuItem x:Name="Input" Header="入力"/>
</ContextMenu>
第2階層目以降は、MenuItemの中に任意のコントロールを入れ子にします。

例えば、MenuItemにテキストボックスを入れたい場合は次の様になります。
<ContextMenu>
<MenuItem x:Name="Input" Header="入力">
<TextBox Text="abcdefgh"/>
</MenuItem>
</ContextMenu>
サンプル画面では1つのMenuItemに複数のコントロール(ラベル、テキストボックス、ボタン)を入れてますが、通常MenuItemには1つのコントロールしか入れられません。
WPFではよく行う手法ですが、GridやStackPanel等を定義し、その上に複数のコントロールを置くという方法を使っています。
<ContextMenu>
<MenuItem x:Name="Input" Header="入力">
<StackPanel Orientation="Horizontal">
<TextBox Text="abcdefgh" Width="100"/>
<Button Content="実行" Margin="5,0,5,0"/>
</StackPanel>
</MenuItem>
</ContextMenu>
それでは、サンプル画面の実際のXAMLを掲載しておきます。
これまで説明した内容を踏まえてソースコードを見て頂ければ、内容が理解できるかと思います。
<DataGrid HorizontalAlignment="Left" Height="110" Margin="71,31,0,0" VerticalAlignment="Top" Width="320">
<DataGrid.ContextMenu>
<ContextMenu>
<TextBox Text="abcdefgh"/>
<Button Content="クリア"/>
<MenuItem x:Name="Input" Header="入力" Click="MenuItem_Click">
<MenuItem.Icon>
<Image Source="Image/InputPinTool_16x.png"/>
</MenuItem.Icon>
<StackPanel Orientation="Horizontal">
<TextBlock Text="制限値" Padding="5,0,10,0" VerticalAlignment="Center"/>
<TextBox Text="12345678" Width="100"/>
<Button Content="実行" Margin="5,0,5,0"/>
</StackPanel>
</MenuItem>
<MenuItem x:Name ="Output" Header="出力">
<MenuItem.Icon>
<Image Source="Image/Output_16xLG.png"/>
</MenuItem.Icon>
<MenuItem x:Name="Clipboard" Header="クリップボード"/>
<MenuItem x:Name="File" Header="ファイル" />
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
メニューを開いた時、動的にコントロールに値をセットする
コンテキストメニューに入れたコントロールに対して初期値を設定したい場合は、XAML上で簡単に設定できます。
しかし、時にはプログラムの動作に応じて、動的に初期値を変更したい場合もあります。
例えば、タブコントロールを使ったプログラムにおいて、選択されているタブ名前を変更するような場合です。

コンテキストメニューのサブメニューを開くと SubmenuOpened イベントが発生しますので、これを使ってコントロールに初期値を登録することが可能です。
下記のサンプルは、TabControlのコンテキストメニューに対して、SubmenuOpened イベントを使った例になります。
<TabControl Margin="205,134,238,54" x:Name="uxTabControl">
<TabControl.ContextMenu>
<ContextMenu>
<MenuItem SubmenuOpened="MenuItem_SubmenuOpened" Header="名前の表示">
<TextBox x:Name="uxTabName"></TextBox>
</MenuItem>
</ContextMenu>
</TabControl.ContextMenu>
<TabItem Header="Editor">
<Grid Background="#FFE5E5E5">
<TextBox TextWrapping="Wrap" AcceptsReturn="True" Text="12345678ABCDE"/>
</Grid>
</TabItem>
<TabItem Header="Calendar">
<Grid Background="#FFE5E5E5">
<Grid/>
<Calendar/>
</Grid>
</TabItem>
</TabControl>
下記はC#側のソースコードです。
private void MenuItem_SubmenuOpened(object sender, RoutedEventArgs e)
{
uxTabName.Text = ((TabItem)uxTabControl.SelectedItem).Header.ToString();
}
チェックが入力できるメニューを作る
何らかの機能をON/OFFするようなメニューを作成したい場合、チェックボックスコントロールを入れてしまうという方法もありますが、実はMenuItemの IsCheckable プロパティに True を設定してあげることで、簡単に実現できます。

下記は Button タグに IsCheckable="True" を記述したXAMLのサンプルです。
<Button Content="Button" HorizontalAlignment="Left" Height="31" Margin="71,235,0,0" VerticalAlignment="Top" Width="113">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="選択" IsCheckable="True"></MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
チェックがされているか否かを取得するには、 MenuItem の IsChecked プロパティを使います。
これは通常のチェックボックスと同じプロパティなので、if 文で参照する場合は (bool)IsChecked という具合に型変換をしてから参照します。
if((bool)menuItem.IsChecked)
{
~チェックが入っている時の処理~
}
初期値としてチェックを入れておきたい場合は、これもチェックボックスと同様に、 IsChecked プロパティに true を設定しておきます。
menuItem.IsChecked = true;
この方法には一つ落とし穴があって、メニューを入れ子にしていた場合、「親階層のMenuItemのクリックイベントを使ってメニューを判定する」という方法がそのまま使えません。
どういうことかというと、親階層のクリックイベントの sender で渡ってくる情報は、あくまでも親のMenuItemの情報であり、子供のMenuItemが渡ってくる訳ではないからです。
つまり、IsCheckable="False" の場合、親のクリックイベントで次のように記述すれば、実際にクリックされたメニューのヘッダが取得できましたが、IsCheckable=”True” の場合、親のヘッダしか取得できません。
var header = ((MenuItem)sender).Header.ToString()
ただ、親のMenuItemにある Items コレクションには、子供のItemMenuが登録されているので、ここから子供のItemMenuを取り出し、チェックされているか否かを IsChecke プロパティで判定することは可能です。
この方法を使うと、次のように記述できます。
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var items = ((MenuItem)sender).Items;
if((bool)((MenuItem)items[0]).IsChecked) { ~チェックされていた時に実行したい処理~ }
if((bool)((MenuItem)items[1]).IsChecked) { ~チェックされていた時に実行したい処理~ }
if((bool)((MenuItem)items[2]).IsChecked) { ~チェックされていた時に実行したい処理~ }
}
まとめ
今回はWPFにおけるコンテキストメニューの使い方について解説しました。
WindowsForm と比べて、WPFのコンテキストメニューはかなり自由度が高いのではないでしょうか。
XAMLの記述量は増えますが、ちょっとした入力機能なら簡単にメニューに含ませることが出来ます。
アプリケーションに機能を追加する際、ボタンの配置では足らないと思った場合は、一度コンテキストメニューの利用についてもご検討ください。