为何WPF DataGrid的ItemsSource会忽略绑定集合自定义的GetEnumerator()方法
问题核心原因
- WPF的ItemsControl系列控件(包含DataGrid)绑定
ItemsSource时,不会直接操作你传入的原始集合对象:系统会自动将原始集合包装为ICollectionView类型的视图层对象,所有控件读取数据、监听变更的操作都针对这个包装后的视图,而非你的自定义集合本身。 - 针对实现了
IList接口的集合,默认生成的ListCollectionView为了性能优化,优先通过IList的索引器和Count属性读取数据,只有当集合未实现IList时才会调用GetEnumerator()枚举全量元素。你继承了List<T>天然实现了IList接口,所以控件读取数据时根本不会触发你重写的枚举逻辑,自然过滤规则不生效。 - 你发送的
NotifyCollectionChangedAction.Reset通知只会触发视图重新从原始集合拉取数据,但因为视图拉取时走的是索引器而非枚举器,拿到的还是基类List<T>的全量原始数据,所以界面不会有任何变化。
可行的解决方案
方案1:仅保留枚举接口(不推荐)
如果你坚持要通过注入枚举逻辑实现过滤,可以修改自定义集合的实现:
- 不要继承
List<T>,改为将List<T>作为内部私有字段存储原始数据 - 仅实现
IEnumerable<T>、INotifyCollectionChanged、INotifyPropertyChanged三个接口,不要实现IList、ICollection接口
这种情况下视图找不到索引器,只能通过GetEnumerator()拿数据,你的过滤规则就会生效。但代价是所有UI虚拟化功能会失效,大数量集下性能会暴跌,不适合生产环境使用。
方案2:维护影子过滤集合(推荐)
和官方ListCollectionView的实现逻辑一致,内部额外维护一个存储过滤后结果的列表:
- 当
Filter属性变更、或者原始集合数据变更时,重新计算过滤后的结果更新影子列表 - 所有对外暴露的
IList索引器、Count属性、GetEnumerator()方法全部返回影子列表的对应内容
这种方案兼容IList的所有特性,支持UI虚拟化,性能开销极低,只需要在刷新时做一次过滤计算,是成熟的生产级实现方案。你担心的额外内存分配可以通过复用影子列表的存储空间优化,实际开销可以忽略不计。
方案3:直接使用原生集合视图过滤
不需要自己实现自定义集合的过滤逻辑,直接用WPF原生提供的ICollectionView能力即可:
// 绑定原始集合后,获取自动生成的默认视图 var view = CollectionViewSource.GetDefaultView(你的集合实例); // 设置过滤规则 view.Filter = obj => ((T)obj)符合你的过滤条件; // 刷新过滤 view.Refresh();
原生实现已经兼容了所有WPF控件的特性,不需要额外造轮子。
内容的提问来源于stack exchange,提问作者Harry
相关产品推荐
相关产品推荐

