web-dev-qa-db-ja.com

すべての要素の置換または要素のコレクションの追加時にObservableCollection.CollectionChangedの複数回の起動を回避する方法

私が持っています ObservableCollection<T>コレクション、すべての要素を新しい要素のコレクションに置き換えたい場合、次のようにします。

collection.Clear(); 

または:

collection.ClearItems();

(ところで、これら2つの方法の違いは何ですか?)

foreachを使用してcollection.Add 1つずつですが、これは複数回起動します

要素のコレクションを追加する場合も同じです。

編集:

ここで良いライブラリを見つけました: 通知を遅延または無効にする機能を備えた強化されたObservableCollection しかし、silverlightをサポートしていないようです。

42
Peter Lee

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);
    }
}
56
Jehof

これを実現するには、ObservableCollectionをサブクラス化し、独自のReplaceAllメソッドを実装します。このメソッドの実装により、内部Itemsプロパティ内のすべてのアイテムが置き換えられ、CollectionChangedイベントが発生します。同様に、AddRangeメソッドを追加できます。これの実装については、この質問に対する回答を参照してください。

ObservableCollectionはAddRangeメソッドをサポートしないため、INotifyCollectionChangingについてはどうですか?

Collection.ClearCollection.ClearItemsの違いは、ClearはパブリックAPIメソッドであるのに対して、ClearItemsは保護されているため、拡張/変更を可能にする拡張ポイントですClearの動作。

10
ColinE

他の人々の参考のために私が実装したものは次のとおりです。

// 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));
    }
}
5
Peter Lee

以前の回答についてはまだコメントできませんので、ここで、C#InvalidOperationException:Collection Was Modifiedをスローしない上記のSmartCollection実装のRemoveRange適応を追加します。述語を使用して、アイテムを削除する必要があるかどうかを確認します。これは、私の場合、削除基準を満たすアイテムのサブセットを作成するよりも最適です。

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));
0
Heather V.

過去数年間、より一般的なソリューションを使用して、バッチ変更操作を作成し、Resetアクションでオブザーバーに通知することにより、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");
});

Executeを呼び出すと、単一の通知が生成されますが、欠点があります-変​​更された要素だけでなく、UI全体でリストが更新されます。これにより、items.Clear()に続いてitems.AddRange(newItems)が最適になります。

0
too