How to Avoid Firing ObservableCollection.CollectionChanged Multiple Times When Replacing All Elements Or Adding a Collection of Elements

前端 未结 5 2044

I have ObservableCollection collection, and I want to replace all elements with a new collection of elements, I could do:

collection.Cl         


        
相关标签:
5条回答
  • 2020-11-27 05:59

    I can't comment on previous answers yet, so I'm adding here a RemoveRange adaptation of the SmartCollection implementations above that won't throw a C# InvalidOperationException: Collection Was Modified. It uses a predicate to check if the item should be removed which, in my case, is more optimal than creating a subset of items that meet the remove criteria.

    public void RemoveRange(Predicate<T> remove)
    {
        // iterates backwards so can remove multiple items without invalidating indexes
        for (var i = Items.Count-1; i > -1; i--) {
            if (remove(Items[i]))
                Items.RemoveAt(i);
        }
    
        this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    

    Example:

    LogEntries.RemoveRange(i => closeFileIndexes.Contains(i.fileIndex));
    
    0 讨论(0)
  • 2020-11-27 05:59

    For the past few years I am using a more generic solution to eliminate too many ObservableCollection notifications by creating a batch change operation and notifying observers with a Reset action:

    public class ExtendedObservableCollection<T>: ObservableCollection<T>
    {
        public ExtendedObservableCollection()
        {
        }
    
        public ExtendedObservableCollection(IEnumerable<T> items)
            : base(items)
        {
        }
    
        public void Execute(Action<IList<T>> itemsAction)
        {
            itemsAction(Items);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }
    

    Using it is straightforward:

    var collection = new ExtendedObservableCollection<string>(new[]
    {
        "Test",
        "Items",
        "Here"
    });
    collection.Execute(items => {
        items.RemoveAt(1);
        items.Insert(1, "Elements");
        items.Add("and there");
    });
    

    Calling Execute will generate a single notification but with a drawback - list will be updated in UI as a whole, not only modified elements. This makes it perfect for items.Clear() followed by items.AddRange(newItems).

    0 讨论(0)
  • 2020-11-27 06:01

    You can achieve this by subclassing ObservableCollection and implementing your own ReplaceAll method. The implementation of this methods would replace all the items within the internal Items property, then fire a CollectionChanged event. Likewise, you can add an AddRange method. For an implementation of this, see the answer to this question:

    ObservableCollection Doesn't support AddRange method, so I get notified for each item added, besides what about INotifyCollectionChanging?

    The difference between Collection.Clear and Collection.ClearItems is that Clear is a public API method, whereas ClearItems is protected, it is an extension point that allows your to extend / modify the behaviour of Clear.

    0 讨论(0)
  • 2020-11-27 06:11

    ColinE is right with all his informations. I only want to add my subclass of ObservableCollection that I use for this specific case.

    public class SmartCollection<T> : ObservableCollection<T> {
        public SmartCollection()
            : base() {
        }
    
        public SmartCollection(IEnumerable<T> collection)
            : base(collection) {
        }
    
        public SmartCollection(List<T> list)
            : base(list) {
        }
    
        public void AddRange(IEnumerable<T> range) {
            foreach (var item in range) {
                Items.Add(item);
            }
    
            this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    
        public void Reset(IEnumerable<T> range) {
            this.Items.Clear();
    
            AddRange(range);
        }
    }
    
    0 讨论(0)
  • 2020-11-27 06:12

    Here is what I implemented for other folks' reference:

    // http://stackoverflow.com/questions/13302933/how-to-avoid-firing-observablecollection-collectionchanged-multiple-times-when-r
    // http://stackoverflow.com/questions/670577/observablecollection-doesnt-support-addrange-method-so-i-get-notified-for-each
    public class ObservableCollectionFast<T> : ObservableCollection<T>
    {
        public ObservableCollectionFast()
            : base()
        {
    
        }
    
        public ObservableCollectionFast(IEnumerable<T> collection)
            : base(collection)
        {
    
        }
    
        public ObservableCollectionFast(List<T> list)
            : base(list)
        {
    
        }
    
        public virtual void AddRange(IEnumerable<T> collection)
        {
            if (collection.IsNullOrEmpty())
                return;
    
            foreach (T item in collection)
            {
                this.Items.Add(item);
            }
    
            this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            // Cannot use NotifyCollectionChangedAction.Add, because Constructor supports only the 'Reset' action.
        }
    
        public virtual void RemoveRange(IEnumerable<T> collection)
        {
            if (collection.IsNullOrEmpty())
                return;
    
            bool removed = false;
            foreach (T item in collection)
            {
                if (this.Items.Remove(item))
                    removed = true;
            }
    
            if (removed)
            {
                this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
                this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                // Cannot use NotifyCollectionChangedAction.Remove, because Constructor supports only the 'Reset' action.
            }
        }
    
        public virtual void Reset(T item)
        {
            this.Reset(new List<T>() { item });
        }
    
        public virtual void Reset(IEnumerable<T> collection)
        {
            if (collection.IsNullOrEmpty() && this.Items.IsNullOrEmpty())
                return;
    
            // Step 0: Check if collection is exactly same as this.Items
            if (IEnumerableUtils.Equals<T>(collection, this.Items))
                return;
    
            int count = this.Count;
    
            // Step 1: Clear the old items
            this.Items.Clear();
    
            // Step 2: Add new items
            if (!collection.IsNullOrEmpty())
            {
                foreach (T item in collection)
                {
                    this.Items.Add(item);
                }
            }
    
            // Step 3: Don't forget the event
            if (this.Count != count)
                this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }
    
    0 讨论(0)
提交回复
热议问题