如何避免在replace所有元素或添加元素集时多次触发ObservableCollection.CollectionChanged
我有ObservableCollection<T>
集合,我想用新的元素集合来replace所有元素,我可以这样做:
collection.Clear();
要么:
collection.ClearItems();
(顺便说一句,这两种方法有什么区别?)
我也可以使用foreach
来collection.Add
一个接一个地添加,但是会多次触发
添加元素集合时也是如此。
编辑:
我在这里find了一个好的库: 增强的ObservableCollection能够延迟或禁用通知,但它似乎不支持silverlight。
ColinE对他所有的信息都是正确的。 我只想添加我用于此特定情况的ObservableCollection
子类。
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); } }
您可以通过inheritanceObservableCollection
并实现您自己的ReplaceAll
方法来实现此目的。 这个方法的实现将取代内部Items
属性中的所有项目,然后触发一个CollectionChanged
事件。 同样,你可以添加一个AddRange
方法。 为了实现这个,请参阅这个问题的答案:
ObservableCollection不支持AddRange方法,所以我得到通知每个项目添加,除了INotifyCollectionChanging?
Collection.Clear
和Collection.ClearItems
之间的区别在于Clear
是一个公共API方法,而ClearItems
是受保护的,它是一个扩展点,允许您扩展/修改Clear
的行为。
以下是我为其他人参考的内容:
// 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)); } }
我不能评论以前的答案,所以我在这里添加一个RemoveRange适应上面的SmartCollection实现不会抛出一个C#InvalidOperationException:集合被修改。 它使用谓词来检查项目是否应该被删除,在我的情况下,比创build符合删除标准的项目子集更优化。
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)); }
例:
LogEntries.RemoveRange(i => closeFileIndexes.Contains(i.fileIndex));
在过去的几年中,我使用更通用的解决scheme,通过创build批量更改操作并通知复位操作的观察者来消除太多的ObservableCollection通知:
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)); } }
使用它很简单:
var collection = new ExtendedObservableCollection<string>(new[] { "Test", "Items", "Here" }); collection.Execute(items => { items.RemoveAt(1); items.Insert(1, "Elements"); items.Add("and there"); });
调用执行会生成一个单一的通知,但有一个缺点 – 列表将在UI中作为一个整体进行更新,而不仅仅是修改的元素。 这使得它完美的items.Clear()后面items.AddRange(newItems)。