两个ListView选中/取消选中记录的数据绑定问题
解决WPF ListView多选同步到第二个列表的问题
原方案的核心问题是误用了SelectedItem属性——这是为单选场景设计的,当用户取消选中所有项时,SelectedItem会被设为null,导致向第二个列表添加空值;同时它无法跟踪多个选中项的状态变化,自然没法正确移除取消选中的项。以下是更优雅的MVVM解决方案:
1. 创建项包装ViewModel
定义一个ItemViewModel,封装每个列表项的文本和选中状态,让选中状态的变化能被ViewModel监听:
public class ItemViewModel : INotifyPropertyChanged { private string _text; public string Text { get => _text; set { _text = value; OnPropertyChanged(nameof(Text)); } } private bool _isSelected; public bool IsSelected { get => _isSelected; set { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
2. 修改主ViewModel
将原有的字符串集合替换为ItemViewModel集合,监听集合项的增减和选中状态变化,自动同步第二个列表:
private ObservableCollection<ItemViewModel> _firstList; public ObservableCollection<ItemViewModel> FirstList { get => _firstList; set { if (_firstList != value) { // 移除旧集合的事件监听 if (_firstList != null) { foreach (var item in _firstList) item.PropertyChanged -= Item_PropertyChanged; _firstList.CollectionChanged -= FirstList_CollectionChanged; } _firstList = value; // 添加新集合的事件监听 if (_firstList != null) { foreach (var item in _firstList) item.PropertyChanged += Item_PropertyChanged; _firstList.CollectionChanged += FirstList_CollectionChanged; } RaisePropertyChanged(nameof(FirstList)); SyncSecondList(); } } } private ObservableCollection<string> _secondList = new ObservableCollection<string>(); public ObservableCollection<string> SecondList { get => _secondList; set { if (_secondList != value) { _secondList = value; RaisePropertyChanged(nameof(SecondList)); } } } // 监听集合项的增减 private void FirstList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) foreach (ItemViewModel item in e.NewItems) item.PropertyChanged += Item_PropertyChanged; if (e.OldItems != null) foreach (ItemViewModel item in e.OldItems) item.PropertyChanged -= Item_PropertyChanged; SyncSecondList(); } // 监听项的选中状态变化 private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(ItemViewModel.IsSelected)) SyncSecondList(); } // 同步选中项到第二个列表 private void SyncSecondList() { var selectedTexts = FirstList?.Where(x => x.IsSelected).Select(x => x.Text).ToList() ?? new List<string>(); var currentTexts = SecondList.ToList(); // 移除不再选中的项 foreach (var text in currentTexts.Except(selectedTexts)) SecondList.Remove(text); // 添加新选中的项 foreach (var text in selectedTexts.Except(currentTexts)) SecondList.Add(text); }
3. 更新XAML绑定
修改第一个ListView的绑定,直接关联ItemViewModel的属性,无需依赖ListViewItem的选中状态:
<!-- 第一个列表:带复选框的多选列表 --> <ListView ItemsSource="{Binding FirstList}"> <ListView.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Vertical"/> </ItemsPanelTemplate> </ListView.ItemsPanel> <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <CheckBox IsChecked="{Binding IsSelected}" Margin="0,0,5,0"/> <TextBlock Text="{Binding Text}"/> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> <!-- 第二个列表:显示选中项 --> <ListView ItemsSource="{Binding SecondList}"> <ListView.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Vertical"/> </ItemsPanelTemplate> </ListView.ItemsPanel> <ListView.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding}"/> </DataTemplate> </ListView.ItemTemplate> </ListView>
可选优化:用CollectionViewSource直接过滤
如果不需要维护独立的SecondList集合,可以用CollectionViewSource直接过滤选中项,实现更简洁:
修改ViewModel
private ObservableCollection<ItemViewModel> _firstList; public ObservableCollection<ItemViewModel> FirstList { get => _firstList; set { _firstList = value; RaisePropertyChanged(nameof(FirstList)); RaisePropertyChanged(nameof(SelectedItemsView)); } } // 过滤后的选中项视图 public ICollectionView SelectedItemsView { get { var view = CollectionViewSource.GetDefaultView(FirstList); view.Filter = item => ((ItemViewModel)item).IsSelected; return view; } }
修改XAML
第二个ListView直接绑定过滤视图:
<ListView ItemsSource="{Binding SelectedItemsView}"> <ListView.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Vertical"/> </ItemsPanelTemplate> </ListView.ItemsPanel> <ListView.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Text}"/> </DataTemplate> </ListView.ItemTemplate> </ListView>
内容的提问来源于stack exchange,提问作者KarkMump
相关产品推荐
相关产品推荐

