ObservableCollection不支持AddRange方法,所以我得到通知每个项目添加,除了INotifyCollectionChanging?
我希望能够添加一个范围,并获得整个批量更新。
我也希望能够在完成之前取消操作(即,除了“更改”之外的集合更改)。
相关问题哪个.net集合一次添加多个对象并得到通知?
请参阅更新和优化的C#7版本 。 我不想删除VB.NET版本,所以我只是把它张贴在一个单独的答案。
转到更新的版本
似乎没有支持,我自己实现,FYI,希望它是有帮助的:
我更新了VB版本,从现在开始,在更改集合之前引发了一个事件,所以您可以后悔(在使用DataGrid
, ListView
等等时很有用,您可以向用户显示“Are you sure”确认) 更新的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
我认为AddRange更好地实现,如下所示:
public void AddRange(IEnumerable<T> collection) { foreach (var i in collection) Items.Add(i); OnCollectionChanged( new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); }
它为您节省了一份清单副本。 另外,如果你想微观优化,你可以添加多达N个项目,如果超过N个项目添加重置。
certificate需要OnPropertyChanged("Count")
和OnPropertyChanged("Item[]")
调用以按照ObservableCollection
行为。 请注意,如果您不打扰,我不知道后果是什么。
下面是一个testing方法,显示在普通可观察集合中每个添加有两个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,交换标准为您的collections和改变到一个添加范围,你会得到零PropertyChanges。 请注意,集合更改可以正常工作,但不能完全实现ObservableCollection的function。 所以testingshimmy集合看起来像这样:
[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 :( }
仅供参考这里是来自ObservableCollection的InsertItem(也称为Add)的代码:
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); }
您必须小心地将UI绑定到您的自定义集合 – Default CollectionView类仅支持单个项目通知。
这里是我的优化版本的ObservableRangeCollection
(James Montemagno的优化版本)。
它的执行速度非常快,并且意味着尽可能重复使用现有的元素,避免不必要的事件,或者尽可能将其分配到一个事件中。 ReplaceRange
方法用适当的索引replace/删除/添加所需的元素,并对可能的事件进行批处理。
对Xamarin.Forms UI进行testing,对大集合进行非常频繁的更新(每秒5-7次更新)。
RAW代码 – 以原始格式打开,然后按Ctrl + A全选,然后按Ctrl + C进行复制。
namespace System.Collections.ObjectModel { using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; 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> { private const string CountName = nameof(Count); private const string IndexerName = "Item[]"; /// <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) { } /// <summary> /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). /// </summary> public void AddRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Add) { if (notificationMode != NotifyCollectionChangedAction.Add && notificationMode != NotifyCollectionChangedAction.Reset) throw new ArgumentException("Mode must be either Add or Reset for AddRange.", nameof(notificationMode)); if (collection == null) throw new ArgumentNullException(nameof(collection)); if (collection is ICollection<T> list) { if (list.Count == 0) return; } else if (!collection.Any()) return; else list = new List<T>(collection); CheckReentrancy(); int startIndex = Count; foreach (var i in collection) Items.Add(i); NotifyProperties(); if (notificationMode == NotifyCollectionChangedAction.Reset) OnCollectionReset(); else OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list as IList ?? list.ToList(), startIndex)); } /// <summary> /// Called by base class Collection<T> when the list is being cleared; /// raises a CollectionChanged event to any listeners. /// </summary> protected override void ClearItems() { if (Count > 0) base.ClearItems(); } /// <summary> /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). /// </summary> public virtual void RemoveRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Remove) { if (notificationMode != NotifyCollectionChangedAction.Remove && notificationMode != NotifyCollectionChangedAction.Reset) throw new ArgumentException("Mode must be either Remove or Reset for RemoveRange.", nameof(notificationMode)); if (collection == null) throw new ArgumentNullException(nameof(collection)); if (Count == 0) return; if (collection is ICollection<T> list && list.Count == 0) return; else if (!collection.Any()) return; CheckReentrancy(); if (notificationMode == NotifyCollectionChangedAction.Reset) { foreach (var i in collection) Items.Remove(i); OnCollectionReset(); NotifyProperties(); return; } var removed = new Dictionary<int, List<T>>(); var curSegmentIndex = -1; foreach (var item in collection) { var index = IndexOf(item); if (index < 0) continue; Items.RemoveAt(index); if (!removed.TryGetValue(index - 1, out var segment) && !removed.TryGetValue(index, out segment)) { curSegmentIndex = index; removed[index] = new List<T> { item }; } else segment.Add(item); } if (Count == 0) OnCollectionReset(); else foreach (var segment in removed) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, segment.Value, segment.Key)); NotifyProperties(); } /// <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> /// <param name="noDuplicates"> /// Sets whether we should ignore items already in the collection when adding items. /// false (default) items already existing in the collection will be reused to increase performance. /// true - perform regular clear and add, and notify about a reset when done. /// </param> public void ReplaceRange(IEnumerable<T> collection, bool reset = false) { if (collection == null) throw new ArgumentNullException(nameof(collection)); if (collection is IList<T> list) { if (list.Count == 0) { Clear(); return; } } else if (!collection.Any()) { Clear(); return; } else list = new List<T>(collection); CheckReentrancy(); if (reset) { Items.Clear(); AddRange(collection, NotifyCollectionChangedAction.Reset); return; } var oldCount = Count; var lCount = list.Count; for (int i = 0; i < Math.Max(Count, lCount); i++) { if (i < Count && i < lCount) { T old = this[i], @new = list[i]; if (Equals(old, @new)) continue; else { Items[i] = @new; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, @new, @old, i)); } } else if (Count > lCount) { var removed = new Stack<T>(); for (var j = Count - 1; j >= i; j--) { removed.Push(this[j]); Items.RemoveAt(j); } OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed.ToList(), i)); break; } else { var added = new List<T>(); for (int j = i; j < list.Count; j++) { var @new = list[j]; Items.Add(@new); added.Add(@new); } OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, i)); break; } } NotifyProperties(Count != oldCount); } void OnCollectionReset() => OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); void NotifyProperties(bool count = true) { if (count) OnPropertyChanged(new PropertyChangedEventArgs(CountName)); OnPropertyChanged(new PropertyChangedEventArgs(IndexerName)); } } }
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 } }
是的,添加您自己的自定义可观察集合将是公平的。 不要忘记提出适当的事件,而不pipe它是否被UI使用;)你将不得不提出“Item []”属性(WPF方和绑定控件所需)的属性更改通知以及NotifyCollectionChangedEventArgs添加了一组项目(你的范围)。 我已经做了这样的事情(以及sorting支持和一些其他的东西),并没有问题与演示文稿和代码隐藏层。
因为在ObservableCollection上可能需要做很多操作,例如首先清除然后AddRange,然后为ComboBox插入“All”项目。我以下列解决scheme结束:
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" }); });
执行完成处理底层列表后,重置通知只会被调用一次。
以下是对收集更改和UI问题的其他帮助:
ObservableRangeCollection应该通过一个testing
[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.
同时使用一个控件。
我没有看到一个理想的解决scheme,但NotifyCollectionChangedAction.Reset而不是添加/删除部分解决问题。 请参阅net_prog中提到的http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx
这是对接受的答案进行修改以提供更多的function。
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 }
Events Classes:
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; } }
Note: I did not manually raise OnCollectionChanged
in the base methods because it appears only to be possible to create a CollectionChangedEventArgs
using the Reset
action. If you try to raise OnCollectionChanged
using Reset
for a single item change, your items control will appear to flicker, which is something you want to avoid.
You can also use this code to extend 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); } } }
Then you don't need to change class in existing code.