ListView 项目 SelectionChanged 在取消选择时不会触发,使用带有 MVVM 模式的 WPF

问题描述 投票:0回答:1

我有一个

ListView
,它可以正确地将
SelectionChanged
上选定的项目发送到我的
ViewModel
。当选择一个或多个项目时,
SelectionChanged
属性会发送选定的项目。但是当取消选择一个或多个项目时,不会触发
SelectionChanged
属性。

这是我所做的:

XAML

<ListView 
  x:Name="myListView"
  SelectionMode="Extended">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding GetSelectedItemsCommand}"  CommandParameter="{Binding ElementName=myListView, Path=SelectedItems}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Nombre" DisplayMemberBinding="{Binding FileName}" />
            <GridViewColumn Header="Dirección" DisplayMemberBinding="{Binding FilePath}" />
        </GridView>
    </ListView.View>
</ListView>

视图模型

private ObservableCollection<FileItem> _selectedItems = new ObservableCollection<FileItem>();

public ObservableCollection<FileItem> SelectedItems
{
   get { return _selectedItems; }
   set { SetProperty(ref _selectedItems, value); }
}
private DelegateCommand<ObservableCollection<Object>> _getSelectedItems;

public DelegateCommand<ObservableCollection<Object>> GetSelectedItemsCommand =>
    _getSelectedItems ?? (_getSelectedItems = new DelegateCommand<ObservableCollection<Object>>(ExecuteGetSelectedItems));

private void ExecuteGetSelectedItems(ObservableCollection<Object> fileItems)
{
    foreach (var file in fileItems)
    {
        FileItem fileItem = (FileItem)file;
        if (fileItem != null && !SelectedItems.Contains(file))
        {
            SelectedItems.Add(fileItem);
        }
    }
}

我需要一种方法来获取未选择的项目,因为

SelectionChanged
属性在取消选择时不起作用。

c# .net wpf mvvm
1个回答
0
投票

您不应该在视图模型中处理 UI 事件数据。

Interaction
行为通常会导致代码异味,违反 MVVM 设计规则。它只是一个多余的帮手。
解决方案很简单:处理
SelectionChanged
事件并使用事件数据获取选定和未选定的项目。从这里你有多个好的解决方案:

  1. 通过调用API方法将数据直接传递给视图模型类实例(视图模型类型不再是匿名的)
  2. 调用绑定到当前元素的
    ICommand
    实现并将数据作为命令参数传递
  3. 更新绑定到数据源的本地依赖属性

以下示例使用附加属性来允许注册将在

ICommand
上调用的
Selector.SelectionChanged

为此使用附加属性可以最大限度地提高灵活性,因为它使每个
SelctionChanged
源元素能够定义自己的
ICommand
,而无需为每个元素实现新的依赖属性:

DataChanges.cs
数据参数用于与视图模型交换数据。这是可选的。它只会让数据处理更加方便。

class DataChanges<TItem>
{
  public DataChanges(TItem oldValue, TItem newValue)
  {
    this.OldValue = oldValue;
    this.NewValue = newItems;
  }

  public TItem NewValue { get; }
  public TItem OldValue { get; }
}

ViewModel.cs

class ViewModel : INotifyPropertyChanged
{
  public DelegateCommand<DataChanges<IEnumerable<FileItem>>> ProcessSelectedItemsCommand { get; }
    = new DelegateCommand<DataChanges<IEnumerable<FileItem>>>(ExecuteProcessSelectedItemsCommand));

  private void ExecuteProcessSelectedItemsCommand(DataChanges<IEnumerable<FileItem>> dataChanges)
  {
    RemoveUnselectedFileItems(dataChanges.OldValue);
    AddSelectedFileItems(dataChanges.NewValue);
  }

  private void AddSelectedFileItems(IEnumerable<FileItem> itemsToAdd)
  {
    // Use LINQ to filter duplicates
    var newItemsExcludingExistingItems = itemsToAdd.Except(this.SelectedItems);
    foreach (FileItem newFileItem in newItemsExcludingExistingItems)
    {
      this.SelectedItems.Add(newFileItem);
    }
  }

  private void RemoveUnselectedFileItems(IEnumerable<FileItem> itemsToRemove)
  {
    foreach (FileItem oldFileItem in itemsToRemove)
    {
      this.SelectedItems.Remove(oldFileItem);
    }
  }
}

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public static void SetFileItemsChangedCommande(UIElement attachingElement, ICommand value)
    => attachingElement.SetValue(FileItemsChangedCommandProperty, value);

  public static ICommand GetFileItemsChangedCommande(UIElement attachingElement)
    => return (ICommand)attachingElement.GetValue(FileItemsChangedCommandProperty);

  public static readonly DependencyProperty FileItemsChangedCommandProperty = DependencyProperty.RegisterAttached(
    "FileItemsChangedCommand",
    typeof(ICommand),
    typeof(MainWindow),
    new PropertyMetadata(default));

  public MainWindow()
  {
    InitializeComponent();

    this.DataContext = new ViewModel();
  }

  private void OnFileItemsSelectionChanged(object sender, SelectionChangedEventArgs e)
  {
    var sourceElement = (UIElement)sender;
    ICommand command = GetFileItemsChangedCommand(sourceElement);

    IEnumerable<FileItem> oldFileItems = e.RemovedItems.Cast<FileItem>();
    IEnumerable<FileItem> newFileItems = e.AddedItems.Cast<FileItem>();
    var commandArgument = new DataChanges<IEnumerable<FileItem>>(oldFileItems, newFileItems);
    if (command?.CanExecute(commandArgument) ?? false)
    {
      command. Execute(commandArgument);
    }
  }
}

MainWindow.xaml

<Window>
  <ListBox MainWindow.FileItemsChangedCommand="{Binding ProcessSelectedItemsCommand}"
           SelectionChanged="OnFileItemsSelectionChanged" />
</Window>
© www.soinside.com 2019 - 2024. All rights reserved.