WPFのTreeViewの子要素を右クリックするとメニューを出して、そのアイテムを利用したコマンドを実装したかったんですが、やたらと実装が難航したので設定方法をメモ。
実装例
<UserControl.Resources>
<ContextMenu x:Key="SampleContextMenu">
<MenuItem Header="右クリックメニューアイテム"
Command="{Binding
Path=PlacementTarget.Tag.Command,
RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding
Path=PlacementTarget.DataContext,
RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</UserControl.Resources>
<TreeView ItemsSource="{Binding Path=...}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Tag" Value="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeView}}}"/>
<Setter Property="ContextMenu" Value="{Binding Source={StaticResource SampleContextMenu}}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<!-- 省略 -->
</TreeView.ItemTemplate>
</TreeView>
やりたかったこと
- 右クリックメニューのクリックで、ViewModelに実装したCommandを実行する
- その際の引数として、右クリックしたTreeViewItemのデータを利用したい
- イベントハンドラは使わずに実現したい(コードビハインドに書くのは最後の手段)
内容解説
まず注意する必要があるのは、WPFのContextMenuはXAMLのVisualTreeの定義とは別管理?(※要調査)になるらしく、単純にViewModelのCommandはそのまま定義しても見えないということです。右クリックメニューを出したアイテムが見たい場合は PlacementTarget
を利用する必要があるようです。
TreeViewの場合は、Templateの関係でDataContextが書き換わってしまっています。そのため2のTreeViewItemのデータはPlacementTargetのDataContextを利用すれば良いです。
ViewModelに定義しているCommandを使いたい場合はさらに上のDataContextを参照する必要があります。Templateの親を指定できる TemplatedParent
はContextMenuからでは使えません。なので、TreeViewItemのTagプロパティに親のDataContextを事前にBindしておき、ContextMenuから PlacementTarget.Tag
で参照できるようにします。
(ちなみにTagプロパティは任意のデータを格納する専用のプロパティのようです。)
FrameworkElement.Tag プロパティ (System.Windows)
これでTreeViewItemのContextMenuからViewModelのCommandを実行できるようになりました。
Templateを使用しているListViewでも同様のことが起きると思いますが、上記の実装で同様に対処できるかと思います。
参考
WPF ContextMenu woes: How do I set the DataContext of the ContextMenu? - Stack Overflow
WPF/XAML で右クリックメニューへの DataContext の引き回し - graphics.hatenablog.com