WPF MVVM模式下ListView取消选中时SelectionChanged未触发问题
问题分析与解决方案
首先明确:SelectionChanged事件在取消选中项时是会触发的,你的问题出在参数传递和处理逻辑上——之前的实现只能拿到当前选中的项集合,无法获取被取消选中的项,且未处理移除逻辑。
方案一:双向绑定SelectedItems(MVVM推荐方案)
WPF原生ListView.SelectedItems是只读集合,无法直接双向绑定,我们可以通过附加属性实现双向同步:
1. 创建附加属性类
public class ListViewSelectedItemsBehavior { public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached( "SelectedItems", typeof(IList), typeof(ListViewSelectedItemsBehavior), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged)); public static IList GetSelectedItems(DependencyObject obj) { return (IList)obj.GetValue(SelectedItemsProperty); } public static void SetSelectedItems(DependencyObject obj, IList value) { obj.SetValue(SelectedItemsProperty, value); } private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is ListView listView) { listView.SelectionChanged -= OnListViewSelectionChanged; if (e.NewValue is IList newSelectedItems) { listView.SelectionChanged += OnListViewSelectionChanged; SyncSelectedItems(listView, newSelectedItems); } } } private static void OnListViewSelectionChanged(object sender, SelectionChangedEventArgs e) { if (sender is ListView listView) { var boundSelectedItems = GetSelectedItems(listView); if (boundSelectedItems == null) return; // 移除取消选中的项 foreach (var item in e.RemovedItems) { if (boundSelectedItems.Contains(item)) { boundSelectedItems.Remove(item); } } // 添加新选中的项 foreach (var item in e.AddedItems) { if (!boundSelectedItems.Contains(item)) { boundSelectedItems.Add(item); } } } } private static void SyncSelectedItems(ListView listView, IList selectedItems) { listView.SelectedItems.Clear(); foreach (var item in selectedItems) { listView.SelectedItems.Add(item); } } }
2. 修改XAML绑定
<ListView x:Name="myListView" SelectionMode="Extended" local:ListViewSelectedItemsBehavior.SelectedItems="{Binding SelectedItems}"> <ListView.View> <GridView> <GridViewColumn Header="Nombre" DisplayMemberBinding="{Binding FileName}" /> <GridViewColumn Header="Dirección" DisplayMemberBinding="{Binding FilePath}" /> </GridView> </ListView.View> </ListView>
3. 简化ViewModel
无需额外命令,直接维护SelectedItems即可:
private ObservableCollection<FileItem> _selectedItems = new ObservableCollection<FileItem>(); public ObservableCollection<FileItem> SelectedItems { get { return _selectedItems; } set { SetProperty(ref _selectedItems, value); } }
方案二:修改事件绑定,获取取消选中项
如果想继续使用Interaction.Triggers,可以传递事件参数直接拿到RemovedItems:
1. 修改XAML
<ListView x:Name="myListView" SelectionMode="Extended"> <i:Interaction.Triggers> <i:EventTrigger EventName="SelectionChanged"> <i:InvokeCommandAction Command="{Binding SelectionChangedCommand}" PassEventArgsToCommand="True" /> </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>
2. 修改ViewModel命令
private DelegateCommand<SelectionChangedEventArgs> _selectionChangedCommand; public DelegateCommand<SelectionChangedEventArgs> SelectionChangedCommand => _selectionChangedCommand ?? (_selectionChangedCommand = new DelegateCommand<SelectionChangedEventArgs>(ExecuteSelectionChanged)); private void ExecuteSelectionChanged(SelectionChangedEventArgs e) { // 处理取消选中的项 foreach (var item in e.RemovedItems.Cast<FileItem>()) { if (SelectedItems.Contains(item)) { SelectedItems.Remove(item); } } // 处理新选中的项 foreach (var item in e.AddedItems.Cast<FileItem>()) { if (!SelectedItems.Contains(item)) { SelectedItems.Add(item); } } }
问题根源总结
- 之前的
CommandParameter传递的是SelectedItems,这是更新后的选中集合,无法获取被取消的项; - 原
ExecuteGetSelectedItems方法只做添加操作,未处理移除逻辑,导致ViewModel选中集合与实际状态脱节。
内容的提问来源于stack exchange,提问作者c3n74ur0
相关产品推荐
相关产品推荐

