【WPF】从普通 ItemsControl 到支持筛选的 ItemsControl:深入掌握 CollectionViewSource 用法
✨ 从普通 ItemsControl 到支持筛选的 ItemsControl:深入掌握 CollectionViewSource 用法
在日常 WPF 开发中,我们经常需要对数据进行筛选、排序、分组等操作,而原生的 ItemsControl
并不直接支持这些功能。本文将介绍如何通过 CollectionViewSource
给一个普通的 ItemsControl
加上 动态筛选功能,并指出其中的一些关键注意点。
📦 一、初始结构:普通 ItemsControl 显示列表
我们从一个最简单的 ItemsControl
开始:
<ItemsControl ItemsSource="{Binding MBConfigList}"><ItemsControl.ItemTemplate><DataTemplate><TextBlock Text="{Binding FileName}" /></DataTemplate></ItemsControl.ItemTemplate>
</ItemsControl>
这个控件可以显示绑定的 MBConfigList
数据,但没有任何过滤能力。
🛠️ 二、目标:支持按光源类型筛选
我们希望最终能够让用户勾选光源类型,动态筛选出指定类型的数据。例如:
- 支持“全选”
- 多个
CheckBox
动态刷新列表 - 用户操作后立即生效
🧰 三、改造步骤
1️⃣ 引入 CollectionViewSource
我们在 ViewModel 中定义一个 CollectionViewSource
并设置过滤事件:
private CollectionViewSource _viewSource;public ICollectionView FilteredMBConfigList
{get => _viewSource?.View;private set => SetProperty(ref _filteredView, value); // 实现 INotifyPropertyChanged
}public void InitializeFilteredView()
{_viewSource = new CollectionViewSource{Source = GlobalData.Instance.saveInfo.MBConfigList};_viewSource.Filter += ApplyFilter;FilteredMBConfigList = _viewSource.View;
}
FilteredMBConfigList 必须是支持通知的不然界面是不会变化的。
2️⃣ 编写筛选逻辑 ApplyFilter
private void ApplyFilter(object sender, FilterEventArgs e)
{if (e.Item is MBConfigInfo item){var selectedTypes = LightSourceItems.Where(x => x.IsChecked).Select(x => x.Name).ToHashSet();//ToHashSet()是为了更快的查询// 显示所有或匹配项e.Accepted = selectedTypes.Count == 0 || selectedTypes.Contains(item.LightSourceType);}
}
private CollectionViewSource _viewSource; 不能申明成局部变量,不然ApplyFilter只能生效一次。
e.Accepted == true 的选项才会被显示出来。
3️⃣ UI 绑定到新集合
<ItemsControl ItemsSource="{Binding FilteredMBConfigList}"><ItemsControl.ItemTemplate><DataTemplate><TextBlock Text="{Binding FileName}" /></DataTemplate></ItemsControl.ItemTemplate>
</ItemsControl>
这样,控件就用上了支持筛选的视图。
🚨 四、关键注意事项
⚠️ 1. CollectionViewSource 必须缓存
不能每次 get
都创建新的 CollectionViewSource
,否则不会触发 .Refresh()
。
// 错误写法(每次都 new):
public ICollectionView Filtered => new CollectionViewSource { Source = xxx }.View;// 正确:只创建一次
⚠️ 2. 调用 View.Refresh() 以应用筛选
数据变化时,你必须手动调用:
FilteredMBConfigList.Refresh();
建议绑定项(如 CheckBox
)中 PropertyChanged
事件或命令中执行刷新。
FilteredMBConfigList.Refresh();调用后会重新触发ApplyFilter 以到达筛选的目的。
⚠️ 3. 筛选逻辑不要复杂耗时
FilterEventArgs
的处理函数会在每次刷新时对 所有项 执行。避免长计算!
⚠️ 4. UI 绑定的是 View,不是 CollectionViewSource 本身
绑定语法是:
ItemsSource="{Binding FilteredMBConfigList}"
不是 _viewSource
,而是其 .View
。
✅ 五、示例:支持多选筛选项结构
使用一个简单的模型:
public class LightSourceFilterItem : INotifyPropertyChanged
{public string Name { get; }public bool IsChecked { get; set; } // 实现通知
}
ViewModel:
public ObservableCollection<LightSourceFilterItem> LightSourceItems { get; set; }LightSourceItems = new ObservableCollection<LightSourceFilterItem>(new[] { "上光源", "下光源", "侧光源" }.Select(name =>{var item = new LightSourceFilterItem(name) { IsChecked = true };item.PropertyChanged += (_, __) => FilteredMBConfigList?.Refresh();return item;}));
XAML:
<ItemsControl ItemsSource="{Binding LightSourceItems}"><ItemsControl.ItemTemplate><DataTemplate><CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked, Mode=TwoWay}" /></DataTemplate></ItemsControl.ItemTemplate>
</ItemsControl>
🏁 六、总结
优点 ✅ | 注意点 ⚠️ |
---|---|
支持筛选、排序、分组 | .Refresh() 要手动调用 |
与 ItemsControl , ListBox , DataGrid 搭配无缝 | Filter 逻辑要快 |
不改变原始集合 | 不要频繁 new CollectionViewSource |
使用 CollectionViewSource
是 WPF 中实现数据视图分离的经典方式,非常适合构建支持过滤的 UI。