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

ObservableCollection更新后WPF ListView未同步问题求助

问题:定时Observable更新ObservableCollectionEx后WPF ListView不刷新,按钮点击却正常

我实现了继承自ObservableCollection<T>的ObservableCollectionEx类,用来在批量更新集合时抑制通知,直到更新完成后再触发一次Reset通知。现在在一个VSTO Word的CustomTaskPane对应的WPF UserControl中,有两种方式更新集合:一种是通过Rx的Observable.Interval定时触发,另一种是按钮点击触发。调试确认两种方式都成功更新了集合内容,但只有按钮点击能触发ListView的UI刷新。

ObservableCollectionEx 实现代码

public class ObservableCollectionEx<T> : ObservableCollection<T>
{
    private bool _notificationSupressed = false;
    private bool _supressNotification = false;
    public bool SupressNotification
    {
        get
        {
            return _supressNotification;
        }
        set
        {
            _supressNotification = value;
            if (_supressNotification == false && _notificationSupressed)
            {
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                _notificationSupressed = false;
            }
        }
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (SupressNotification)
        {
            _notificationSupressed = true;
            return;
        }
        base.OnCollectionChanged(e);
    }
}

定时更新相关代码

public partial class CrossReferenceControl : UserControl, ICrossReferenceControl
{
    private ICrossReferenceControlViewModel referenceControlViewModel;
    private IOpenDocumentModel OpenDocumentModel;
    private ICrossReferenceGuy CrossReferenceGuy;
    private bool isOpen;

    private static TimeSpan period = TimeSpan.FromSeconds(20);
    private IObservable<long> observable = Observable.Interval(period);

    public readonly Subject<bool> OpenDocModelUpdateActionSubject = new Subject<bool>();

    public ICrossReferenceControlViewModel ReferenceControlViewModel => referenceControlViewModel;
    public bool IsOpen 
    {
        get { return isOpen; }
        set { isOpen = value; }
    }

    public CrossReferenceControl(IOpenDocumentModel openDocumentModel, ICrossReferenceControlViewModel referenceControlViewModel, ICrossReferenceGuy crossReferenceGuy)
    {
        InitializeComponent();
        this.referenceControlViewModel = referenceControlViewModel;
        OpenDocumentModel = openDocumentModel;
        CrossReferenceGuy = crossReferenceGuy;
        DataContext = referenceControlViewModel;
        observable.Subscribe(O => OpenDocumentModel.UpdateCaptionsSubject.OnNext(IsOpen));
    }
}

按钮点击更新代码

private void ButtonRefresh_Click(object sender, RoutedEventArgs e)
{
    OpenDocumentModel.UpdateCaptionsSubject.OnNext(IsOpen);
}

问题原因分析

核心问题是Rx的Observable.Interval默认运行在后台线程,而WPF的UI绑定要求INotifyCollectionChanged通知必须在**Dispatcher线程(UI线程)**上触发。按钮点击事件本身就在UI线程执行,所以集合通知能被ListView正常接收;但定时任务的回调是在后台线程,此时更新集合后触发的OnCollectionChanged(包括Reset通知)属于跨线程操作,WPF绑定系统会忽略这类通知,导致UI不刷新。

另外需要确认:UpdateCaptionsSubject的订阅逻辑中,是否在更新ObservableCollectionEx时正确处理了线程上下文?如果订阅代码没有切换到UI线程,集合的更新和通知都会在后台线程执行,同样会导致UI无响应。


解决方案

方案1:在订阅Observable.Interval时切换到UI线程

修改定时任务的订阅代码,使用ObserveOnDispatcher(需引用System.Reactive.Windows.Threading包)将回调切换到UI线程:

observable
    .ObserveOnDispatcher() // 将后续操作切换到UI线程
    .Subscribe(O => OpenDocumentModel.UpdateCaptionsSubject.OnNext(IsOpen));

方案2:在UpdateCaptionsSubject的订阅端切换到UI线程

如果无法修改定时任务的订阅,可在订阅UpdateCaptionsSubject并更新集合的代码中,确保操作在UI线程执行:

// 示例:在ViewModel或OpenDocumentModel的订阅逻辑中
OpenDocumentModel.UpdateCaptionsSubject
    .ObserveOnDispatcher()
    .Subscribe(isOpen => {
        // 此处执行集合更新逻辑,已处于UI线程
        UpdateCaptionsCollection(isOpen);
    });

方案3:修改ObservableCollectionEx,强制在UI线程触发通知

让集合类本身处理线程问题,重写OnCollectionChanged时自动切换到UI线程:

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    if (SupressNotification)
    {
        _notificationSupressed = true;
        return;
    }
    // 检查是否在UI线程,不在则通过Dispatcher调度
    if (Application.Current.Dispatcher.CheckAccess())
    {
        base.OnCollectionChanged(e);
    }
    else
    {
        Application.Current.Dispatcher.Invoke(() => base.OnCollectionChanged(e));
    }
}

注:此方式需确保Application.Current不为null,VSTO环境下通常有效,非WPF宿主环境需额外处理。


内容的提问来源于stack exchange,提问作者nomacreates

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 00:15:33