トップ回答者
TreeView のコンテキスト メニューへ独自コマンドをバインドするには

質問
-
TreeView のコンテキスト メニューに独自に作成したコマンドをバインドするため、以下のようなコードを書きましたが、想定した動作をしません。
想定した動作: 「コマンド実行」というメッセージ ウィンドウが表示される。
実際の動作: 何も起きない。想定した動作をさせるためにはどうすればよいでしょうか。
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:my="clr-namespace:WpfApplication2" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.DataContext> <my:TestViewModel x:Name="TestViewModel"/> </Grid.DataContext> <TreeView ItemsSource="{Binding Path=TreeRoot, Mode=OneWay}"> <TreeView.Resources> <ContextMenu x:Key="topLevelTreeMenu"> <MenuItem Header="Test Command" Command="{Binding Path=TestCommand}"/> </ContextMenu> </TreeView.Resources> <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="ContextMenu" Value="{StaticResource topLevelTreeMenu}" /> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Path=Children}"> <TextBlock Text="{Binding Path=Name}"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid> </Window>
using System; using System.Windows; using System.Windows.Input; namespace WpfApplication2 { public class TestViewModel { public ICommand TestCommand { get; private set; } public TestTreeItemChildren TreeRoot { get; private set; } public TestViewModel() { this.TestCommand = new TestCommandImpl(); this.TreeRoot = new TestTreeItemChildren(); this.TreeRoot.Add(new TestTreeItem() { Name = "rootnode" }); // テスト用。 this._testTreeGenerate(); } private void _testTreeGenerate() { this.TreeRoot[0].Children.Add(new TestTreeItem() { Name = "subnode" }); } private class TestCommandImpl : ICommand { public bool CanExecute(object parameter) { return true; } public event EventHandler CanExecuteChanged; public void Execute(object parameter) { MessageBox.Show("コマンド実行"); } } } }
namespace WpfApplication2 { public class TestTreeItem { public string Name { get; set; } public TestTreeItemChildren Children { get; set; } public TestTreeItem() { this.Children = new TestTreeItemChildren(); } } }
using System.Collections.ObjectModel; namespace WpfApplication2 { public class TestTreeItemChildren : ObservableCollection<TestTreeItem> { } }
回答
-
データコンテキストの継承は論理ツリーによって行われますが、ContextMenuやToolTipといった新しくウィンドウが開くものにおいては、そこで論理ツリーが途切れるため、データコンテキストが継承されなくなります。
そのため、MenuItemのDataContextはTreeViewItemのDataContextとは別物になっており、当然ながらTextCommandなるプロパティも持っていないことになります。
こういうケースでデータコンテキストを引っ張ってくるには、PlacementTargetプロパティで新しいウィンドウ(ContextMenu)を表示する元となった要素(TreeViewItem)を参照するのが定番です。
WPF: ContextMenu Strikes Again. DataContext Not Updated - CodeProject
- 回答としてマーク DJ_Kaosun 2014年10月21日 8:27
すべての返信
-
データコンテキストの継承は論理ツリーによって行われますが、ContextMenuやToolTipといった新しくウィンドウが開くものにおいては、そこで論理ツリーが途切れるため、データコンテキストが継承されなくなります。
そのため、MenuItemのDataContextはTreeViewItemのDataContextとは別物になっており、当然ながらTextCommandなるプロパティも持っていないことになります。
こういうケースでデータコンテキストを引っ張ってくるには、PlacementTargetプロパティで新しいウィンドウ(ContextMenu)を表示する元となった要素(TreeViewItem)を参照するのが定番です。
WPF: ContextMenu Strikes Again. DataContext Not Updated - CodeProject
- 回答としてマーク DJ_Kaosun 2014年10月21日 8:27
-
ありがとうございます。頂いた情報をもとに検索したサンプル コードを色々いじくりまわした結果、一応動くようになりました。
わざわざ Grid で括っていたり、Tag という見慣れないものが出てきたりしていたので、いろいろ調べてみます。
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:my="clr-namespace:WpfApplication2" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <my:TestViewModel x:Name="testViewModel"/> </Window.DataContext> <Grid> <TreeView ItemsSource="{Binding Path=TreeRoot, Mode=OneWay}"> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Children}"> <Grid Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TreeView}}"> <Grid.ContextMenu> <ContextMenu> <MenuItem Header="Test Command" Command="{Binding Path=PlacementTarget.Tag.TestCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" /> </ContextMenu> </Grid.ContextMenu> <TextBlock Text="{Binding Name}"/> </Grid> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid> </Window>