如何将列表视图项的上下文菜单绑定到ICommand

时间:2019-09-13 08:00:08

标签: c# wpf mvvm

我试图将listview项的上下文菜单绑定到我的VM中包含的ICommand,但是无法绑定它。

我尝试了以下代码以在列表视图项上显示上下文菜单,并将其绑定到我的VM。

查看代码

<ListView ItemsSource="{Binding StudyList, Mode=TwoWay}" SelectedItem="{Binding SelectedStudy, Mode=TwoWay}" >
    <ListView.Resources>
        <ContextMenu x:Key="ItemContextMenu" ItemsSource="{Binding StudyVM}">
            <MenuItem Header="Lock" Command="{Binding LockCommand}"/>
        </ContextMenu>
    </ListView.Resources>

    <ListView.ItemContainerStyle>
        <Style TargetType="{x:Type ListViewItem}">
            <Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}" />
        </Style>
    </ListView.ItemContainerStyle>

    <ListView.View>
        <GridView>
            <GridViewColumn Header="Status" DisplayMemberBinding="{Binding FullStatus}" Width="60"/>
            <GridViewColumn Header="Patient Name" DisplayMemberBinding="{Binding FullName}" Width="350"/>
        </GridView>
    </ListView.View>
</ListView>

VM代码

public sealed class StudyVM : BaseVM {
   public RelayCommand LockCommand { get; set; }

   public StudyVM() {
       LockCommand = new RelayCommand(() => ExecuteLockCommad());
   }

   void ExecuteLockCommad() {
       //Some code to be execute when menu item clicked
   }
}

我已将视图的DataContext设置为StudyVM。请注意,我已经跳过了一些与列表视图项源和列表视图所选项有关的代码(不在主题范围内并且工作正常)。

所有视图部分都可以正常工作,就像所有列表视图项都显示在列表中一样,当我们单击列表项时,上下文菜单会显示出来。但是唯一的问题是,即使我通过LockCommand将其绑定到上下文菜单项,单击菜单项时ExecuteLockCommad方法也不会执行。

3 个答案:

答案 0 :(得分:2)

您的代码遇到的问题是DataContext中的ContextMenu
您当前的ConextMenu绑定将从当前上下文中解析命令。由于ContextMenu是按ListViewItem分配的,因此DataContext就是ListViewItem的内容。就您而言,StudyEntity
但是,这不是主要问题,绑定可以指向其他上下文。在您的情况下,该值必须为StudyVM。因为这就是您DataContext中的ListView,所以我们可以通过以下代码段将其指向:

Command="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=DataContext.LockCommand}"  

Article from MSDN about RelativeSource MarkupExtension
获取DataContext的另一种方法可以通过Name完成。 (我知道我问过你为什么要使用它,这就是为什么)

<ListView Name="ListView" ItemsSource="{Binding StudyList, Mode=TwoWay}" SelectedItem="{Binding SelectedStudy, Mode=TwoWay}">
    <ListView.Resources>
        <ContextMenu x:Key="ContextMenu">
            <MenuItem Header="Lock" Command="{Binding ElementName=ListView, Path=DataContext.LockCommand}"/>
        </ContextMenu>
    </ListView.Resources>
    ...
</ListView>  

这可以达到相同的结果,但是在WPF中使用名称有一个缺点(确切地说是x:Name)。 Article about memory leak

答案 1 :(得分:0)

我尝试更改并进一步简化您的代码...

MainWindow.xaml

<Window x:Class="WpfApp5.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp5"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>

    <Grid Margin="5,5,5,5">
        <ListView Name="lvSelectStudy"  >
            <!--ItemsSource="{Binding StudyList, Mode=TwoWay}" SelectedItem="{Binding SelectedStudy, Mode=TwoWay}"-->
            <ListView.Resources>
                <ContextMenu x:Key="ItemContextMenu">
                    <!--ItemsSource="{Binding StudyVM}"-->
                    <MenuItem Header="Lock" Command="{Binding LockCommand}"/>
                </ContextMenu>
            </ListView.Resources>

            <ListView.ItemContainerStyle>
                <Style TargetType="{x:Type ListViewItem}">
                    <Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}" />
                </Style>
            </ListView.ItemContainerStyle>
            <!--<ListView.View>
                <GridView>
                    <GridViewColumn Header="Status" DisplayMemberBinding="{Binding FullStatus}" Width="60"/>
                    <GridViewColumn Header="Patient Name" DisplayMemberBinding="{Binding FullName}" Width="350"/>
                </GridView>
            </ListView.View>-->
            <ListViewItem>Item1</ListViewItem>
            <ListViewItem>Item2</ListViewItem>
            <ListViewItem>Item3</ListViewItem>
            <ListViewItem>Item4</ListViewItem>
            <ListViewItem>Item5</ListViewItem>
        </ListView>
    </Grid>
</Window>

MainWindowViewModel.cs

using GalaSoft.MvvmLight.Command;

namespace WpfApp5
{
    public sealed class MainWindowViewModel : ViewModelBase
    {
        public RelayCommand LockCommand { get; set; }

        public MainWindowViewModel()
        {
            LockCommand = new RelayCommand(() => ExecuteLockCommand());
        }
        void ExecuteLockCommand()
        {
            //Some code to be execute when menu item clicked
        }
    }
}

如果在void ExecuteLockCommand()中放置一个断点,则在右键单击ListViewItem后会调用该断点... 请注意,在您的代码中,此方法的名称与ViewModel的构造函数中定义的名称不同。

答案 2 :(得分:0)

问题在于ContextMenu不在视觉树中,因此您基本上必须告诉Context菜单要使用的数据上下文。您可以如下所示通过容器的tag属性传递数据上下文。

<ListView Name="lvSelectStudy" Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}">
    <ListView.Resources>
        <ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource {RelativeSource Self}}">                                
            <MenuItem Header="Lock" Command="{Binding LockCommand}"/>
        </ContextMenu>
    </ListView.Resources>
    ...
</ListView>