19

我有一个使用 MVVM 数据绑定的 WPF 应用程序。我ObservableCollection<...>确实在其中添加了很多项目。

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

更新:我尝试过这门课:

using System;
using System.Linq;
using System.Collections.Specialized;
using System.Collections.Generic;

namespace MyProject
{

    /// <summary> 
    /// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    public class ObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>
    {

        /// <summary> 
        /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). 
        /// </summary> 
        public void AddRange(IEnumerable<T> collection)
        {
            foreach (var i in collection) Items.Add(i);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection.ToList()));
        }

        /// <summary> 
        /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). 
        /// </summary> 
        public void RemoveRange(IEnumerable<T> collection)
        {
            foreach (var i in collection) Items.Remove(i);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, collection.ToList()));
        }

        /// <summary> 
        /// Clears the current collection and replaces it with the specified item. 
        /// </summary> 
        public void Replace(T item)
        {
            ReplaceRange(new T[] { item });
        }
        /// <summary> 
        /// Clears the current collection and replaces it with the specified collection. 
        /// </summary> 
        public void ReplaceRange(IEnumerable<T> collection)
        {
            List<T> old = new List<T>(Items);
            Items.Clear();
            foreach (var i in collection) Items.Add(i);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, collection.ToList()));
        }

        /// <summary> 
        /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. 
        /// </summary> 
        public ObservableCollection() : base() { }

        /// <summary> 
        /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. 
        /// </summary> 
        /// <param name="collection">collection: The collection from which the elements are copied.</param> 
        /// <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception> 
        public ObservableCollection(IEnumerable<T> collection) : base(collection) { }
    }
}

我现在收到此错误:

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

错误出现在这里:

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

5 回答 5

23

ObservableCollection 的这种扩展很容易解决这个问题。

它公开了一个公共的 SupressNotification 属性,以允许用户控制何时禁止 CollectionChanged 通知。

它不提供范围插入/删除,但如果 CollectionChanged 通知被抑制,在大多数情况下,对集合执行范围操作的需求就会减少。

此实现将所有被抑制的通知替换为重置通知。这在逻辑上是合情合理的。当用户禁止通知,进行批量更改然后重新启用它时,应该适当地发送重新发送通知。

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);
    }
}
于 2015-04-28T05:08:01.123 回答
18

一个非常快速和简单的方法是继承 ObservableCollection 并在调用 AddRange 时暂停通知。有关说明,请参阅以下博客文章

于 2012-05-13T15:34:59.663 回答
8

在我看来,有一种“棘手”的方式,但非常准确,可以实现这一目标。是写你自己ObservableCollection并实现AddRange处理。

通过这种方式,您可以将所有 10k 元素添加到某个“持有人集合”中,然后在您完成后,使用您的AddRange来执行此操作。 ObservableColleciton

您可以在此链接上找到更多信息:

ObservableCollection 不支持 AddRange 方法....

或者这个也是

AddRange 和 ObservableCollection

于 2012-05-13T14:32:43.353 回答
0

抱歉,我想将此作为评论发布,因为我不会提供完整的实现细节,但这有点太长了。

关于“不支持范围动作”,这来自ListCollectionViewWPF 用于绑定的,它确实不支持范围动作。但是,正常情况下CollectionView会。

WPF 选择ListCollectionView在绑定集合实现非泛型IList接口时使用。因此,基本上要使AddRange解决方案正常工作,您需要完全重新实现 ObservableCollection (而不是对其进行交互),但没有非泛型接口:

public class MyObservableCollection<T> :
    IList<T>,
    IReadOnlyList<T>,
    INotifyCollectionChanged,
    INotifyPropertyChanged
{
   // ...
}

在 dotPeek 或等效工具的帮助下,实现这一点应该不会花很长时间。请注意,由于您将使用 aCollectionView而不是 a ,您可能会失去一些优化ListCollectionView,但根据我自己的经验,在全球范围内使用这样的类完全提高了性能。

于 2018-04-05T03:03:03.037 回答
0

我发现有必要对小果哥的回答进行扩展。我的代码与该答案相同,除了:

  1. 我添加了对 OnPropertyChanged 方法的覆盖,以禁止发布 PropertyChanged 事件。
  2. 在属性设置器中,对 OnPropertyChanged 进行了两次调用
  3. 为了清楚起见,我重命名了字段和属性

我的 ObservableCollection 是 DataGrid 的 ItemsSource,在那里我有替换数千个项目的案例。如果不实施#1,我发现我没有获得我需要的性能提升(这是巨大的!)。我不确定#2 可能有多重要,但它显示在另一个 StackOverflow 页面中,该页面对同一问题采取了稍微不同的方法。我猜测抑制 PropertyChanged 事件提高了我的性能这一事实证明 DataGrid 已订阅该事件,因此在关闭通知抑制时发布事件可能很重要。

有一点需要注意的是,我认为没有必要从方法 OnPropertyChanged 设置 _havePendingNotifications = true,但如果您发现不同,可以考虑添加。

    /// <summary>
    /// If this property is set to true, then CollectionChanged and PropertyChanged
    /// events are not published. Furthermore, if collection changes occur while this property is set
    /// to true, then subsequently setting the property to false will cause a CollectionChanged event
    /// to be published with Action=Reset.  This is designed for faster performance in cases where a
    /// large number of items are to be added or removed from the collection, especially including cases
    /// where the entire collection is to be replaced.  The caller should follow this pattern:
    ///   1) Set NotificationSuppressed to true
    ///   2) Do a number of Add, Insert, and/or Remove calls
    ///   3) Set NotificationSuppressed to false
    /// </summary>
    public Boolean NotificationSuppressed
    {
        get { return _notificationSuppressed; }
        set
        {
            _notificationSuppressed = value;
            if (_notificationSuppressed == false && _havePendingNotifications)
            {
                OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                OnPropertyChanged(new PropertyChangedEventArgs("Count"));
                OnCollectionChanged(
                           new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                _havePendingNotifications = false;
            }
        }
    }
    /// <summary> This field is backing store for public property NotificationSuppressed </summary>
    protected Boolean _notificationSuppressed = false;
    /// <summary>
    /// This field indicates whether there have been notifications that have been suppressed due to the
    /// NotificationSuppressed property having value of true.  If this field is true, then when
    /// NotificationSuppressed is next set to false, a CollectionChanged event is published with
    /// Action=Reset, and the field is reset to false.
    /// </summary>
    protected Boolean _havePendingNotifications = false;
    /// <summary>
    /// This method publishes the CollectionChanged event with the provided arguments.
    /// </summary>
    /// <param name="e">container for arguments of the event that is published</param>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (NotificationSuppressed)
        {
            _havePendingNotifications = true;
            return;
        }
        base.OnCollectionChanged(e);
    }
    /// <summary>
    /// This method publishes the PropertyChanged event with the provided arguments.
    /// </summary>
    /// <param name="e">container for arguments of the event that is published</param>
    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (NotificationSuppressed) return;
        base.OnPropertyChanged(e);
    }
于 2021-11-19T00:13:36.090 回答