WPF 从列表视图项触发视图模型命令

WPF firing a view model Command from a list view item

有一个 WPF MVVM 应用程序。在主视图上,我有一个元素列表,用 ListView.ItemTemplate 定义,因为我想有一个带有删除操作的上下文菜单。

Command 与视图分离并保存在 ViewModel DreamListingViewModel.

问题是在单击“删除”时我无法让它在 ViewModelk 上执行命令,因为上下文是项目的上下文,而不是项目容器。

我可以通过将上下文菜单定义移到列表视图元素之外以某种方式使其工作,但是当我打开上下文菜单时,它会闪烁,就好像它被调用了“20”次(我认为确实发生了,就像我在集合中有元素一样多次),无论如何,我需要一个干净的解决方案,我对 XAML.

非常不满意

我的视图如下所示:

  <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid Margin="0 5 0 5" Background="Transparent" Width="auto">

                        <Grid.ContextMenu>
                            <ContextMenu>
                                <MenuItem Header="Delete"
                                          Command="{Binding DeleteSelectedDream}" 
                                          CommandParameter="{Binding DeleteSelectedDream, 
                                                                     RelativeSource={RelativeSource 
                                                                     Mode=FindAncestor,
                                                                     AncestorType={x:Type viewmodels:DreamListingViewModel}}}"
                                    />
                            </ContextMenu>
                        </Grid.ContextMenu>
...

它是主要的 window 并在 App.cs 中的通用主机中初始化:

 public partial class App : Application
    {
        private readonly IHost _host;

        public App()
        {
            ...

            _host = Host.CreateDefaultBuilder().ConfigureServices(services =>
            {
                ...
                services.AddTransient<DreamListingViewModel>();
                services.AddSingleton((s) => new DreamListingView()
                {
                    DataContext = s.GetRequiredService<DreamListingViewModel>()
                });
                ...
            }).Build();

Command 和 CommandParameter 值是我一直在试验的值,但它不起作用

这是我的 ViewModel 的样子:

 internal class DreamListingViewModel : ViewModelBase
    {
        public ICommand DeleteSelectedDream{ get; }
 ...

最后,当命令被触发时,我需要传递显示菜单的当前元素。

所以,这就是我想要的:

  1. 用户用鼠标右键单击列表项 - 确定
  2. 看到带有删除条目的菜单 - 确定
  3. 单击“删除”时,命令 DeleteSelectedDream 以当前梦想(列表中的项目)作为参数被触发 - ERR

你的例子有点缺乏必要的信息,但我会尽力提供帮助。

首先您需要验证您确实绑定到您的视图模型。您使用的是 Prism 还是标准的 WPF?在视图的 code-behind 的构造函数中,将 DataContext 设置为 VM 的实例。

InitializeComponent(); 
this.DataContext = new DreamListingViewModel(); 

现在,您通过模式 'FindAncestor' 绑定到相对源,并且 AncestorType 设置为视图模型的类型。这通常是行不通的,因为视图模型自然不是 WPF 视图的 可视化树 的一部分。也许您的 ItemTemplate 以某种方式将其连接起来。在我的一个大型 WPF 应用程序中,我将 Telerik UI 用于 WPF 以及与您类似的方法,但是,我将上下文菜单的 DataContext 设置为 RelativeSource 设置为 Self 结合 Path 设置为 PlacementTarget.DataContext.

你不必在我的例子中使用所有的XAML,只需要观察我是怎么做的。将 'RadContextMenu' 与 'ContextMenu' 交换,忽略挪威语单词 - 此处仅使用您需要的内容:

<telerik:RadContextMenu x:Key="CanceledOperationsViewContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
                <MenuItem Header="{Binding PatientName}" IsEnabled="False" Style="{StaticResource ContextMenuHeading}" />
                <MenuItem Header="Gå til aktuell SomeAcme-liste" IsEnabled="{Binding IsValid}" Command="{Binding NavigateToListCommand}" />
                <MenuItem Header="Åpne protokoll..." Command="{Binding CommonFirstProtocolCommand, Mode=OneWay}" CommandParameter="{Binding}" />
                <MenuItem Header="Åpne Opr.spl.rapport...." Command="{Binding CommonFirstNurseReportCommand, Mode=OneWay}" CommandParameter="{Binding}" />
            </telerik:RadContextMenu>

在您的示例中它将是:

<ContextMenu x:Key="SomeContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
                <MenuItem Header="Delete" />
                Command="{Binding DeleteSelectedDream}" 
                                      CommandParameter="{Binding DeleteSelectedDream, 
                                                                 RelativeSource={RelativeSource 
                                                                 Mode=FindAncestor,
                                                                 AncestorType={x:Type ListViewItem}}}"
                                />
            </telerik:RadContextMenu>

现在我认为您正在使用 class ListViewItem https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.listviewitem?view=netframework-4.8 您可能需要在此处指定 DataContext.DeleteSelectedDream 以确保绑定到 ICommand 实现所在的 DataContext。

意外找到 this 答案,这基本上就是我需要的,只是添加了一个 CommandParameter 来发送项目,它就像魔术一样有效!

<ListView Name="lvDreams" ItemsSource="{Binding Dreams}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid Margin="0 5 0 5" Background="Transparent" Width="auto">

                <Grid.ContextMenu>
                    <ContextMenu>
                        <MenuItem 
                            Header="Delete"
                            Command="{Binding DataContext.DeleteSelectedDream, Source={x:Reference lvDreams}}"
                            CommandParameter="{Binding}"
                        />
                    </ContextMenu>
                </Grid.ContextMenu>

                ...

        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>