无法修改现有Car模型IEnumerable属性,如何实现WPF UI自动更新?
刚好之前处理过类似的WPF绑定问题,给你整理几个不用修改IEnumerable<Person>属性签名的可行方案,都能解决UI不更新的问题:
方案1:内部改用ObservableCollection存储,对外暴露IEnumerable接口
这是最实用的方案,完全不改动公共属性的定义,只调整私有字段的类型,同时给外部提供操作集合的方法:
public class Car : ModelBase { private string _model; public string Model { get { return _model; } set { this.Update(x => x.Model, () => _model= value, _model, value); } } // 内部换成ObservableCollection,自带INotifyCollectionChanged实现 private ObservableCollection<Person> _allowedDrivers; public IEnumerable<Person> AllowedDrivers { get { return _allowedDrivers; } set { // 赋值时把传入的IEnumerable转成ObservableCollection this.Update(x => x.AllowedDrivers, () => _allowedDrivers = new ObservableCollection<Person>(value ?? Enumerable.Empty<Person>()), _allowedDrivers, value); } } // 给外部提供添加/删除司机的方法,直接操作内部集合 public void AddAllowedDriver(Person driver) { if (driver != null && !_allowedDrivers.Contains(driver)) { _allowedDrivers.Add(driver); } } public void RemoveAllowedDriver(Person driver) { if (driver != null) { _allowedDrivers.Remove(driver); } } }
原理
WPF的绑定系统会“穿透”IEnumerable接口,检查实际的集合实例是否实现了INotifyCollectionChanged——而ObservableCollection刚好自带这个实现。所以哪怕你对外暴露的是IEnumerable,只要内部存的是ObservableCollection,集合元素的增删就会自动触发UI更新。外部代码通过你提供的方法修改集合,就能实时同步到UI上。
方案2:用BindingList替代ObservableCollection(适合特定场景)
如果你的场景需要更传统的集合通知(比如支持元素属性变化的自动通知),可以换成BindingList<T>,它同样能被WPF识别:
public class Car : ModelBase { private string _model; public string Model { get { return _model; } set { this.Update(x => x.Model, () => _model= value, _model, value); } } private BindingList<Person> _allowedDrivers; public IEnumerable<Person> AllowedDrivers { get { return _allowedDrivers; } set { this.Update(x => x.AllowedDrivers, () => _allowedDrivers = new BindingList<Person>(value ?? Enumerable.Empty<Person>()), _allowedDrivers, value); } } public void AddAllowedDriver(Person driver) { if (driver != null && !_allowedDrivers.Contains(driver)) { _allowedDrivers.Add(driver); } } public void RemoveAllowedDriver(Person driver) { if (driver != null) { _allowedDrivers.Remove(driver); } } }
BindingList的好处是默认会在元素属性变化时触发通知(只要Person实现了INotifyPropertyChanged),但在纯WPF场景下,ObservableCollection的使用频率更高。
方案3:手动触发属性变更(极端场景的临时方案)
如果因为某些限制连私有字段都不能改,那只能用这种“笨办法”——每次修改集合后重新生成集合引用,并手动触发PropertyChanged事件:
public class Car : ModelBase { private string _model; public string Model { get { return _model; } set { this.Update(x => x.Model, () => _model= value, _model, value); } } private IEnumerable<Person> _allowedDrivers; public IEnumerable<Person> AllowedDrivers { get { return _allowedDrivers; } set { this.Update(x => x.AllowedDrivers, () => _allowedDrivers=value, _allowedDrivers, value); } } public void AddAllowedDriver(Person driver) { if (driver != null && !_allowedDrivers.Contains(driver)) { // 重新生成集合,触发属性变更通知 _allowedDrivers = _allowedDrivers.Concat(new[] { driver }).ToList(); OnPropertyChanged(nameof(AllowedDrivers)); // 假设ModelBase实现了INotifyPropertyChanged } } public void RemoveAllowedDriver(Person driver) { if (driver != null) { _allowedDrivers = _allowedDrivers.Where(d => d != driver).ToList(); OnPropertyChanged(nameof(AllowedDrivers)); } } }
注意
这种方式每次修改都会创建新的集合实例,大数据量场景下性能会受影响,而且UI会整体刷新集合,不是局部更新,所以只推荐作为临时解决方案。
方案4:用CollectionViewSource增强绑定(配合方案1/2使用)
在XAML里用CollectionViewSource包装你的集合绑定,能让WPF更好地处理集合变化,同时还能添加排序、过滤等功能:
<Window.Resources> <CollectionViewSource x:Key="AllowedDriversView" Source="{Binding YourCarInstance.AllowedDrivers}"/> </Window.Resources> <!-- 绑定到CollectionViewSource而不是直接绑定集合 --> <ListBox ItemsSource="{Binding Source={StaticResource AllowedDriversView}}"/>
这个方案需要配合方案1或方案2使用,因为它本身不会让普通IEnumerable具备变更通知能力,只是优化了WPF对可通知集合的绑定处理。
总结
最推荐方案1,它既保留了原有的IEnumerable属性签名,又完美利用了ObservableCollection的自动变更通知能力,完全适配WPF的绑定机制,性能和体验都最好。
内容的提问来源于stack exchange,提问作者user1702369

