WPF上下文菜单未绑定到右数据绑定项

17 data-binding wpf contextmenu mvvm

在选项卡页面上的用户控件上的上下文菜单中绑定命令时遇到问题.我第一次使用菜单(右键单击选项卡)它工作得很好,但如果我切换选项卡,命令将使用第一次使用的数据绑定实例.

如果我在usercontrol中放置一个绑定到该命令的按钮,它按预期工作...

有人可以告诉我,我做错了什么?

这是一个暴露问题的测试项目:

App.xaml.cs:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        CompanyViewModel model = new CompanyViewModel();
        Window1 window = new Window1();
        window.DataContext = model;
        window.Show();
    }
}
Run Code Online (Sandbox Code Playgroud)

Window1.xaml:

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vw="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">

  <Window.Resources>
    <DataTemplate x:Key="HeaderTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Path=Name}" />
        </StackPanel>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vw:PersonViewModel}">
        <vw:UserControl1/>
    </DataTemplate>

</Window.Resources>
<Grid>
    <TabControl ItemsSource="{Binding Path=Persons}" 
                ItemTemplate="{StaticResource HeaderTemplate}"
                IsSynchronizedWithCurrentItem="True" />
</Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)

UserControl1.xaml:

<UserControl x:Class="WpfApplication1.UserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    MinWidth="200">
    <UserControl.ContextMenu>
        <ContextMenu >
            <MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/>
        </ContextMenu>
    </UserControl.ContextMenu>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0">The name:</Label>
        <TextBox Grid.Column="1" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" />
    </Grid>
</UserControl>
Run Code Online (Sandbox Code Playgroud)

CompanyViewModel.cs:

public class CompanyViewModel
{
    public ObservableCollection<PersonViewModel> Persons { get; set; }
    public CompanyViewModel()
    {
        Persons = new ObservableCollection<PersonViewModel>();
        Persons.Add(new PersonViewModel(new Person { Name = "Kalle" }));
        Persons.Add(new PersonViewModel(new Person { Name = "Nisse" }));
        Persons.Add(new PersonViewModel(new Person { Name = "Jocke" }));
    }
}
Run Code Online (Sandbox Code Playgroud)

PersonViewModel.cs:

public class PersonViewModel : INotifyPropertyChanged
{
    Person _person;
    TestCommand _testCommand;

    public PersonViewModel(Person person)
    {
        _person = person;
        _testCommand = new TestCommand(this);
    }
    public ICommand ChangeCommand 
    {
        get
        {
            return _testCommand;
        }
    }
    public string Name 
    {
        get
        {
            return _person.Name;
        }
        set
        {
            if (value == _person.Name)
                return;
            _person.Name = value;
            OnPropertyChanged("Name");
        }
    }
    void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
}
Run Code Online (Sandbox Code Playgroud)

TestCommand.cs:

public class TestCommand : ICommand
{
    PersonViewModel _person;
    public event EventHandler CanExecuteChanged;

    public TestCommand(PersonViewModel person)
    {
        _person = person;
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }
    public void Execute(object parameter)
    {
        _person.Name = "Changed by command";
    }
}
Run Code Online (Sandbox Code Playgroud)

Person.cs:

public class Person
{
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

Cam*_*and 22

这里要记住的关键是上下文菜单不是可视化树的一部分.

因此,它们不会继承与它们所属的控件相同的源以进行绑定.处理此问题的方法是绑定到ContextMenu本身的放置目标.

<MenuItem Header="Change" Command="{Binding 
    Path=PlacementTarget.ChangeCommand, 
    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
/>
Run Code Online (Sandbox Code Playgroud)

  • @Schnieder:欢迎来到WPF!:d (4认同)
  • WPF 4.0:<ContextMenu DataContext ="{Binding PlacementTarget.DataContext,RelativeSource = {RelativeSource Self}}">(更短). (4认同)
  • 我不相信这个答案.命令绑定DO适用于菜单项(它知道它必须绑定视图模型)...问题是当datacontext由于切换选项卡而发生更改时,菜单项不会重新绑定.如果由于它们不是视觉树的一部分,它怎么会第一次起作用? (2认同)
  • 我仍然很乐意从WPF团队等中找到对此的明确解释。我是否可以建议您在答案中插入一段,解释其原因在于其不在可视化树中,因此当其中的内容不更新时,数据上下文以及绑定不会更新。内容演示者由于选择了一个选项卡而发生了变化(假设是引起问题的原因) (2认同)

Cyb*_*onk 8

我发现将命令绑定到上下文菜单项的最简洁方法是使用一个名为CommandReference的类.您可以在WPF Futures上的Codeplex上的MVVM工具包中找到它.

XAML可能如下所示:

<UserControl x:Class="View.MyView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:vm="clr-namespace:ViewModel;assembly=MyViewModel"
                xmlns:mvvm="clr-namespace:ViewModelHelper;assembly=ViewModelHelper"
           <UserControl.Resources>
                <mvvm:CommandReference x:Key="MyCustomCommandReference" Command="{Binding MyCustomCommand}" />

                <ContextMenu x:Key="ItemContextMenu">
                    <MenuItem Header="Plate">
                        <MenuItem Header="Inspect Now" Command="{StaticResource MyCustomCommandReference}"
                                CommandParameter="{Binding}">
                        </MenuItem>
                    </MenuItem>
               </ContextMenu>
    </UserControl.Resources>
Run Code Online (Sandbox Code Playgroud)

MyCustomCommand是ViewModel上的RelayCommand.在此示例中,ViewModel附加到代码隐藏中的视图的datacontext.

注意:此XAML是从一个正在运行的项目中复制并简化以进行说明.可能存在拼写错误或其他轻微错误.


小智 5

我最近遇到了与ListBox中的ContextMenu相同的问题.我试图在没有任何代码隐藏的情况下以MVVM方式绑定命令.我终于放弃了,我向朋友求助了.他找到了一个略微扭曲但简洁的解决方案.他通过列表框的文本菜单的DataContext的,然后通过访问ListBox的DataContext的发现在视图模型的命令.这是迄今为止我见过的最简单的解决方案.没有自定义代码,没有Tag,只有纯XAML和MVVM.

我在Github上发布了一个完整的工作样本.这是XAML的摘录.

<Window x:Class="WpfListContextMenu.MainWindow" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow" Height="350" Width="268">
  <Grid>
    <DockPanel>
      <ListBox x:Name="listBox" DockPanel.Dock="Top" ItemsSource="{Binding Items}" DisplayMemberPath="Name"
               SelectionMode="Extended">
        <ListBox.ContextMenu>
          <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
            <MenuItem Header="Show Selected" Command="{Binding Path=DataContext.ShowSelectedCommand}"
                      CommandParameter="{Binding Path=SelectedItems}" />
          </ContextMenu>
        </ListBox.ContextMenu>
      </ListBox>
    </DockPanel>
  </Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)