wpf - 如何设置以 TreeView 样式定义的 ContextMenu 的 DataContext

标签 wpf mvvm treeview contextmenu menuitem

我有一个 TreeView,其 ContextMenu 在样式中定义。菜单项有绑定(bind)到它们的命令,但我在绑定(bind)这些命令时遇到问题。我意识到这是因为 ContextMenu 不存在于可视化树中,所以我尝试使用 PlacementTarget 对象和 Tag 属性,我在其他示例中看到了它们的使用,但它仍然不起作用。任何想法我做错了什么。

<Grid Name="MyGrid" DataContext="{Binding}">
    <TreeView Name="TreeView"
        ItemsSource="{Binding TreeViewElements}"
        Tag="{Binding DataContext, ElementName=MyGrid}">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="ContextMenu">
                    <Setter.Value>
                        <ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={x:Static RelativeSource.Self}}">
                            <MenuItem Header="Do Something" 
                                      Command="{Binding DoSomethingCommand}"/>
                        </ContextMenu>
                    </Setter.Value>
                </Setter>
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding SubElements}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Margin="2" Text="{Binding HeaderText}" ></TextBlock>
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
</Grid>

更新

下面的答案都是完全有效和正确的,但经过一番反射(reflection),我意识到我以错误的方式看待这个问题。我看了一下this article by Josh Smith他解释说,使用 ViewModel 模式意味着您使用 TreeView 来显示数据,而不是放置数据。因此,我能够将 TreeView 中的每个项目视为它自己的 View 模型。这意味着我可以从上下文菜单中针对特定 ViewModel 执行任何命令(如果需要,可以使用新 ViewModel 上的父属性导航到父 View 模型)。

最佳答案

ContextMenu 确实有 DataContext。您不需要显式设置它。

在您的情况下,ContextMenu 中的 DataContext 与 TreeViewItem 中的相同。这意味着它不是 ViewModel,而是项目本身。

假设您有一个 Person 类型的对象列表。那么命令应该放在Person内部。

public class Person : INotifyPropertyChanged
{
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; this.OnPropertyChanged("Name"); }
    }

    public ICommand DoSomethingCommand
    {
        get
        {
            return new RelayCommand(x => MessageBox.Show("Works!"));
        }
    }

    ...

然后 XAML 应该如下所示:

        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="ContextMenu">
                    <Setter.Value>
                        <ContextMenu>
                            <MenuItem Header="Do Something" 
                                  Command="{Binding DoSomethingCommand}"/>
                        </ContextMenu>
                    </Setter.Value>
                </Setter>
            </Style>
        </TreeView.ItemContainerStyle>

但是,如果您的 ViewModel 中有该命令,那么您将需要向 ContextMenu 的 DataContext 提供 ViewModel 的实例。

这是一个例子:

class MainWindowViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Person> employee;

    public MainWindowViewModel()
    {
        this.Employee = new ObservableCollection<Person>();
        this.Employee.Add(new Person { Name = "Test" });
    }

    public ObservableCollection<Person> Employee
    {
        get { return this.employee; }
        set { this.employee = value; this.OnPropertyChanged("Employee"); }
    }

    public ICommand DoSomethingCommand
    {
        get
        {
            return new RelayCommand(x => MessageBox.Show("Works!"));
        }
    }

    ...

那么 XAML 将如下所示:

    <TreeView x:Name="treeView" ItemsSource="{Binding Employee}">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=DataContext}"></Setter>
                <Setter Property="ContextMenu">
                    <Setter.Value>
                        <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
                            <MenuItem Header="Do Something" 
                                  Command="{Binding DoSomethingCommand}"/>
                        </ContextMenu>
                    </Setter.Value>
                </Setter>
            </Style>
        </TreeView.ItemContainerStyle>
    </TreeView>

或者 XAML 可能如下所示:

    <TreeView x:Name="treeView" ItemsSource="{Binding Employee}">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="ContextMenu">
                    <Setter.Value>
                        <ContextMenu DataContext="{x:Reference treeView}">
                            <MenuItem Header="Do Something" 
                                  Command="{Binding DataContext.DoSomethingCommand}"/>
                        </ContextMenu>
                    </Setter.Value>
                </Setter>
            </Style>
        </TreeView.ItemContainerStyle>
    </TreeView>

关于wpf - 如何设置以 TreeView 样式定义的 ContextMenu 的 DataContext,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20706017/

相关文章:

c# - RelayCommand不刷新执行/无法执行更改

delphi - 如何使用 "levels"填充基于平面列表的 TreeView ?

wpf - 为什么 TreeViewItem 的 MouseDoubleClick 事件每次双击都会引发多次?

wpf - HierarchicalDataTemplate.ItemTemplate 中的水平拉伸(stretch) WPF ContextMenu MenuItem

Wpf DataGrid 放大/缩小

WPF 绑定(bind) FallbackValue 设置为绑定(bind)

wpf - Xaml中CollectionViewSource SortDescription的绑定(bind)PropertyName

c# - ListView WPF 上的页脚

wpf - 将上下文菜单命令绑定(bind)到不在上下文视觉树中的 ViewModel

delphi - Treeview Imageindex-图像不断变化