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

Wpf 如何设置以TreeView样式定义的ContextMenu的DataContext,wpf,mvvm,treeview,contextmenu,menuitem,Wpf,Mvvm,Treeview,Contextmenu,Menuitem,我有一个树状视图,它在一个样式中定义了它的上下文菜单。菜单项有绑定到它们的命令,但绑定这些命令时遇到问题。我意识到这是因为可视化树中不存在ContextMenu,所以我尝试使用了PlacementTarget对象和标记属性,我在其他示例中看到了该属性,但它仍然不起作用。知道我做错了什么吗 <Grid Name="MyGrid" DataContext="{Binding}"> <TreeView Name="TreeView" ItemsSource="

我有一个树状视图,它在一个样式中定义了它的上下文菜单。菜单项有绑定到它们的命令,但绑定这些命令时遇到问题。我意识到这是因为可视化树中不存在ContextMenu,所以我尝试使用了PlacementTarget对象和标记属性,我在其他示例中看到了该属性,但它仍然不起作用。知道我做错了什么吗

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

更新


下面的答案是完全正确的,但经过思考后,我意识到我对这个问题的看法是错误的。我看了一眼,他解释说,使用ViewModel模式意味着使用TreeView来显示数据,而不是放置数据。因此,我能够将树状视图中的每个项目作为自己的视图模型来处理。这意味着我可以对特定的ViewModel执行上下文菜单中的任何命令(如果需要,可以在新ViewModel上使用父属性导航到父视图模型)。

上下文菜单确实具有DataContext。您不需要显式设置它

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

假设您有一个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>
    <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>
    <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>

但是,如果在ViewModel中有该命令,则需要将ViewModel实例提供给ContextMenu的DataContext

以下是一个例子:

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!"));
        }
    }

    ...
class MainWindowViewModel:INotifyPropertyChanged
{
私人可观察收集员工;
公共主窗口视图模型()
{
this.Employee=新的ObservableCollection();
this.Employee.Add(newperson{Name=“Test”});
}
公开收集雇员
{
获取{返回this.employee;}
设置{this.employee=value;this.OnPropertyChanged(“employee”);}
}
公共ICommand dosomething命令
{
得到
{
返回新的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>
    <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>
    <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>

或者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>
    <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>
    <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>


ItemContainerStyle应用于包装每个项目的
ContentPresenter
,我不相信
Tag
属性是由
ContentPresenter
设置的。请尝试使用
ItemContainerStyle
中的另一个
Style
setter绑定
Tag
属性根据下面的#dev hedgehog的代码。在我的后续评论中,你知道我如何仍然可以访问我右键单击的菜单项的datacontext,因为我需要它来查看菜单项吗?我不确定我是否理解这个问题。假设你已经将
标记
属性绑定到当前的
树项
数据上下文,你知道吗您应该可以访问
DoSomethingCommand
和属性来确定可见性。抱歉,我想我没有解释清楚。该命令绑定到基础viewModel(而不是treeView)根据#devhedgehog提供的第一个XML示例。这意味着我丢失了刚才右键单击的TreeViewItem的datacontext。我需要它,以便可以在MenuItem上设置可见性。我现在明白了。您应该能够进行两个单独的绑定。也许可以将
ContextMenu.datacontext
绑定到
PlacementTarget.DataC将
命令绑定到
PlacementTarget.Tag.DoSomethingCommand
。您可能需要一个
相对资源
与第二个绑定到target
ContextMenu.PlacementTarget.Tag
很好,我只需将标记从移动到Setter属性中-谢谢。好的,现在我意识到了无法绑定MeNuItitem的可视性,因为我已经更改了DATACONTRONT。在这种情况下,如何获取MeNuItIt的DATACONTRONT?您必须考虑绑定中的源更改为VIEW模型或项目本身。