ObservableCollection не підтримує метод AddRange, тому я отримую сповіщення про кожен доданий елемент, крім того, що про INotifyCollectionChanging?


163

Я хочу мати змогу додати діапазон та оновлюватись для всієї маси.

Я також хочу мати можливість скасувати дію, перш ніж вона буде виконана (тобто колекція, що змінюється, крім "зміненої").


Пов'язані питання. Яка .Net колекція для додавання декількох об'єктів одночасно та отримання сповіщення?

Відповіді:


125

Будь ласка, зверніться до оновленої та оптимізованої версії C # 7 . Я не хотів видаляти версію VB.NET, тому я просто розмістив її в окремій відповіді.

Перейти до оновленої версії

Здається, це не підтримується, я реалізував сам, FYI, сподіваюся, що це буде корисно:

Я оновив версію VB і тепер це викликає подія перед зміною колекції , так що ви можете шкодувати (корисно при використанні з DataGrid, ListViewі багато іншого, що ви можете показати «Ви впевнені , що " підтвердження користувача), оновлений VB версія знаходиться внизу цього повідомлення .

Прийміть моє вибачення, що екран занадто вузький, щоб містити мій код, мені це також не подобається.

VB.NET:

Imports System.Collections.Specialized

Namespace System.Collections.ObjectModel
    ''' <summary>
    ''' Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    Public Class ObservableRangeCollection(Of T) : Inherits System.Collections.ObjectModel.ObservableCollection(Of T)

        ''' <summary>
        ''' Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
        ''' </summary>
        Public Sub AddRange(ByVal collection As IEnumerable(Of T))
            For Each i In collection
                Items.Add(i)
            Next
            OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
        End Sub

        ''' <summary>
        ''' Removes the first occurence of each item in the specified collection from ObservableCollection(Of T).
        ''' </summary>
        Public Sub RemoveRange(ByVal collection As IEnumerable(Of T))
            For Each i In collection
                Items.Remove(i)
            Next

            OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
        End Sub

        ''' <summary>
        ''' Clears the current collection and replaces it with the specified item.
        ''' </summary>
        Public Sub Replace(ByVal item As T)
            ReplaceRange(New T() {item})
        End Sub
        ''' <summary>
        ''' Clears the current collection and replaces it with the specified collection.
        ''' </summary>
        Public Sub ReplaceRange(ByVal collection As IEnumerable(Of T))
            Dim old = Items.ToList
            Items.Clear()
            For Each i In collection
                Items.Add(i)
            Next
            OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
        End Sub

        ''' <summary>
        ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()
            MyBase.New()
        End Sub
        ''' <summary>
        ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
        ''' </summary>
        ''' <param name="collection">collection: The collection from which the elements are copied.</param>
        ''' <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception>
        Public Sub New(ByVal collection As IEnumerable(Of T))
            MyBase.New(collection)
        End Sub
    End Class   

End Namespace

C #:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;

/// <summary> 
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. 
/// </summary> 
/// <typeparam name="T"></typeparam> 
public class ObservableRangeCollection<T> : ObservableCollection<T>
{
    /// <summary> 
    /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). 
    /// </summary> 
    public void AddRange(IEnumerable<T> collection)
    {
        if (collection == null) throw new ArgumentNullException("collection");

        foreach (var i in collection) Items.Add(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    /// <summary> 
    /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). 
    /// </summary> 
    public void RemoveRange(IEnumerable<T> collection)
    {
        if (collection == null) throw new ArgumentNullException("collection");

        foreach (var i in collection) Items.Remove(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    /// <summary> 
    /// Clears the current collection and replaces it with the specified item. 
    /// </summary> 
    public void Replace(T item)
    {
        ReplaceRange(new T[] { item });
    }

    /// <summary> 
    /// Clears the current collection and replaces it with the specified collection. 
    /// </summary> 
    public void ReplaceRange(IEnumerable<T> collection)
    {
        if (collection == null) throw new ArgumentNullException("collection");

        Items.Clear();
        foreach (var i in collection) Items.Add(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    /// <summary> 
    /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. 
    /// </summary> 
    public ObservableRangeCollection()
        : base() { }

    /// <summary> 
    /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. 
    /// </summary> 
    /// <param name="collection">collection: The collection from which the elements are copied.</param> 
    /// <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception> 
    public ObservableRangeCollection(IEnumerable<T> collection)
        : base(collection) { }
}

Оновлення - колекція, що спостерігається, із сповіщенням про зміну колекції

Imports System.Collections.Specialized
Imports System.ComponentModel
Imports System.Collections.ObjectModel

Public Class ObservableRangeCollection(Of T) : Inherits ObservableCollection(Of T) : Implements INotifyCollectionChanging(Of T)
    ''' <summary>
    ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        MyBase.New()
    End Sub

    ''' <summary>
    ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
    ''' </summary>
    ''' <param name="collection">collection: The collection from which the elements are copied.</param>
    ''' <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception>
    Public Sub New(ByVal collection As IEnumerable(Of T))
        MyBase.New(collection)
    End Sub

    ''' <summary>
    ''' Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
    ''' </summary>
    Public Sub AddRange(ByVal collection As IEnumerable(Of T))
        Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Add, collection)
        OnCollectionChanging(ce)
        If ce.Cancel Then Exit Sub

        Dim index = Items.Count - 1
        For Each i In collection
            Items.Add(i)
        Next

        OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection, index))
    End Sub


    ''' <summary>
    ''' Inserts the collection at specified index.
    ''' </summary>
    Public Sub InsertRange(ByVal index As Integer, ByVal Collection As IEnumerable(Of T))
        Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Add, Collection)
        OnCollectionChanging(ce)
        If ce.Cancel Then Exit Sub

        For Each i In Collection
            Items.Insert(index, i)
        Next

        OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
    End Sub


    ''' <summary>
    ''' Removes the first occurence of each item in the specified collection from ObservableCollection(Of T).
    ''' </summary>
    Public Sub RemoveRange(ByVal collection As IEnumerable(Of T))
        Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Remove, collection)
        OnCollectionChanging(ce)
        If ce.Cancel Then Exit Sub

        For Each i In collection
            Items.Remove(i)
        Next

        OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
    End Sub



    ''' <summary>
    ''' Clears the current collection and replaces it with the specified item.
    ''' </summary>
    Public Sub Replace(ByVal item As T)
        ReplaceRange(New T() {item})
    End Sub

    ''' <summary>
    ''' Clears the current collection and replaces it with the specified collection.
    ''' </summary>
    Public Sub ReplaceRange(ByVal collection As IEnumerable(Of T))
        Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Replace, Items)
        OnCollectionChanging(ce)
        If ce.Cancel Then Exit Sub

        Items.Clear()
        For Each i In collection
            Items.Add(i)
        Next
        OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
    End Sub

    Protected Overrides Sub ClearItems()
        Dim e As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Reset, Items)
        OnCollectionChanging(e)

        If e.Cancel Then Exit Sub

        MyBase.ClearItems()
    End Sub

    Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As T)
        Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Add, item)
        OnCollectionChanging(ce)
        If ce.Cancel Then Exit Sub

        MyBase.InsertItem(index, item)
    End Sub

    Protected Overrides Sub MoveItem(ByVal oldIndex As Integer, ByVal newIndex As Integer)
        Dim ce As New NotifyCollectionChangingEventArgs(Of T)()
        OnCollectionChanging(ce)
        If ce.Cancel Then Exit Sub

        MyBase.MoveItem(oldIndex, newIndex)
    End Sub

    Protected Overrides Sub RemoveItem(ByVal index As Integer)
        Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Remove, Items(index))
        OnCollectionChanging(ce)
        If ce.Cancel Then Exit Sub

        MyBase.RemoveItem(index)
    End Sub

    Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As T)
        Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Replace, Items(index))
        OnCollectionChanging(ce)
        If ce.Cancel Then Exit Sub

        MyBase.SetItem(index, item)
    End Sub

    Protected Overrides Sub OnCollectionChanged(ByVal e As Specialized.NotifyCollectionChangedEventArgs)
        If e.NewItems IsNot Nothing Then
            For Each i As T In e.NewItems
                If TypeOf i Is INotifyPropertyChanged Then AddHandler DirectCast(i, INotifyPropertyChanged).PropertyChanged, AddressOf Item_PropertyChanged
            Next
        End If
        MyBase.OnCollectionChanged(e)
    End Sub

    Private Sub Item_PropertyChanged(ByVal sender As T, ByVal e As ComponentModel.PropertyChangedEventArgs)
        OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, sender, IndexOf(sender)))
    End Sub

    Public Event CollectionChanging(ByVal sender As Object, ByVal e As NotifyCollectionChangingEventArgs(Of T)) Implements INotifyCollectionChanging(Of T).CollectionChanging
    Protected Overridable Sub OnCollectionChanging(ByVal e As NotifyCollectionChangingEventArgs(Of T))
        RaiseEvent CollectionChanging(Me, e)
    End Sub
End Class


Public Interface INotifyCollectionChanging(Of T)
    Event CollectionChanging(ByVal sender As Object, ByVal e As NotifyCollectionChangingEventArgs(Of T))
End Interface

Public Class NotifyCollectionChangingEventArgs(Of T) : Inherits CancelEventArgs

    Public Sub New()
        m_Action = NotifyCollectionChangedAction.Move
        m_Items = New T() {}
    End Sub

    Public Sub New(ByVal action As NotifyCollectionChangedAction, ByVal item As T)
        m_Action = action
        m_Items = New T() {item}
    End Sub

    Public Sub New(ByVal action As NotifyCollectionChangedAction, ByVal items As IEnumerable(Of T))
        m_Action = action
        m_Items = items
    End Sub

    Private m_Action As NotifyCollectionChangedAction
    Public ReadOnly Property Action() As NotifyCollectionChangedAction
        Get
            Return m_Action
        End Get
    End Property

    Private m_Items As IList
    Public ReadOnly Property Items() As IEnumerable(Of T)
        Get
            Return m_Items
        End Get
    End Property
End Class

4
Чи все-таки це не призведе до зміни подій, що змінилися для окремих колекцій, коли ви повторюєте колекцію та додаєте / видаляєте елементи?
Скотт Дорман

6
Скотт Дорман правильний. Виклик вбудованого методу Add спричинить щось на зразок 3 подій на додавання (NotifyPropertyChange для кількості елементів, NotifyPropertyChange для індексації елементів [] та подія NotifyCollectionChanged для доданого елемента). Для отримання найкращої продуктивності для великого оновлення потрібна повна заміна ObservableCollection.
Пройшов кодування

14
@HiTechMagic Скотт невірний. оскільки це викликає Додати на захищені елементи IList <T> Колекції <T>, таких змінених подій не надсилається, я перевірив за допомогою тестових підрахунків підрахунку викликів до події CollectionChanged з AddRange, і я можу підтвердити, що це лише один.
weston

8
Потрібно зателефонувати OnPropertyChanged("Count");і OnPropertyChanged("Item[]");у методах додавання / видалення / заміни діапазону, щоб повністю імітувати стандартний ObservableCollection.
weston

9
Код C # не працює: Додаткова інформація: Діапазон дії не підтримується.
Сайкор

42

Перш за все, будь ласка , голосувати і коментувати по запиту API на .NET репо.

Ось моя оптимізована версія ObservableRangeCollection(оптимізована версія Джеймса Montemagno в одному ).

Він працює дуже швидко і призначений для повторного використання існуючих елементів, коли це можливо, та уникнення зайвих подій, або об'єднання їх в один, коли це можливо. У ReplaceRangeметоді замінює / видаляє / додає необхідні елементи від відповідних показників і партій можливих подій.

Тестовано на інтерфейсі Xamarin.Forms з прекрасними результатами за дуже часті оновлення великої колекції (5-7 оновлень в секунду).

Примітка: Оскільки WPF не звик працювати з операціями на діапазоні, він буде кидати a NotSupportedException, використовуючи ObservableRangeCollectionзнизу в роботі, пов’язаній з користувальницьким інтерфейсом WPF, наприклад, прив'язуючи його до ListBoxт. Д. (Ви все одно можете використовувати, ObservableRangeCollection<T>якщо не пов'язаний з UI) .
Однак ви можете скористатися WpfObservableRangeCollection<T>способом вирішення.
Справжнім рішенням було б створення того, CollectionViewхто знає, як впоратися з операціями діапазону, але я все одно не мав часу реалізувати це.

RAW Code - відкрийте як Raw, потім зробітьCtrl+,Aщоб вибрати все, потімCtrl+Cдля копіювання.

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;

namespace System.Collections.ObjectModel
{
  /// <summary>
  /// Implementation of a dynamic data collection based on generic Collection&lt;T&gt;,
  /// implementing INotifyCollectionChanged to notify listeners
  /// when items get added, removed or the whole list is refreshed.
  /// </summary>
  public class ObservableRangeCollection<T> : ObservableCollection<T>
  {
    //------------------------------------------------------
    //
    //  Private Fields
    //
    //------------------------------------------------------

    #region Private Fields    
    [NonSerialized]
    private DeferredEventsCollection _deferredEvents;
    #endregion Private Fields


    //------------------------------------------------------
    //
    //  Constructors
    //
    //------------------------------------------------------

    #region Constructors
    /// <summary>
    /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity.
    /// </summary>
    public ObservableRangeCollection() { }

    /// <summary>
    /// Initializes a new instance of the ObservableCollection class that contains
    /// elements copied from the specified collection and has sufficient capacity
    /// to accommodate the number of elements copied.
    /// </summary>
    /// <param name="collection">The collection whose elements are copied to the new list.</param>
    /// <remarks>
    /// The elements are copied onto the ObservableCollection in the
    /// same order they are read by the enumerator of the collection.
    /// </remarks>
    /// <exception cref="ArgumentNullException"> collection is a null reference </exception>
    public ObservableRangeCollection(IEnumerable<T> collection) : base(collection) { }

    /// <summary>
    /// Initializes a new instance of the ObservableCollection class
    /// that contains elements copied from the specified list
    /// </summary>
    /// <param name="list">The list whose elements are copied to the new list.</param>
    /// <remarks>
    /// The elements are copied onto the ObservableCollection in the
    /// same order they are read by the enumerator of the list.
    /// </remarks>
    /// <exception cref="ArgumentNullException"> list is a null reference </exception>
    public ObservableRangeCollection(List<T> list) : base(list) { }

    #endregion Constructors

    //------------------------------------------------------
    //
    //  Public Methods
    //
    //------------------------------------------------------

    #region Public Methods

    /// <summary>
    /// Adds the elements of the specified collection to the end of the <see cref="ObservableCollection{T}"/>.
    /// </summary>
    /// <param name="collection">
    /// The collection whose elements should be added to the end of the <see cref="ObservableCollection{T}"/>.
    /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
    /// </param>
    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
    public void AddRange(IEnumerable<T> collection)
    {
      InsertRange(Count, collection);
    }

    /// <summary>
    /// Inserts the elements of a collection into the <see cref="ObservableCollection{T}"/> at the specified index.
    /// </summary>
    /// <param name="index">The zero-based index at which the new elements should be inserted.</param>
    /// <param name="collection">The collection whose elements should be inserted into the List<T>.
    /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.</param>                
    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is not in the collection range.</exception>
    public void InsertRange(int index, IEnumerable<T> collection)
    {
      if (collection == null)
        throw new ArgumentNullException(nameof(collection));
      if (index < 0)
        throw new ArgumentOutOfRangeException(nameof(index));
      if (index > Count)
        throw new ArgumentOutOfRangeException(nameof(index));

      if (collection is ICollection<T> countable)
      {
        if (countable.Count == 0)
        {
          return;
        }
      }
      else if (!ContainsAny(collection))
      {
        return;
      }

      CheckReentrancy();

      //expand the following couple of lines when adding more constructors.
      var target = (List<T>)Items;
      target.InsertRange(index, collection);

      OnEssentialPropertiesChanged();

      if (!(collection is IList list))
        list = new List<T>(collection);

      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, index));
    }


    /// <summary> 
    /// Removes the first occurence of each item in the specified collection from the <see cref="ObservableCollection{T}"/>.
    /// </summary>
    /// <param name="collection">The items to remove.</param>        
    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
    public void RemoveRange(IEnumerable<T> collection)
    {
      if (collection == null)
        throw new ArgumentNullException(nameof(collection));

      if (Count == 0)
      {
        return;
      }
      else if (collection is ICollection<T> countable)
      {
        if (countable.Count == 0)
          return;
        else if (countable.Count == 1)
          using (IEnumerator<T> enumerator = countable.GetEnumerator())
          {
            enumerator.MoveNext();
            Remove(enumerator.Current);
            return;
          }
      }
      else if (!(ContainsAny(collection)))
      {
        return;
      }

      CheckReentrancy();

      var clusters = new Dictionary<int, List<T>>();
      var lastIndex = -1;
      List<T> lastCluster = null;
      foreach (T item in collection)
      {
        var index = IndexOf(item);
        if (index < 0)
        {
          continue;
        }

        Items.RemoveAt(index);

        if (lastIndex == index && lastCluster != null)
        {
          lastCluster.Add(item);
        }
        else
        {
          clusters[lastIndex = index] = lastCluster = new List<T> { item };
        }
      }

      OnEssentialPropertiesChanged();

      if (Count == 0)
        OnCollectionReset();
      else
        foreach (KeyValuePair<int, List<T>> cluster in clusters)
          OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster.Value, cluster.Key));

    }

    /// <summary>
    /// Iterates over the collection and removes all items that satisfy the specified match.
    /// </summary>
    /// <remarks>The complexity is O(n).</remarks>
    /// <param name="match"></param>
    /// <returns>Returns the number of elements that where </returns>
    /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
    public int RemoveAll(Predicate<T> match)
    {
      return RemoveAll(0, Count, match);
    }

    /// <summary>
    /// Iterates over the specified range within the collection and removes all items that satisfy the specified match.
    /// </summary>
    /// <remarks>The complexity is O(n).</remarks>
    /// <param name="index">The index of where to start performing the search.</param>
    /// <param name="count">The number of items to iterate on.</param>
    /// <param name="match"></param>
    /// <returns>Returns the number of elements that where </returns>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
    public int RemoveAll(int index, int count, Predicate<T> match)
    {
      if (index < 0)
        throw new ArgumentOutOfRangeException(nameof(index));
      if (count < 0)
        throw new ArgumentOutOfRangeException(nameof(count));
      if (index + count > Count)
        throw new ArgumentOutOfRangeException(nameof(index));
      if (match == null)
        throw new ArgumentNullException(nameof(match));

      if (Count == 0)
        return 0;

      List<T> cluster = null;
      var clusterIndex = -1;
      var removedCount = 0;

      using (BlockReentrancy())
      using (DeferEvents())
      {
        for (var i = 0; i < count; i++, index++)
        {
          T item = Items[index];
          if (match(item))
          {
            Items.RemoveAt(index);
            removedCount++;

            if (clusterIndex == index)
            {
              Debug.Assert(cluster != null);
              cluster.Add(item);
            }
            else
            {
              cluster = new List<T> { item };
              clusterIndex = index;
            }

            index--;
          }
          else if (clusterIndex > -1)
          {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex));
            clusterIndex = -1;
            cluster = null;
          }
        }

        if (clusterIndex > -1)
          OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex));
      }

      if (removedCount > 0)
        OnEssentialPropertiesChanged();

      return removedCount;
    }

    /// <summary>
    /// Removes a range of elements from the <see cref="ObservableCollection{T}"/>>.
    /// </summary>
    /// <param name="index">The zero-based starting index of the range of elements to remove.</param>
    /// <param name="count">The number of elements to remove.</param>
    /// <exception cref="ArgumentOutOfRangeException">The specified range is exceeding the collection.</exception>
    public void RemoveRange(int index, int count)
    {
      if (index < 0)
        throw new ArgumentOutOfRangeException(nameof(index));
      if (count < 0)
        throw new ArgumentOutOfRangeException(nameof(count));
      if (index + count > Count)
        throw new ArgumentOutOfRangeException(nameof(index));

      if (count == 0)
        return;

      if (count == 1)
      {
        RemoveItem(index);
        return;
      }

      //Items will always be List<T>, see constructors
      var items = (List<T>)Items;
      List<T> removedItems = items.GetRange(index, count);

      CheckReentrancy();

      items.RemoveRange(index, count);

      OnEssentialPropertiesChanged();

      if (Count == 0)
        OnCollectionReset();
      else
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, index));
    }

    /// <summary> 
    /// Clears the current collection and replaces it with the specified collection,
    /// using the default <see cref="EqualityComparer{T}"/>.
    /// </summary>             
    /// <param name="collection">The items to fill the collection with, after clearing it.</param>
    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
    public void ReplaceRange(IEnumerable<T> collection)
    {
      ReplaceRange(0, Count, collection, EqualityComparer<T>.Default);
    }

    /// <summary>
    /// Clears the current collection and replaces it with the specified collection,
    /// using the specified comparer to skip equal items.
    /// </summary>
    /// <param name="collection">The items to fill the collection with, after clearing it.</param>
    /// <param name="comparer">An <see cref="IEqualityComparer{T}"/> to be used
    /// to check whether an item in the same location already existed before,
    /// which in case it would not be added to the collection, and no event will be raised for it.</param>
    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is null.</exception>
    public void ReplaceRange(IEnumerable<T> collection, IEqualityComparer<T> comparer)
    {
      ReplaceRange(0, Count, collection, comparer);
    }

    /// <summary>
    /// Removes the specified range and inserts the specified collection,
    /// ignoring equal items (using <see cref="EqualityComparer{T}.Default"/>).
    /// </summary>
    /// <param name="index">The index of where to start the replacement.</param>
    /// <param name="count">The number of items to be replaced.</param>
    /// <param name="collection">The collection to insert in that location.</param>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
    public void ReplaceRange(int index, int count, IEnumerable<T> collection)
    {
      ReplaceRange(index, count, collection, EqualityComparer<T>.Default);
    }

    /// <summary>
    /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact.
    /// </summary>
    /// <param name="index">The index of where to start the replacement.</param>
    /// <param name="count">The number of items to be replaced.</param>
    /// <param name="collection">The collection to insert in that location.</param>
    /// <param name="comparer">The comparer to use when checking for equal items.</param>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is null.</exception>
    public void ReplaceRange(int index, int count, IEnumerable<T> collection, IEqualityComparer<T> comparer)
    {
      if (index < 0)
        throw new ArgumentOutOfRangeException(nameof(index));
      if (count < 0)
        throw new ArgumentOutOfRangeException(nameof(count));
      if (index + count > Count)
        throw new ArgumentOutOfRangeException(nameof(index));

      if (collection == null)
        throw new ArgumentNullException(nameof(collection));
      if (comparer == null)
        throw new ArgumentNullException(nameof(comparer));

      if (collection is ICollection<T> countable)
      {
        if (countable.Count == 0)
        {
          RemoveRange(index, count);
          return;
        }
      }
      else if (!ContainsAny(collection))
      {
        RemoveRange(index, count);
        return;
      }

      if (index + count == 0)
      {
        InsertRange(0, collection);
        return;
      }

      if (!(collection is IList<T> list))
        list = new List<T>(collection);

      using (BlockReentrancy())
      using (DeferEvents())
      {
        var rangeCount = index + count;
        var addedCount = list.Count;

        var changesMade = false;
        List<T>
            newCluster = null,
            oldCluster = null;


        int i = index;
        for (; i < rangeCount && i - index < addedCount; i++)
        {
          //parallel position
          T old = this[i], @new = list[i - index];
          if (comparer.Equals(old, @new))
          {
            OnRangeReplaced(i, newCluster, oldCluster);
            continue;
          }
          else
          {
            Items[i] = @new;

            if (newCluster == null)
            {
              Debug.Assert(oldCluster == null);
              newCluster = new List<T> { @new };
              oldCluster = new List<T> { old };
            }
            else
            {
              newCluster.Add(@new);
              oldCluster.Add(old);
            }

            changesMade = true;
          }
        }

        OnRangeReplaced(i, newCluster, oldCluster);

        //exceeding position
        if (count != addedCount)
        {
          var items = (List<T>)Items;
          if (count > addedCount)
          {
            var removedCount = rangeCount - addedCount;
            T[] removed = new T[removedCount];
            items.CopyTo(i, removed, 0, removed.Length);
            items.RemoveRange(i, removedCount);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed, i));
          }
          else
          {
            var k = i - index;
            T[] added = new T[addedCount - k];
            for (int j = k; j < addedCount; j++)
            {
              T @new = list[j];
              added[j - k] = @new;
            }
            items.InsertRange(i, added);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, i));
          }

          OnEssentialPropertiesChanged();
        }
        else if (changesMade)
        {
          OnIndexerPropertyChanged();
        }
      }
    }

    #endregion Public Methods


    //------------------------------------------------------
    //
    //  Protected Methods
    //
    //------------------------------------------------------

    #region Protected Methods

    /// <summary>
    /// Called by base class Collection&lt;T&gt; when the list is being cleared;
    /// raises a CollectionChanged event to any listeners.
    /// </summary>
    protected override void ClearItems()
    {
      if (Count == 0)
        return;

      CheckReentrancy();
      base.ClearItems();
      OnEssentialPropertiesChanged();
      OnCollectionReset();
    }

    /// <summary>
    /// Called by base class Collection&lt;T&gt; when an item is set in list;
    /// raises a CollectionChanged event to any listeners.
    /// </summary>
    protected override void SetItem(int index, T item)
    {
      if (Equals(this[index], item))
        return;

      CheckReentrancy();
      T originalItem = this[index];
      base.SetItem(index, item);

      OnIndexerPropertyChanged();
      OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, item, index);
    }

    /// <summary>
    /// Raise CollectionChanged event to any listeners.
    /// Properties/methods modifying this ObservableCollection will raise
    /// a collection changed event through this virtual method.
    /// </summary>
    /// <remarks>
    /// When overriding this method, either call its base implementation
    /// or call <see cref="BlockReentrancy"/> to guard against reentrant collection changes.
    /// </remarks>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
      if (_deferredEvents != null)
      {
        _deferredEvents.Add(e);
        return;
      }
      base.OnCollectionChanged(e);
    }

    protected virtual IDisposable DeferEvents() => new DeferredEventsCollection(this);

    #endregion Protected Methods


    //------------------------------------------------------
    //
    //  Private Methods
    //
    //------------------------------------------------------

    #region Private Methods

    /// <summary>
    /// Helper function to determine if a collection contains any elements.
    /// </summary>
    /// <param name="collection">The collection to evaluate.</param>
    /// <returns></returns>
    private static bool ContainsAny(IEnumerable<T> collection)
    {
      using (IEnumerator<T> enumerator = collection.GetEnumerator())
        return enumerator.MoveNext();
    }

    /// <summary>
    /// Helper to raise Count property and the Indexer property.
    /// </summary>
    private void OnEssentialPropertiesChanged()
    {
      OnPropertyChanged(EventArgsCache.CountPropertyChanged);
      OnIndexerPropertyChanged();
    }

    /// <summary>
    /// /// Helper to raise a PropertyChanged event for the Indexer property
    /// /// </summary>
    private void OnIndexerPropertyChanged() =>
      OnPropertyChanged(EventArgsCache.IndexerPropertyChanged);

    /// <summary>
    /// Helper to raise CollectionChanged event to any listeners
    /// </summary>
    private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) =>
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));

    /// <summary>
    /// Helper to raise CollectionChanged event with action == Reset to any listeners
    /// </summary>
    private void OnCollectionReset() =>
      OnCollectionChanged(EventArgsCache.ResetCollectionChanged);

    /// <summary>
    /// Helper to raise event for clustered action and clear cluster.
    /// </summary>
    /// <param name="followingItemIndex">The index of the item following the replacement block.</param>
    /// <param name="newCluster"></param>
    /// <param name="oldCluster"></param>
    //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable<T> collection, IEqualityComparer<T> comparer),
    //move when supported language version updated.
    private void OnRangeReplaced(int followingItemIndex, ICollection<T> newCluster, ICollection<T> oldCluster)
    {
      if (oldCluster == null || oldCluster.Count == 0)
      {
        Debug.Assert(newCluster == null || newCluster.Count == 0);
        return;
      }

      OnCollectionChanged(
          new NotifyCollectionChangedEventArgs(
              NotifyCollectionChangedAction.Replace,
              new List<T>(newCluster),
              new List<T>(oldCluster),
              followingItemIndex - oldCluster.Count));

      oldCluster.Clear();
      newCluster.Clear();
    }

    #endregion Private Methods

    //------------------------------------------------------
    //
    //  Private Types
    //
    //------------------------------------------------------

    #region Private Types
    private sealed class DeferredEventsCollection : List<NotifyCollectionChangedEventArgs>, IDisposable
    {
      private readonly ObservableRangeCollection<T> _collection;
      public DeferredEventsCollection(ObservableRangeCollection<T> collection)
      {
        Debug.Assert(collection != null);
        Debug.Assert(collection._deferredEvents == null);
        _collection = collection;
        _collection._deferredEvents = this;
      }

      public void Dispose()
      {
        _collection._deferredEvents = null;
        foreach (var args in this)
          _collection.OnCollectionChanged(args);
      }
    }

    #endregion Private Types

  }

  /// <remarks>
  /// To be kept outside <see cref="ObservableCollection{T}"/>, since otherwise, a new instance will be created for each generic type used.
  /// </remarks>
  internal static class EventArgsCache
  {
    internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs("Count");
    internal static readonly PropertyChangedEventArgs IndexerPropertyChanged = new PropertyChangedEventArgs("Item[]");
    internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
  }
}

1
чи можете ви мені допомогти з цим кодом? Можливо, я використовую неправильну версію фреймворка, але, наприклад, у рядку 48 методу AddRange є "if (колекція - список ICollection <T>)" - але що це за "список"?
Bartek Boro


1
Чи можливо використовувати його у програмі WPF? Я намагаюся це зробити, але отримання "Діапазон дії не підтримується". виняток.
lucas.mdo

1
System.NotSupportedException: Діапазон дії не підтримується. dot net 4.5 vs 2017
програміст Henka

1
@HenkaProgrammer Я оновив свою відповідь. У WPF використовуйте це .
Шиммі Вайцхандлер

31

Я думаю, що AddRange краще реалізувати так:

public void AddRange(IEnumerable<T> collection)
{
    foreach (var i in collection) Items.Add(i);
    OnCollectionChanged(
        new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

Це економить копію списку. Крім того, якщо ви хочете мікро-оптимізувати, ви можете зробити додавання до N елементів, і якщо більше, ніж N елементів, де додано, зробити скидання.



1
Ви зберігаєте копію списку у ObservableCollection, але втрачаєте можливість тонкого розрізнення у свого споживача. Якщо ви споживаєте ObervableCollection самостійно, ви, можливо, зможете краще оптимізувати, розбираючись між "багато предметів додано" та скинути (що зазвичай змушує вас почати з нуля)
NobodysNightmare

@SamSaffron Посилання мертве.
AgentFire

Згадане зараз посилання @SamSaffron - docs.microsoft.com/en-us/archive/blogs/nathannesbit/…
habakuk

14

Вам доведеться обережно прив’язувати інтерфейс користувача до своєї власної колекції - клас CollectionView за замовчуванням підтримує лише одне повідомлення про елементи.


13

Підтвердження необхідності OnPropertyChanged("Count")та OnPropertyChanged("Item[]")дзвінків для того, щоб поводитись так, як відповідає ObservableCollection. Зауважте, що я не знаю, які наслідки, якщо ви не турбуєтесь!

Ось метод тестування, який показує, що є два події PropertyChange для кожного додавання у звичайній колекції, що спостерігається. Один за "Count"і один для "Item[]".

[TestMethod]
public void TestAddSinglesInOldObsevableCollection()
{
  int colChangedEvents = 0;
  int propChangedEvents = 0;
  var collection = new ObservableCollection<object>();
  collection.CollectionChanged += (sender, e) => { colChangedEvents++; };
  (collection as INotifyPropertyChanged).PropertyChanged += (sender, e) => { propChangedEvents++; };
  collection.Add(new object());
  collection.Add(new object());
  collection.Add(new object());
  Assert.AreEqual(3, colChangedEvents);
  Assert.AreEqual(6, propChangedEvents);
}

@Shimmy, поміняйте підставку для своєї колекції та змініть на діапазон додавання, і ви отримаєте нульові зміни Properties. Зауважте, що зміна колекції працює добре, але не виконує точно те, що робить ObservableCollection. Тож тест на яскраву колекцію виглядає приблизно так:

[TestMethod]
public void TestShimmyAddRange()
{
  int colChangedEvents = 0;
  int propChangedEvents = 0;
  var collection = new ShimmyObservableCollection<object>();
  collection.CollectionChanged += (sender, e) => { colChangedEvents++; };
  (collection as INotifyPropertyChanged).PropertyChanged += (sender, e) => { propChangedEvents++; };
  collection.AddRange(new[]{
    new object(), new object(), new object(), new object()}); //4 objects at once
  Assert.AreEqual(1, colChangedEvents);  //great, just one!
  Assert.AreEqual(2, propChangedEvents); //fails, no events :(
}

FYI тут - код від InsertItem (його також називають Add) від ObservableCollection:

protected override void InsertItem(int index, T item)
{
  base.CheckReentrancy();
  base.InsertItem(index, item);
  base.OnPropertyChanged("Count");
  base.OnPropertyChanged("Item[]");
  base.OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
}

3

C # підсумував нащадок.

Більше читання: http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx

public sealed class ObservableCollectionEx<T> : ObservableCollection<T>
{
    #region Ctor

    public ObservableCollectionEx()
    {
    }

    public ObservableCollectionEx(List<T> list) : base(list)
    {
    }

    public ObservableCollectionEx(IEnumerable<T> collection) : base(collection)
    {
    }

    #endregion

    /// <summary> 
    /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). 
    /// </summary> 
    public void AddRange(
        IEnumerable<T> itemsToAdd,
        ECollectionChangeNotificationMode notificationMode = ECollectionChangeNotificationMode.Add)
    {
        if (itemsToAdd == null)
        {
            throw new ArgumentNullException("itemsToAdd");
        }
        CheckReentrancy();

        if (notificationMode == ECollectionChangeNotificationMode.Reset)
        {
            foreach (var i in itemsToAdd)
            {
                Items.Add(i);
            }

            OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

            return;
        }

        int startIndex = Count;
        var changedItems = itemsToAdd is List<T> ? (List<T>) itemsToAdd : new List<T>(itemsToAdd);
        foreach (var i in changedItems)
        {
            Items.Add(i);
        }

        OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, changedItems, startIndex));
    }

    public enum ECollectionChangeNotificationMode
    {
        /// <summary>
        /// Notifies that only a portion of data was changed and supplies the changed items (not supported by some elements,
        /// like CollectionView class).
        /// </summary>
        Add,

        /// <summary>
        /// Notifies that the entire collection was changed, does not supply the changed items (may be inneficient with large
        /// collections as requires the full update even if a small portion of items was added).
        /// </summary>
        Reset
    }
}

1
Оскільки блог, на який ви посилаєтесь, згадує, ListCollectionView (і, напевно, інші) не підтримує події зміни кількох елементів. Вам доведеться скористатися дією Скидання замість Додати тут.
Søren Boisen

2

Так, додавання вашої власної користувальницької колекції буде справедливим. Не забудьте підняти відповідні події незалежно від того, використовується він на даний момент користувальницьким інтерфейсом чи ні;) Вам доведеться підняти сповіщення про зміну власності для властивості "Item []" (необхідна з боку WPF та прив'язаного контролю), а також NotifyCollectionChangedEventArgs із додаванням набору елементів (ваш асортимент). Я робив такі речі (як і сортування підтримки та деякі інші речі) і не мав проблем із шарами Presentation та Code Behind.


2

Оскільки на ObservableCollection можна виконати ряд операцій, наприклад, Очистити спочатку Очистити, потім AddRange, а потім вставити елемент "Усі" для ComboBox, який закінчив із наступним рішенням:

public static class LinqExtensions
{
    public static ICollection<T> AddRange<T>(this ICollection<T> source, IEnumerable<T> addSource)
    {
        foreach(T item in addSource)
        {
            source.Add(item);
        }

        return source;
    }
}

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

І приклад, як ним користуватися:

MyDogs.Execute(items =>
{
    items.Clear();
    items.AddRange(Context.Dogs);
    items.Insert(0, new Dog { Id = 0, Name = "All Dogs" });
});

Повідомлення про скидання буде викликано лише один раз після того, як Execute закінчить обробку базового списку.


1

Ось додаткова допомога щодо зміни колекції та проблем із інтерфейсом користувача:


5
Хоча це теоретично може відповісти на питання, бажано було б сюди включити істотні частини відповіді та надати посилання для довідки.
Spontifixus

Посилання на посилання є хорошим ресурсом для користувацької реалізації списку, що підтримується, що підтримує AddRange () і безпечно для потоків.
Гейл Іверсон

1

ObservableRangeCollection повинен пройти тест на зразок

[Test]
public void TestAddRangeWhileBoundToListCollectionView()
{
    int collectionChangedEventsCounter = 0;
    int propertyChangedEventsCounter = 0;
    var collection = new ObservableRangeCollection<object>();

    collection.CollectionChanged += (sender, e) => { collectionChangedEventsCounter++; };
    (collection as INotifyPropertyChanged).PropertyChanged += (sender, e) => { propertyChangedEventsCounter++; };

    var list = new ListCollectionView(collection);

    collection.AddRange(new[] { new object(), new object(), new object(), new object() });

    Assert.AreEqual(4, collection.Count);
    Assert.AreEqual(1, collectionChangedEventsCounter);
    Assert.AreEqual(2, propertyChangedEventsCounter);
}

інакше ми отримуємо

System.NotSupportedException : Range actions are not supported.

під час використання з керуванням.

Я не бачу ідеального рішення, але NotifyCollectionChangedAction.Reset замість Add / Remove частково вирішує проблему. Дивіться http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx, як згадував net_prog


0

Ось модифікація прийнятої відповіді для забезпечення більшої функціональності.

RangeCollection.cs:

public class RangeCollection<T> : ObservableCollection<T>
{
    #region Members

    /// <summary>
    /// Occurs when a single item is added.
    /// </summary>
    public event EventHandler<ItemAddedEventArgs<T>> ItemAdded;

    /// <summary>
    /// Occurs when a single item is inserted.
    /// </summary>
    public event EventHandler<ItemInsertedEventArgs<T>> ItemInserted;

    /// <summary>
    /// Occurs when a single item is removed.
    /// </summary>
    public event EventHandler<ItemRemovedEventArgs<T>> ItemRemoved;

    /// <summary>
    /// Occurs when a single item is replaced.
    /// </summary>
    public event EventHandler<ItemReplacedEventArgs<T>> ItemReplaced;

    /// <summary>
    /// Occurs when items are added to this.
    /// </summary>
    public event EventHandler<ItemsAddedEventArgs<T>> ItemsAdded;

    /// <summary>
    /// Occurs when items are removed from this.
    /// </summary>
    public event EventHandler<ItemsRemovedEventArgs<T>> ItemsRemoved;

    /// <summary>
    /// Occurs when items are replaced within this.
    /// </summary>
    public event EventHandler<ItemsReplacedEventArgs<T>> ItemsReplaced;

    /// <summary>
    /// Occurs when entire collection is cleared.
    /// </summary>
    public event EventHandler<ItemsClearedEventArgs<T>> ItemsCleared;

    /// <summary>
    /// Occurs when entire collection is replaced.
    /// </summary>
    public event EventHandler<CollectionReplacedEventArgs<T>> CollectionReplaced;

    #endregion

    #region Helper Methods

    /// <summary>
    /// Throws exception if any of the specified objects are null.
    /// </summary>
    private void Check(params T[] Items)
    {
        foreach (T Item in Items)
        {
            if (Item == null)
            {
                throw new ArgumentNullException("Item cannot be null.");
            }
        }
    }

    private void Check(IEnumerable<T> Items)
    {
        if (Items == null) throw new ArgumentNullException("Items cannot be null.");
    }

    private void Check(IEnumerable<IEnumerable<T>> Items)
    {
        if (Items == null) throw new ArgumentNullException("Items cannot be null.");
    }

    private void RaiseChanged(NotifyCollectionChangedAction Action)
    {
        this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    #endregion

    #region Bulk Methods

    /// <summary> 
    /// Adds the elements of the specified collection to the end of this.
    /// </summary> 
    public void AddRange(IEnumerable<T> NewItems)
    {
        this.Check(NewItems);
        foreach (var i in NewItems) this.Items.Add(i);
        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
        this.OnItemsAdded(new ItemsAddedEventArgs<T>(NewItems));
    }

    /// <summary>
    /// Adds variable IEnumerable<T> to this.
    /// </summary>
    /// <param name="List"></param>
    public void AddRange(params IEnumerable<T>[] NewItems)
    {
        this.Check(NewItems);
        foreach (IEnumerable<T> Items in NewItems) foreach (T Item in Items) this.Items.Add(Item);
        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
        //TO-DO: Raise OnItemsAdded with combined IEnumerable<T>.
    }

    /// <summary> 
    /// Removes the first occurence of each item in the specified collection. 
    /// </summary> 
    public void Remove(IEnumerable<T> OldItems)
    {
        this.Check(OldItems);
        foreach (var i in OldItems) Items.Remove(i);
        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
        OnItemsRemoved(new ItemsRemovedEventArgs<T>(OldItems));
    }

    /// <summary>
    /// Removes all occurences of each item in the specified collection.
    /// </summary>
    /// <param name="itemsToRemove"></param>
    public void RemoveAll(IEnumerable<T> OldItems)
    {
        this.Check(OldItems);
        var set = new HashSet<T>(OldItems);
        var list = this as List<T>;
        int i = 0;
        while (i < this.Count) if (set.Contains(this[i])) this.RemoveAt(i); else i++;
        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
        OnItemsRemoved(new ItemsRemovedEventArgs<T>(OldItems));
    }

    /// <summary> 
    /// Replaces all occurences of a single item with specified item.
    /// </summary> 
    public void ReplaceAll(T Old, T New)
    {
        this.Check(Old, New);
        this.Replace(Old, New, false);
        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
        this.OnItemReplaced(new ItemReplacedEventArgs<T>(Old, New));
    }

    /// <summary> 
    /// Clears this and adds specified collection. 
    /// </summary> 
    public void ReplaceCollection(IEnumerable<T> NewItems, bool SupressEvent = false)
    {
        this.Check(NewItems);
        IEnumerable<T> OldItems = new List<T>(this.Items);
        this.Items.Clear();
        foreach (T Item in NewItems) this.Items.Add(Item);
        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
        this.OnReplaced(new CollectionReplacedEventArgs<T>(OldItems, NewItems));
    }

    private void Replace(T Old, T New, bool BreakFirst)
    {
        List<T> Cloned = new List<T>(this.Items);
        int i = 0;
        foreach (T Item in Cloned)
        {
            if (Item.Equals(Old))
            {
                this.Items.Remove(Item);
                this.Items.Insert(i, New);
                if (BreakFirst) break;
            }
            i++;
        }
    }

    /// <summary> 
    /// Replaces the first occurence of a single item with specified item.
    /// </summary> 
    public void Replace(T Old, T New)
    {
        this.Check(Old, New);
        this.Replace(Old, New, true);
        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
        this.OnItemReplaced(new ItemReplacedEventArgs<T>(Old, New));
    }

    #endregion

    #region  New Methods

    /// <summary>
    /// Removes a single item.
    /// </summary>
    /// <param name="Item"></param>
    public new void Remove(T Item)
    {
        this.Check(Item);
        base.Remove(Item);
        OnItemRemoved(new ItemRemovedEventArgs<T>(Item));
    }

    /// <summary>
    /// Removes a single item at specified index.
    /// </summary>
    /// <param name="i"></param>
    public new void RemoveAt(int i)
    {
        T OldItem = this.Items[i]; //This will throw first if null
        base.RemoveAt(i);
        OnItemRemoved(new ItemRemovedEventArgs<T>(OldItem));
    }

    /// <summary>
    /// Clears this.
    /// </summary>
    public new void Clear()
    {
        IEnumerable<T> OldItems = new List<T>(this.Items);
        this.Items.Clear();
        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
        this.OnCleared(new ItemsClearedEventArgs<T>(OldItems));
    }

    /// <summary>
    /// Adds a single item to end of this.
    /// </summary>
    /// <param name="t"></param>
    public new void Add(T Item)
    {
        this.Check(Item);
        base.Add(Item);
        this.OnItemAdded(new ItemAddedEventArgs<T>(Item));
    }

    /// <summary>
    /// Inserts a single item at specified index.
    /// </summary>
    /// <param name="i"></param>
    /// <param name="t"></param>
    public new void Insert(int i, T Item)
    {
        this.Check(Item);
        base.Insert(i, Item);
        this.OnItemInserted(new ItemInsertedEventArgs<T>(Item, i));
    }

    /// <summary>
    /// Returns list of T.ToString().
    /// </summary>
    /// <returns></returns>
    public new IEnumerable<string> ToString()
    {
        foreach (T Item in this) yield return Item.ToString();
    }

    #endregion

    #region Event Methods

    private void OnItemAdded(ItemAddedEventArgs<T> i)
    {
        if (this.ItemAdded != null) this.ItemAdded(this, new ItemAddedEventArgs<T>(i.NewItem));
    }

    private void OnItemInserted(ItemInsertedEventArgs<T> i)
    {
        if (this.ItemInserted != null) this.ItemInserted(this, new ItemInsertedEventArgs<T>(i.NewItem, i.Index));
    }

    private void OnItemRemoved(ItemRemovedEventArgs<T> i)
    {
        if (this.ItemRemoved != null) this.ItemRemoved(this, new ItemRemovedEventArgs<T>(i.OldItem));
    }

    private void OnItemReplaced(ItemReplacedEventArgs<T> i)
    {
        if (this.ItemReplaced != null) this.ItemReplaced(this, new ItemReplacedEventArgs<T>(i.OldItem, i.NewItem));
    }

    private void OnItemsAdded(ItemsAddedEventArgs<T> i)
    {
        if (this.ItemsAdded != null) this.ItemsAdded(this, new ItemsAddedEventArgs<T>(i.NewItems));
    }

    private void OnItemsRemoved(ItemsRemovedEventArgs<T> i)
    {
        if (this.ItemsRemoved != null) this.ItemsRemoved(this, new ItemsRemovedEventArgs<T>(i.OldItems));
    }

    private void OnItemsReplaced(ItemsReplacedEventArgs<T> i)
    {
        if (this.ItemsReplaced != null) this.ItemsReplaced(this, new ItemsReplacedEventArgs<T>(i.OldItems, i.NewItems));
    }

    private void OnCleared(ItemsClearedEventArgs<T> i)
    {
        if (this.ItemsCleared != null) this.ItemsCleared(this, new ItemsClearedEventArgs<T>(i.OldItems));
    }

    private void OnReplaced(CollectionReplacedEventArgs<T> i)
    {
        if (this.CollectionReplaced != null) this.CollectionReplaced(this, new CollectionReplacedEventArgs<T>(i.OldItems, i.NewItems));
    }

    #endregion

    #region RangeCollection

    /// <summary> 
    /// Initializes a new instance. 
    /// </summary> 
    public RangeCollection() : base() { }

    /// <summary> 
    /// Initializes a new instance from specified enumerable. 
    /// </summary> 
    public RangeCollection(IEnumerable<T> Collection) : base(Collection) { }

    /// <summary> 
    /// Initializes a new instance from specified list.
    /// </summary> 
    public RangeCollection(List<T> List) : base(List) { }

    /// <summary>
    /// Initializes a new instance with variable T.
    /// </summary>
    public RangeCollection(params T[] Items) : base()
    {
        this.AddRange(Items);
    }

    /// <summary>
    /// Initializes a new instance with variable enumerable.
    /// </summary>
    public RangeCollection(params IEnumerable<T>[] Items) : base()
    {
        this.AddRange(Items);
    }

    #endregion
}

Класи подій:

public class CollectionReplacedEventArgs<T> : ReplacedEventArgs<T>
{
    public CollectionReplacedEventArgs(IEnumerable<T> Old, IEnumerable<T> New) : base(Old, New) { }
}

public class ItemAddedEventArgs<T> : EventArgs
{
    public T NewItem;
    public ItemAddedEventArgs(T t)
    {
        this.NewItem = t;
    }
}

public class ItemInsertedEventArgs<T> : EventArgs
{
    public int Index;
    public T NewItem;
    public ItemInsertedEventArgs(T t, int i)
    {
        this.NewItem = t;
        this.Index = i;
    }
}

public class ItemRemovedEventArgs<T> : EventArgs
{
    public T OldItem;
    public ItemRemovedEventArgs(T t)
    {
        this.OldItem = t;
    }
}

public class ItemReplacedEventArgs<T> : EventArgs
{
    public T OldItem;
    public T NewItem;
    public ItemReplacedEventArgs(T Old, T New)
    {
        this.OldItem = Old;
        this.NewItem = New;
    }
}

public class ItemsAddedEventArgs<T> : EventArgs
{
    public IEnumerable<T> NewItems;
    public ItemsAddedEventArgs(IEnumerable<T> t)
    {
        this.NewItems = t;
    }
}

public class ItemsClearedEventArgs<T> : RemovedEventArgs<T>
{
    public ItemsClearedEventArgs(IEnumerable<T> Old) : base(Old) { }
}

public class ItemsRemovedEventArgs<T> : RemovedEventArgs<T>
{
    public ItemsRemovedEventArgs(IEnumerable<T> Old) : base(Old) { }
}

public class ItemsReplacedEventArgs<T> : ReplacedEventArgs<T>
{
    public ItemsReplacedEventArgs(IEnumerable<T> Old, IEnumerable<T> New) : base(Old, New) { }
}

public class RemovedEventArgs<T> : EventArgs
{
    public IEnumerable<T> OldItems;
    public RemovedEventArgs(IEnumerable<T> Old)
    {
        this.OldItems = Old;
    }
}

public class ReplacedEventArgs<T> : EventArgs
{
    public IEnumerable<T> OldItems;
    public IEnumerable<T> NewItems;
    public ReplacedEventArgs(IEnumerable<T> Old, IEnumerable<T> New)
    {
        this.OldItems = Old;
        this.NewItems = New;
    }
}

Примітка: Я не вручну підняти OnCollectionChangedв базових методів , так як вона тільки можна створити з CollectionChangedEventArgsдопомогою Resetдії. Якщо ви спробуєте підняти, OnCollectionChangedвикористовуючи Resetдля зміни одного елемента, контроль елементів буде мерехтіти, чого ви хочете уникати.


-1

Ви також можете використовувати цей код для розширення ObservableCollection:

public static class ObservableCollectionExtend
{
    public static void AddRange<TSource>(this ObservableCollection<TSource> source, IEnumerable<TSource> items)
    {
        foreach (var item in items)
        {
            source.Add(item);
        }
    }
}

Тоді вам не потрібно змінювати клас у існуючому коді.


11
Це пояснює, як працюють методи розширення на C #, але не допомагає в основній суті питання, що полягає в тому, як додати кілька елементів без отримання декількох повідомлень про зміни.
Чарлі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.