Prism绑定无Setter的ObservableCollection时DataGrid不更新问题
XAML DataGrid绑定LINQ生成的ObservableCollection时UI不刷新问题
问题现象
项目定义A、B、C三个实体类,C类对应的ObservableCollection<C>需通过LINQ联合查询A、B类的集合数据生成。当通过移除项的方式更新ObservableCollection<A>时,断点调试可确认Cs属性返回结果已同步反映变更,但DataGrid UI始终不刷新显示最新数据。
相关代码
实体类定义
public class A { public int Id { get; set; } public string Name { get; set; } } public class B { public int Id { get; set; } public int Age { get; set; } } public class C { public string Name { get; set; } public int Age { get; set; } }
DataContext(ViewModel)原代码
private ObservableCollection<A> _as; public ObservableCollection<A> As { get => _as; set { SetProperty(ref _as, value); } } private ObservableCollection<B> _bs; public ObservableCollection<B> Bs { get => _bs; // 注意此处存在笔误 set { SetProperty(ref _as, value); } } public ObservableCollection<C> Cs { get { var query = from x in As join y in Bs on x.Id equals y.Id select new C() { Name = x.Name, Age = y.Age }; return new ObservableCollection<C>(query); } }
XAML绑定代码
<DataGrid ItemsSource="{Binding Cs}" Grid.Row="1"/>
问题根因
- 核心原因:
Cs是只读计算属性,没有在源集合变更时触发PropertyChanged通知。WPF绑定系统不会主动轮询属性值变化,只有收到对应属性的变更通知时,才会重新读取属性值更新UI。断点调试时手动访问Cs能拿到最新值,不代表UI收到了刷新信号。 - 代码笔误:
Bs属性的set访问器中,SetProperty传入的是_as字段而非_bs字段,给Bs赋值时会错误修改As集合的引用,本身就会导致数据逻辑混乱。 - 设计缺陷:每次访问
Cs的get方法都会生成全新的ObservableCollection<C>实例,就算触发了属性通知,也会导致DataGrid丢失选中状态、滚动位置等交互信息,同时产生不必要的GC压力。
修复方案
- 首先修复
Bs属性的笔误,set访问器正确传入_bs字段:
private ObservableCollection<B> _bs; public ObservableCollection<B> Bs { get => _bs; set { SetProperty(ref _bs, value); } }
- 监听
As、Bs两个源集合的CollectionChanged事件,在集合内容增删改时同步刷新Cs的数据,同时触发必要的变更通知。根据业务场景二选一即可:
方案1:维护固定Cs集合实例(推荐,无交互状态丢失)
初始化时就创建Cs集合实例,源集合变更时清空Cs重新填充查询结果,全程不更换Cs的实例引用,不会丢失DataGrid的交互状态,性能更好。
完整参考实现:
public class MainViewModel : BindableBase // 基类需实现INotifyPropertyChanged接口 { public MainViewModel() { _as = new ObservableCollection<A>(); _bs = new ObservableCollection<B>(); // 给初始集合注册变更监听 As.CollectionChanged += SourceCollection_CollectionChanged; Bs.CollectionChanged += SourceCollection_CollectionChanged; // 初始化加载一次Cs数据 RefreshCsData(); } private void SourceCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { RefreshCsData(); } /// <summary> /// 重新查询填充Cs集合 /// </summary> private void RefreshCsData() { Cs.Clear(); var query = from a in As join b in Bs on a.Id equals b.Id select new C { Name = a.Name, Age = b.Age }; foreach (var item in query) { Cs.Add(item); } } private ObservableCollection<A> _as; public ObservableCollection<A> As { get => _as; set { // 移除旧集合的事件监听,避免内存泄漏 if (_as != null) _as.CollectionChanged -= SourceCollection_CollectionChanged; SetProperty(ref _as, value); // 给新集合注册事件监听 if (_as != null) _as.CollectionChanged += SourceCollection_CollectionChanged; RefreshCsData(); } } private ObservableCollection<B> _bs; public ObservableCollection<B> Bs { get => _bs; set { if (_bs != null) _bs.CollectionChanged -= SourceCollection_CollectionChanged; SetProperty(ref _bs, value); if (_bs != null) _bs.CollectionChanged += SourceCollection_CollectionChanged; RefreshCsData(); } } // Cs只读,初始化后不更换实例 public ObservableCollection<C> Cs { get; } = new ObservableCollection<C>(); }
方案2:触发Cs属性变更通知(实现简单,适合无交互状态场景)
如果不需要保留DataGrid的选中、滚动位置等状态,可以在源集合变更时主动触发Cs的属性变更通知,强制绑定系统重新读取Cs的值。注意该方案每次刷新都会生成新的集合实例,交互状态会丢失。
关键代码片段:
// 构造函数中注册集合监听 public MainViewModel() { _as = new ObservableCollection<A>(); _bs = new ObservableCollection<B>(); As.CollectionChanged += (s,e) => OnPropertyChanged(nameof(Cs)); Bs.CollectionChanged += (s,e) => OnPropertyChanged(nameof(Cs)); } public ObservableCollection<C> Cs { get { var query = from a in As join b in Bs on a.Id equals b.Id select new C { Name = a.Name, Age = b.Age }; return new ObservableCollection<C>(query); } } // 注意As和Bs的set方法中,更换集合实例后也要触发Cs的变更通知
内容的提问来源于stack exchange,提问作者chillydk147
相关产品推荐
相关产品推荐

