[WPF][MVVM] TreeViewItemの右クリックメニューからViewModelのCommandを実行する

  • 2
    いいね
  • 0
    コメント

WPFのTreeViewの子要素を右クリックするとメニューを出して、そのアイテムを利用したコマンドを実装したかったんですが、やたらと実装が難航したので設定方法をメモ。

実装例

ContextMenu側XAML
<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側XAML
<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>

やりたかったこと

  1. 右クリックメニューのクリックで、ViewModelに実装したCommandを実行する
  2. その際の引数として、右クリックしたTreeViewItemのデータを利用したい
  3. イベントハンドラは使わずに実現したい(コードビハインドに書くのは最後の手段)

内容解説

まず注意する必要があるのは、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