none
TreeView のコンテキスト メニューへ独自コマンドをバインドするには RRS feed

  • 質問

  • 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>
        {
        }
    }
    

    2014年10月21日 5:07

回答

  • データコンテキストの継承は論理ツリーによって行われますが、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
    2014年10月21日 6:57

すべての返信

  • データコンテキストの継承は論理ツリーによって行われますが、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
    2014年10月21日 6:57
  • ありがとうございます。頂いた情報をもとに検索したサンプル コードを色々いじくりまわした結果、一応動くようになりました。

    わざわざ 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>
    

    2014年10月21日 8:27