50

我有ObservableCollection<T>集合,我想用新的元素集合替换所有元素,我可以这样做:

collection.Clear(); 

或者:

collection.ClearItems();

(顺便说一句,这两种方法有什么区别?)

我也可以foreach一一使用collection.Add,但这会触发多次

添加元素集合时相同。

编辑:

我在这里找到了一个很好的库:Enhanced ObservableCollection,能够延迟或禁用通知,但它似乎不支持 Silverlight。

4

5 回答 5

75

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);
    }
}
于 2012-11-09T06:34:48.110 回答
11

您可以通过子类化ObservableCollection和实现自己的ReplaceAll方法来实现这一点。此方法的实现将替换内部Items属性中的所有项目,然后触发一个CollectionChanged事件。同样,您可以添加一个AddRange方法。有关此的实现,请参阅此问题的答案:

ObservableCollection 不支持 AddRange 方法,所以我会收到添加的每个项目的通知,除了 INotifyCollectionChanging 呢?

和之间的区别在于Collection.ClearCollection.ClearItemsClear是一个公共 API 方法,而ClearItems受保护,它是一个扩展点,允许您扩展/修改Clear.

于 2012-11-09T06:17:41.753 回答
6

这是我为其他人参考而实现的:

// 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));
    }
}
于 2012-11-12T04:54:56.003 回答
1

在过去的几年里,我使用了一个更通用的解决方案来消除过多的 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)。

于 2016-07-08T12:07:49.157 回答
0

我还不能评论以前的答案,所以我在这里添加了对上述 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));
于 2014-08-12T13:10:16.563 回答