You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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压力。

修复方案

  1. 首先修复Bs属性的笔误,set访问器正确传入_bs字段:
private ObservableCollection<B> _bs;
public ObservableCollection<B> Bs
{
    get => _bs;
    set { SetProperty(ref _bs, value); }
}
  1. 监听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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 04:36:14