ObservableCollection不支持AddRange方法,所以每添加一个项目我都会被通知到,那么INotifyCollectionChanging怎么办呢?
ObservableCollection不支持AddRange方法,所以每添加一个项目我都会被通知到,那么INotifyCollectionChanging怎么办呢?
我想能够添加一个范围并且可以获得整个块的更新。
我还想能够在完成操作之前取消操作(即除了\'更改\'之外的收集更改)。
相关问题:
首先,请在.NET repo上的API请求上投票和评论。
下面是我优化过的ObservableRangeCollection
的版本(优化过的James Montemagno的版本)。
它的性能非常快,并且在可能时重复使用现有元素,并避免不必要的事件或在可能时将它们批处理到一个事件中。
ReplaceRange
方法通过适当的索引替换/删除/添加所需元素,并批处理可能的事件。
在Xamarin.Forms UI上测试,对于对大型集合的非常频繁的更新(每秒5-7次更新),效果非常好。
注意:
由于WPF不习惯使用范围操作,因此在WPF UI相关工作中使用下面的ObservableRangeCollection
将会抛出NotSupportedException
,例如将其绑定到ListBox
等(如果未绑定到UI,仍然可以使用ObservableRangeCollection
)。
但是,您可以使用WpfObservableRangeCollection
解决方法。
真正的解决方案是创建一个CollectionView
,它知道如何处理范围操作,但我还没有时间来实现这个方案。
原始代码 - 打开原始代码,然后按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 { ////// Implementation of a dynamic data collection based on generic Collection<T>, /// implementing INotifyCollectionChanged to notify listeners /// when items get added, removed or the whole list is refreshed. /// public class ObservableRangeCollection: ObservableCollection { //------------------------------------------------------ // // Private Fields // //------------------------------------------------------ #region Private Fields [NonSerialized] private DeferredEventsCollection _deferredEvents; #endregion Private Fields //------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors /// /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity. /// public ObservableRangeCollection() { } ////// 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. /// /// The collection whose elements are copied to the new list. ////// The elements are copied onto the ObservableCollection in the /// same order they are read by the enumerator of the collection. /// ///collection is a null reference public ObservableRangeCollection(IEnumerablecollection) : base(collection) { } /// /// Initializes a new instance of the ObservableCollection class /// that contains elements copied from the specified list /// /// The list whose elements are copied to the new list. ////// The elements are copied onto the ObservableCollection in the /// same order they are read by the enumerator of the list. /// ///list is a null reference public ObservableRangeCollection(Listlist) : base(list) { } #endregion Constructors //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods /// /// Adds the elements of the specified collection to the end of the /// /// The collection whose elements should be added to the end of the. /// . /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. /// /// public void AddRange(IEnumerable is null. collection) { InsertRange(Count, collection); } /// /// Inserts the elements of a collection into the /// The zero-based index at which the new elements should be inserted. /// The collection whose elements should be inserted into the Listat the specified index. /// . /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. /// /// is null. public void InsertRange(int index, IEnumerable is not in the collection range. 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 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 )Items; target.InsertRange(index, collection); OnEssentialPropertiesChanged(); if (!(collection is IList list)) list = new List (collection); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, index)); } /// /// Removes the first occurence of each item in the specified collection from the /// The items to remove. ///. /// public void RemoveRange(IEnumerable is null. collection) { if (collection == null) throw new ArgumentNullException(nameof(collection)); if (Count == 0) { return; } else if (collection is ICollection countable) { if (countable.Count == 0) return; else if (countable.Count == 1) using (IEnumerator enumerator = countable.GetEnumerator()) { enumerator.MoveNext(); Remove(enumerator.Current); return; } } else if (!(ContainsAny(collection))) { return; } CheckReentrancy(); var clusters = new Dictionary >(); var lastIndex = -1; List 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 { item }; } } OnEssentialPropertiesChanged(); if (Count == 0) OnCollectionReset(); else foreach (KeyValuePair > cluster in clusters) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster.Value, cluster.Key)); } /// /// Iterates over the collection and removes all items that satisfy the specified match. /// ///The complexity is O(n). /// ///Returns the number of elements that where ///public int RemoveAll(Predicate is null. match) { return RemoveAll(0, Count, match); } /// /// Iterates over the specified range within the collection and removes all items that satisfy the specified match. /// ///The complexity is O(n). /// The index of where to start performing the search. /// The number of items to iterate on. /// ///Returns the number of elements that where ////// is out of range. /// is out of range. public int RemoveAll(int index, int count, Predicate is null. 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 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 { 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; } /// /// Removes a range of elements from the /// The zero-based starting index of the range of elements to remove. /// The number of elements to remove. ///>. /// The specified range is exceeding the collection. 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, see constructors var items = (List )Items; List removedItems = items.GetRange(index, count); CheckReentrancy(); items.RemoveRange(index, count); OnEssentialPropertiesChanged(); if (Count == 0) OnCollectionReset(); else OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, index)); } /// /// Clears the current collection and replaces it with the specified collection, /// using the default /// The items to fill the collection with, after clearing it. ///. /// public void ReplaceRange(IEnumerable is null. collection) { ReplaceRange(0, Count, collection, EqualityComparer .Default); } /// /// Clears the current collection and replaces it with the specified collection, /// using the specified comparer to skip equal items. /// /// The items to fill the collection with, after clearing it. /// Anto 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. /// /// is null. public void ReplaceRange(IEnumerable is null. collection, IEqualityComparer comparer) { ReplaceRange(0, Count, collection, comparer); } /// /// Removes the specified range and inserts the specified collection, /// ignoring equal items (using /// The index of where to start the replacement. /// The number of items to be replaced. /// The collection to insert in that location. ///). /// /// is out of range. /// is out of range. public void ReplaceRange(int index, int count, IEnumerable is null. collection) { ReplaceRange(index, count, collection, EqualityComparer .Default); } /// /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact. /// /// The index of where to start the replacement. /// The number of items to be replaced. /// The collection to insert in that location. /// The comparer to use when checking for equal items. ////// is out of range. /// is out of range. /// is null. public void ReplaceRange(int index, int count, IEnumerable is null. collection, IEqualityComparer 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 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 list)) list = new List (collection); using (BlockReentrancy()) using (DeferEvents()) { var rangeCount = index + count; var addedCount = list.Count; var changesMade = false; List 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 { @new }; oldCluster = new List { old }; } else { newCluster.Add(@new); oldCluster.Add(old); } changesMade = true; } } OnRangeReplaced(i, newCluster, oldCluster); //exceeding position if (count != addedCount) { var items = (List )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 /// /// Called by base class Collection<T> when the list is being cleared; /// raises a CollectionChanged event to any listeners. /// protected override void ClearItems() { if (Count == 0) return; CheckReentrancy(); base.ClearItems(); OnEssentialPropertiesChanged(); OnCollectionReset(); } ////// Called by base class Collection<T> when an item is set in list; /// raises a CollectionChanged event to any listeners. /// 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); } ////// Raise CollectionChanged event to any listeners. /// Properties/methods modifying this ObservableCollection will raise /// a collection changed event through this virtual method. /// ////// When overriding this method, either call its base implementation /// or call 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 ///to guard against reentrant collection changes. /// /// Helper function to determine if a collection contains any elements. /// /// The collection to evaluate. ///private static bool ContainsAny(IEnumerable collection) { using (IEnumerator enumerator = collection.GetEnumerator()) return enumerator.MoveNext(); } /// /// Helper to raise Count property and the Indexer property. /// private void OnEssentialPropertiesChanged() { OnPropertyChanged(EventArgsCache.CountPropertyChanged); OnIndexerPropertyChanged(); } ////// /// Helper to raise a PropertyChanged event for the Indexer property /// /// private void OnIndexerPropertyChanged() => OnPropertyChanged(EventArgsCache.IndexerPropertyChanged); ////// Helper to raise CollectionChanged event to any listeners /// private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); ////// Helper to raise CollectionChanged event with action == Reset to any listeners /// private void OnCollectionReset() => OnCollectionChanged(EventArgsCache.ResetCollectionChanged); ////// Helper to raise event for clustered action and clear cluster. /// /// The index of the item following the replacement block. /// /// //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerablecollection, IEqualityComparer comparer), //move when supported language version updated. private void OnRangeReplaced(int followingItemIndex, ICollection newCluster, ICollection oldCluster) { if (oldCluster == null || oldCluster.Count == 0) { Debug.Assert(newCluster == null || newCluster.Count == 0); return; } OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Replace, new List (newCluster), new List (oldCluster), followingItemIndex - oldCluster.Count)); oldCluster.Clear(); newCluster.Clear(); } #endregion Private Methods //------------------------------------------------------ // // Private Types // //------------------------------------------------------ #region Private Types private sealed class DeferredEventsCollection : List , IDisposable { private readonly ObservableRangeCollection _collection; public DeferredEventsCollection(ObservableRangeCollection 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 } /// /// To be kept outside 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); } }, since otherwise, a new instance will be created for each generic type used. ///
请参考更新和优化的C# 7版本。我不想删除VB.NET版本,所以我只是把它单独发布了。
转到更新的版本
似乎它不被支持,我自己实现了一个,供您参考,希望能有所帮助:
我更新了VB版本,现在在更改集合之前会引发事件,以便您可以后悔(与DataGrid
,ListView
等一起使用时有用,您可以向用户显示“确定吗”确认),更新的VB版本在此消息底部。
请原谅屏幕太窄以容纳我的代码,我也不喜欢。
VB.NET:
Imports System.Collections.Specialized Namespace System.Collections.ObjectModel '''''' Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. ''' '''Public Class ObservableRangeCollection(Of T) : Inherits System.Collections.ObjectModel.ObservableCollection(Of T) ''' ''' Adds the elements of the specified collection to the end of the ObservableCollection(Of T). ''' 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 '''''' Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). ''' 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 '''''' Clears the current collection and replaces it with the specified item. ''' Public Sub Replace(ByVal item As T) ReplaceRange(New T() {item}) End Sub '''''' Clears the current collection and replaces it with the specified collection. ''' 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 '''''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. ''' '''Public Sub New() MyBase.New() End Sub ''' ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. ''' ''' collection: The collection from which the elements are copied. '''The collection parameter cannot be null. 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; ////// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. /// ///public class ObservableRangeCollection : ObservableCollection { /// /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). /// public void AddRange(IEnumerablecollection) { if (collection == null) throw new ArgumentNullException("collection"); foreach (var i in collection) Items.Add(i); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). /// public void RemoveRange(IEnumerablecollection) { if (collection == null) throw new ArgumentNullException("collection"); foreach (var i in collection) Items.Remove(i); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// /// Clears the current collection and replaces it with the specified item. /// public void Replace(T item) { ReplaceRange(new T[] { item }); } ////// Clears the current collection and replaces it with the specified collection. /// public void ReplaceRange(IEnumerablecollection) { if (collection == null) throw new ArgumentNullException("collection"); Items.Clear(); foreach (var i in collection) Items.Add(i); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. /// public ObservableRangeCollection() : base() { } ////// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. /// /// collection: The collection from which the elements are copied. ///The collection parameter cannot be null. public ObservableRangeCollection(IEnumerablecollection) : 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) '''''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. ''' '''Public Sub New() MyBase.New() End Sub ''' ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. ''' ''' collection: The collection from which the elements are copied. '''The collection parameter cannot be null. Public Sub New(ByVal collection As IEnumerable(Of T)) MyBase.New(collection) End Sub '''''' Adds the elements of the specified collection to the end of the ObservableCollection(Of T). ''' 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 '''''' Inserts the collection at specified index. ''' 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 '''''' Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). ''' 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 '''''' Clears the current collection and replaces it with the specified item. ''' Public Sub Replace(ByVal item As T) ReplaceRange(New T() {item}) End Sub '''''' Clears the current collection and replaces it with the specified collection. ''' 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