暂停对C#和WPF中ObservableCollection的更新

23 浏览
0 Comments

暂停对C#和WPF中ObservableCollection的更新

我有一个使用MVVM数据绑定的WPF应用程序。我正在向ObservableCollection<...>添加项目,而且确实有很多项目。

现在我想知道,每次我向集合中添加一个项目时,它是否立即触发事件并导致不必要的开销?如果是这样的话,我是否可以暂时禁用事件通知,并在我的代码结束时手动触发它,这样如果我添加了10,000个项目,则只会触发一次,而不是10,000次?

更新:我尝试使用这个类:

using System;
using System.Linq;
using System.Collections.Specialized;
using System.Collections.Generic;
namespace MyProject
{
    ///  
    /// 表示在项目添加、删除或整个列表刷新时提供通知的动态数据集合。
    ///  
    ///  
    public class ObservableCollection : System.Collections.ObjectModel.ObservableCollection
    {
        ///  
        /// 将指定集合的元素添加到ObservableCollection(Of T)的末尾。
        ///  
        public void AddRange(IEnumerable collection)
        {
            foreach (var i in collection) Items.Add(i);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection.ToList()));
        }
        ///  
        /// 从ObservableCollection(Of T)中删除指定集合中的每个项目的第一个匹配项。
        ///  
        public void RemoveRange(IEnumerable collection)
        {
            foreach (var i in collection) Items.Remove(i);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, collection.ToList()));
        }
        ///  
        /// 清除当前集合并用指定项替换它。
        ///  
        public void Replace(T item)
        {
            ReplaceRange(new T[] { item });
        }
        ///  
        /// 清除当前集合并用指定集合替换它。
        ///  
        public void ReplaceRange(IEnumerable collection)
        {
            List old = new List(Items);
            Items.Clear();
            foreach (var i in collection) Items.Add(i);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, collection.ToList()));
        }
        ///  
        /// 初始化 System.Collections.ObjectModel.ObservableCollection(Of T) 类的新实例。
        ///  
        public ObservableCollection() : base() { }
        ///  
        /// 初始化 System.Collections.ObjectModel.ObservableCollection(Of T) 类的新实例,并从指定的集合中复制元素。
        ///  
        /// collection: 要复制元素的集合。 
        /// collection 参数不能为 null。 
        public ObservableCollection(IEnumerable collection) : base(collection) { }
    }
}

现在我得到了这个错误:

附加信息:不支持范围操作。

错误发生在这里:

OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection.ToList()));

0
0 Comments

使用以下方式可以非常快速和简单地对ObservableCollection进行子类化,并在调用AddRange时暂停通知。请参考以下博客文章以了解更多详情。

public class SuspendableObservableCollection : ObservableCollection
{
    private bool _suspendCollectionChangeNotification;
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (!_suspendCollectionChangeNotification)
        {
            base.OnCollectionChanged(e);
        }
    }
    public void AddRange(IEnumerable items)
    {
        if (items == null)
        {
            throw new ArgumentNullException(nameof(items));
        }
        _suspendCollectionChangeNotification = true;
        try
        {
            foreach (var item in items)
            {
                Add(item);
            }
        }
        finally
        {
            _suspendCollectionChangeNotification = false;
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }
}

原因:ObservableCollection在调用AddRange时会触发多次CollectionChanged事件,而有时候我们希望暂停这些通知,以提高性能或避免重复操作。

解决方法:创建一个SuspendableObservableCollection类,继承自ObservableCollection,并添加一个AddRange方法。在AddRange方法中,我们通过设置_suspendCollectionChangeNotification标志来暂停通知,然后遍历items并逐个调用Add方法将其添加到集合中。最后,我们将_suspendCollectionChangeNotification标志重置为false,并手动触发CollectionChanged事件以通知UI进行更新。

这样,我们就可以使用SuspendableObservableCollection类来暂停通知,并在需要的时候一次性添加多个项到集合中,而不会触发多次CollectionChanged事件。

0
0 Comments

在C#和WPF中暂停更新ObservableCollection的原因是为了解决问题,这个扩展的ObservableCollection可以很容易地解决这个问题。它通过公开一个SupressNotification属性来允许用户控制何时抑制CollectionChanged通知。它不提供范围插入/删除,但是如果抑制CollectionChanged通知,大多数情况下不需要对集合进行范围操作。

这个实现将所有被抑制的通知替换为重置通知。这在逻辑上是合理的。当用户抑制通知,进行批量更改,然后重新启用时,发送一个重置通知是恰当的。

下面是实现的代码:

public class ObservableCollectionEx : ObservableCollection
{
    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);
    }
}

这个实现非常干净、简洁!在Visual Studio 2019中进行的样本测试中,将WPF控件绑定到一个包含几十行的ObservableCollection对象上。原始的ObservableCollection每行更新UI需要约90微秒。而这个可抑制的版本只需要约15微秒。

0