在ViewModel中获取WPF ListView的选定项

15
有些帖子讨论为 ListView.SelectedItems 添加数据绑定能力,需要大量的代码。在我的情况下,我不需要从 ViewModel 设置它,只需获取所选项目以便对其执行操作,并且它是由命令触发的,因此不需要推送更新。

是否有简单的解决方案(以代码行数为单位),也许在代码后台? 只要 View 和 ViewModel 不需要相互引用,我就可以使用代码后台。 我认为这是一个更通用的问题:“ VM按需从视图获取数据的最佳实践”,但我似乎找不到任何东西...

5个回答

28

当仅在执行命令时获取所选项 (SelectedItems) 时,可以使用 CommandParameter 并传入 ListView.SelectedItems

<ListBox x:Name="listbox" ItemsSource="{Binding StringList}" SelectionMode="Multiple"/>
<Button Command="{Binding GetListItemsCommand}" CommandParameter="{Binding SelectedItems, ElementName=listbox}" Content="GetSelectedListBoxItems"/>

9
SelectedItems(复数)不支持数据绑定。请参见此链接此链接。即使在使用CommandParameter时也无法正常工作,我始终获得null,而使用SelectedItem(单数)就可以。 - NS.X.
@user986080 我没有意识到 SelectedItems 不支持绑定。我已经从答案中删除了它。然而,CommandParameter 是有效的,我已经测试过并且能够获取所选项目的列表。 - evanb
我认为我遇到了这个CommandParameter bug,这很麻烦需要解决。但是理想情况下,您的答案是正确的方法。 - NS.X.
非常感谢您提供这个CommandParameter解决方案。对我非常有帮助。 - WAQ
GetListItemsCommand是在ViewModel中定义的命令时会发生什么?第一个绑定使用ViewModel作为DataContext,而第二个绑定到参数则使用自身作为DataContext... - rhcpfan
显示剩余2条评论

11

可以通过以下交互触发器来实现:

  1. 您需要添加对以下内容的引用:

    Microsoft.Expression.Interactions System.Windows.Interactivity

将以下xmlns添加到您的xaml中

xmlns:i="http://schemas.microsoft.com/expression//2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

请在您的GridView标签内添加以下代码

<GridView x:Name="GridName">
<i:Interaction.Triggers>
   <i:EventTrigger EventName="SelectionChanged">
       <i:InvokeCommandAction Command="{Binding Datacontext.SelectionChangedCommand, ElementName=YourUserControlName}" CommandParameter="{Binding SelectedItems, ElementName=GridName}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

在 ViewModel 中声明以下属性的代码:

public DelegateCommand<object> SelectionChangedCommand {get;set;}

在 Viewmodel 的构造函数中,将 Command 初始化如下:

SelectionChangedCommand = new DelegateCommand<object> (items => {
   var itemList = (items as ObservableCollection<object>).Cast<YourDto>().ToList();
}

4
我不认为考虑“视图和ViewModel不需要相互了解”的条件是正确的;在MVVM中,视图始终知道ViewModel。
我也遇到过这种情况,在视图的代码后台中访问ViewModel,然后填充一些数据(如选定的项目),这在使用第三方控件(如ListView、DataGrid等)时变得必要。
如果直接绑定VM属性不可行,则会侦听ListViw.SelectionChanged事件,然后在该事件中更新ViewModel的SelectedItems属性。
更新:为了使VM从视图中获取数据,您可以在视图上公开一个接口,处理视图特定的功能,而ViewModel将通过该接口引用您的视图;使用接口仍然使视图和ViewModel大体上解耦,但我通常不喜欢这样做。 MVVM,提供View到ViewModel的关联 我仍然更喜欢在视图中处理事件并保持VM更新(选定的项目),这样VM不需要担心在执行任何操作之前拉取数据,它只需要使用可用的数据(因为那总是更新的)。

抱歉我表达不够清晰。所谓彼此不了解是指它们之间没有相互引用。 另外,注册 SelectionChanged 事件并不是必须的,因为 ViewModel 只需要在执行命令时获取选定项。这更像是“VM如何按需从View中拉取数据”。 - NS.X.

3
我可以向您保证: SelectedItems 可以作为 XAML 的 CommandParameter 进行绑定。
在大量的查找和谷歌搜索之后,我终于找到了一个简单的解决方案来解决这个常见的问题。
要使其工作,您必须遵循以下所有规则
  1. Following Ed Ball's suggestion', on you XAML command databinding, define CommandParameter property BEFORE Command property. This a very time-consuming bug.

  2. Make sure your ICommand's CanExecute and Execute methods have a parameter of object type. This way you can prevent silenced cast exceptions that occurs whenever databinding CommandParameter type does not match your command method's parameter type.

    private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)  
    {
        // Your goes heres
    }
    
    private bool OnDeleteSelectedItemsExecute(object SelectedItems)  
    {
        // Your goes heres
    }
    
例如,您可以将列表视图/列表框的 SelectedItems 属性发送到您的 ICommand 方法或列表视图/列表框本身。很棒,不是吗?
希望它能防止有人像我一样花费大量时间来弄清楚如何将 SelectedItems 接收为 CanExecute 参数。

0

由于其他答案都没有帮助到我(使用SelectedItems作为CommandParameter始终为null),这里提供了一种适用于通用 Windows 平台 (UWP) 应用程序的解决方案。它使用了Microsoft.Xaml.InteractivityMicrosoft.Xaml.Interactions.Core

以下是视图:

<ListView x:Name="ItemsList">
    <Interactivity:Interaction.Behaviors>
         <Core:EventTriggerBehavior EventName="SelectionChanged">
             <Core:InvokeCommandAction Command="{x:Bind ViewModel.SelectedItemsChanged}" />
         </Core:EventTriggerBehavior>
    </Interactivity:Interaction.Behaviors>
    <!-- content etc. -->
</ListView>

这是 ViewModel(RelayCommand 是 MVVM Light 中的一个类):
private List<YourType> _selectedItems = new List<YourType>();

private RelayCommand<SelectionChangedEventArgs> _selectedItemsChanged;
public RelayCommand<SelectionChangedEventArgs> SelectedItemsChanged
{
    get
    {
        if (_selectedItemsChanged == null)
            _selectedItemsChanged = new RelayCommand<SelectionChangedEventArgs>((selectionChangedArgs) =>
            {
                // add a guard here to immediatelly return if you are modifying the original collection from code

                foreach (var item in selectionChangedArgs.AddedItems)
                    _selectedItems.Add((YourType)item);

                foreach (var item in selectionChangedArgs.RemovedItems)
                    _selectedItems.Remove((YourType)item);
            });
        return _selectedItemsChanged;
    }
}

请注意,如果您在选择完成后(用户按下按钮等)从原始集合中删除项目,则还将从您的_selectedItems列表中删除这些项目! 如果您在foreach循环中执行此操作,则会收到InvalidOperationException。 为避免出现此问题,请在标记位置添加保护,例如:
if (_deletingItems)
    return;

在编程中,当你需要移除某些项目时,在相应的方法中,可以这样做:

然后在你移除项目的方法中,执行以下操作:

_deletingItems = true;
foreach (var item in _selectedItems)
    YourOriginalCollection.Remove(item);
_deletingItems = false;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接