[英]How to Avoid Firing ObservableCollection.CollectionChanged Multiple Times When Replacing All Elements Or Adding a Collection of Elements
我有ObservableCollection<T>
集合,我想用新的元素集合替換所有元素,我可以這樣做:
collection.Clear();
要么:
collection.ClearItems();
(順便說一句,這兩種方法有什么區別?)
我也可以使用foreach
來collection.Add
。逐個添加,但這會多次觸發
添加元素集合時也一樣。
編輯:
我在這里找到了一個很好的庫: 增強的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);
}
}
您可以通過繼承ObservableCollection
並實現自己的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));
}
}
我還不能評論以前的答案,所以我在這里添加一個上面的SmartCollection實現的RemoveRange改編版,它不會拋出C#InvalidOperationException:Collection was Modified。 它使用謂詞來檢查是否應該刪除該項,在我的情況下,它比創建符合刪除條件的項的子集更優化。
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));
在過去幾年中,我使用更通用的解決方案來通過創建批量更改操作並通過重置操作通知觀察者來消除過多的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)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.