【问题标题】:Command bind to ContextMenu (which on ListBoxItem in ListBox) don't work [duplicate]命令绑定到 ContextMenu(在 ListBox 中的 ListBoxItem 上)不起作用 [重复]
【发布时间】:2013-10-27 04:29:22
【问题描述】:

在 WPF 中,使用 MVVM 灯,有一个 Class(由一些学生组成),而 Class 持有一些 Students。

右击一个学生的名字,然后会显示一个MessageBox,这样就可以了:

ClassDetailView.xaml

<UserControl DataContext="{Binding ClassDetail, Source={StaticResource Locator}}">
    <DockPanel>
        <ListBox 
            ItemsSource="{Binding Students}" 
            DisplayMemberPath="Name">
            <ListBox.ContextMenu>
                <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
                    <MenuItem 
                        Header="Show Selected" 
                        Command="{Binding Path=DataContext.RemoveStudentCommand}"
                        CommandParameter="{Binding Path=SelectedItem}"/>
                </ContextMenu>
            </ListBox.ContextMenu>
        </ListBox>
    </DockPanel>
</UserControl>

但是,这样不行(使用 ListBox.ItemContainerStyle):

<ListBox.ItemContainerStyle>
    <Style TargetType="ListBoxItem">
        <Setter Property="ContextMenu">
            <Setter.Value>
                <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
                    <MenuItem Header="Show Selected" 
                            Command="{Binding Path=DataContext.RemoveStudentCommand}"
                            CommandParameter="{Binding Path=SelectedItem}"/>
                 </ContextMenu>
             </Setter.Value>
         </Setter>
     </Style>
 </ListBox.ItemContainerStyle>

而不是

<ListBox.ContextMenu>
    <ContextMenu ...>
        ...
    <ContextMenu />
</ListBox.ContextMenu>

ClassDetailViewModel.cs

namespace ContextMenu.ViewModel
{
    public class ClassDetailViewModel : ViewModelBase
    {
        public ClassDetailViewModel()
        {
            CreateData();
        }

        public void CreateData()
        {
            students.Add(new StudentViewModel() { Name = "QQ" });
            students.Add(new StudentViewModel() { Name = "WW" });
            students.Add(new StudentViewModel() { Name = "EE" });
            students.Add(new StudentViewModel() { Name = "RR" });
            students.Add(new StudentViewModel() { Name = "AA" });
            students.Add(new StudentViewModel() { Name = "SS" });
            students.Add(new StudentViewModel() { Name = "DD" });
            students.Add(new StudentViewModel() { Name = "FF" });
            students.Add(new StudentViewModel() { Name = "ZZ" });
            students.Add(new StudentViewModel() { Name = "XX" });
        }

        public const string StudentsPropertyName = "Students";
        private ObservableCollection<StudentViewModel> students = 
            new ObservableCollection<StudentViewModel>();
        public ObservableCollection<StudentViewModel> Students
        {
            get { return students; }
            set
            {
                if (students == value) { return; }
                students = value;
                RaisePropertyChanged(StudentsPropertyName);
            }
        }

        private RelayCommand<StudentViewModel> removeStudentCommand;
        public RelayCommand<StudentViewModel> RemoveStudentCommand
        {
            get
            {
                return removeStudentCommand
                    ?? (removeStudentCommand =
                        new RelayCommand<StudentViewModel>(ExecuteRemoveStudentCommand));
            }
        }
        private void ExecuteRemoveStudentCommand(StudentViewModel student)
        {
            if (null == student) { return; }
            MessageBox.Show(string.Format("RemoveStudent:{0}", student.Name));
        }
    }
}

StudentViewModel.cs

namespace ContextMenu.ViewModel
{
    public class StudentViewModel : ViewModelBase
    {
        public const string NamePropertyName = "Name";
        private string name = "";
        public string Name
        {
            get { return name; }
            set
            {
                if (name == value) { return; }
                name = value;
                RaisePropertyChanged(NamePropertyName);
            }
        }
    }
}

【问题讨论】:

    标签: wpf mvvm command contextmenu


    【解决方案1】:

    您可以将上下文菜单用作资源吗?

    类似:

    <UserControl.Resources>
    
    <ContextMenu x:name="contextMenuExample" DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
                        <MenuItem Header="Show Selected" 
                                Command="{Binding Path=DataContext.RemoveStudentCommand}"
                                CommandParameter="{Binding Path=SelectedItem}"/>
                     </ContextMenu>
    </UserControl.Resources>
    

    然后在列表中,执行以下操作:

    <Listbox ContextMenu = {StaticResource contextMenuExample} ... />
    

    或者你真的要使用 ItemContainerStyle?

    来自 -> how to right click on item from Listbox and open menu on WPF

    <ListBox Name="someListBox" MouseDown="someListBox_MouseDown">
        <ListBox.Resources>
    
            <!--Defines a context menu-->
            <ContextMenu x:Key="MyElementMenu">
                <MenuItem Header="Delete" Click="MenuItemDelete_Click"/>
            </ContextMenu>
    
            <!--Sets a context menu for each ListBoxItem in the current ListBox-->
            <Style TargetType="{x:Type ListBoxItem}">
                 <Setter Property="ContextMenu" Value="{StaticResource MyElementMenu}"/>
            </Style>
    
        </ListBox.Resources>
        <ListBoxItem>...</ListBoxItem>
        <ListBoxItem>...</ListBoxItem>
        <ListBoxItem>...</ListBoxItem>
    </ListBox>
    

    【讨论】:

    • 我只想在右键单击 ListBoxItem 而不是整个 ListBox 时显示 ContextMenu。
    • 如果使用 ListBox.Resources,我认为它们没有什么不同。并且在我的应用中使用 MVVM,所以会更复杂。
    • 您可以在用户控件的资源中拥有 ContextMenu,然后将其应用到具有相同行为的不同列表框中。在这些列表框中,您唯一需要做的就是样式部分...
    • 你能把上面的转换成mvvm吗?毕竟,我认为这是 DataContent 的问题。
    【解决方案2】:

    通过将 ContextMenu 移动到 ListBoxItem,您已将 DataContext 从 ClassDetailsViewModel(ListBox 的 DataContext)更改为 StudentViewModel(ListBoxItem 的 DataContext)。因此,您需要更改访问父 ListBox 的 DataContext 的路径才能访问 RelayCommand。

    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext}">
                        <MenuItem Header="Show Selected" 
                                Command="{Binding Path=RemoveStudentCommand}"
                                CommandParameter="{Binding Path=SelectedItem}"/>
                     </ContextMenu>
                 </Setter.Value>
             </Setter>
         </Style>
     </ListBox.ItemContainerStyle>
    

    【讨论】:

    • 这样也行不通。
    • 它实际上可以工作,但出于某些奇怪的原因,只有将 ContextMenu 定义为资源。
    【解决方案3】:

    您需要一个代理将命令绑定到列表框项的上下文菜单。在这里查看答案:

    http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

    【讨论】:

    • 这个解决方案对我来说很好,我之前也读过 Josh Smith 的类似解决方案
    • 是的!您还可以通过RelativeSource 绑定使用此技术绑定到控件。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2017-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多