9
9
namespace CodingNinja . Wpf . ObjectModel ;
10
10
11
11
/// <summary>
12
- /// Implementation of a dynamic data collection based on <see cref="Collection{T}"/>,
13
- /// implementing <see cref="INotifyCollectionChanged"/> to notify listeners
14
- /// when items get added, removed or the whole list is refreshed.
12
+ /// <see cref="ObservableCollection{T}"/> that supports bulk operations to avoid frequent update notification events.
15
13
/// </summary>
14
+ /// <typeparam name="T"></typeparam>
16
15
public class ObservableRangeCollection < T > : ObservableCollection < T >
17
16
{
18
17
//------------------------------------------------------
@@ -37,26 +36,26 @@ public class ObservableRangeCollection<T> : ObservableCollection<T>
37
36
#region Constructors
38
37
39
38
/// <summary>
40
- /// Initializes a new instance of <see cref="ObservableCollection {T}"/> that is empty and has default initial capacity.
39
+ /// Initializes a new instance of <see cref="ObservableRangeCollection {T}"/> that is empty and has default initial capacity.
41
40
/// </summary>
42
41
/// <param name="allowDuplicates">Whether duplicate items are allowed in the collection.</param>
43
- /// <param name="comparer">Support for <see cref="AllowDuplicates"/>.</param>
42
+ /// <param name="comparer">Supports for <see cref="AllowDuplicates"/>.</param>
44
43
public ObservableRangeCollection ( bool allowDuplicates = true , EqualityComparer < T > ? comparer = null )
45
44
{
46
45
AllowDuplicates = allowDuplicates ;
47
46
Comparer = comparer ?? EqualityComparer < T > . Default ;
48
47
}
49
48
50
49
/// <summary>
51
- /// Initializes a new instance of the <see cref="ObservableCollection {T}"/> class that contains
50
+ /// Initializes a new instance of the <see cref="ObservableRangeCollection {T}"/> class that contains
52
51
/// elements copied from the specified collection and has sufficient capacity
53
52
/// to accommodate the number of elements copied.
54
53
/// </summary>
55
54
/// <param name="collection">The collection whose elements are copied to the new list.</param>
56
55
/// <param name="allowDuplicates">Whether duplicate items are allowed in the collection.</param>
57
- /// <param name="comparer">Support for <see cref="AllowDuplicates"/>.</param>
56
+ /// <param name="comparer">Supports for <see cref="AllowDuplicates"/>.</param>
58
57
/// <remarks>
59
- /// The elements are copied onto the <see cref="ObservableCollection {T}"/> in the
58
+ /// The elements are copied onto the <see cref="ObservableRangeCollection {T}"/> in the
60
59
/// same order they are read by the enumerator of the collection.
61
60
/// </remarks>
62
61
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference.</exception>
@@ -67,14 +66,14 @@ public ObservableRangeCollection(IEnumerable<T> collection, bool allowDuplicates
67
66
}
68
67
69
68
/// <summary>
70
- /// Initializes a new instance of the <see cref="ObservableCollection {T}"/> class
69
+ /// Initializes a new instance of the <see cref="ObservableRangeCollection {T}"/> class
71
70
/// that contains elements copied from the specified list.
72
71
/// </summary>
73
72
/// <param name="list">The list whose elements are copied to the new list.</param>
74
73
/// <param name="allowDuplicates">Whether duplicate items are allowed in the collection.</param>
75
- /// <param name="comparer">Support for <see cref="AllowDuplicates"/>.</param>
74
+ /// <param name="comparer">Supports for <see cref="AllowDuplicates"/>.</param>
76
75
/// <remarks>
77
- /// The elements are copied onto the <see cref="ObservableCollection {T}"/> in the
76
+ /// The elements are copied onto the <see cref="ObservableRangeCollection {T}"/> in the
78
77
/// same order they are read by the enumerator of the list.
79
78
/// </remarks>
80
79
/// <exception cref="ArgumentNullException"><paramref name="list"/> is a null reference.</exception>
@@ -103,7 +102,7 @@ public ObservableRangeCollection(List<T> list, bool allowDuplicates = true, Equa
103
102
public bool AllowDuplicates { get ; set ; } = true ;
104
103
105
104
/// <summary>
106
- /// Support for <see cref="AllowDuplicates"/>.
105
+ /// Supports for <see cref="AllowDuplicates"/>.
107
106
/// </summary>
108
107
public EqualityComparer < T > Comparer { get ; }
109
108
@@ -124,10 +123,11 @@ public ObservableRangeCollection(List<T> list, bool allowDuplicates = true, Equa
124
123
/// The collection whose elements should be added to the end of the <see cref="ObservableCollection{T}"/>.
125
124
/// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
126
125
/// </param>
126
+ /// <returns>Returns the number of items successfully added.</returns>
127
127
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
128
- public void AddRange ( IEnumerable < T > collection )
128
+ public int AddRange ( IEnumerable < T > collection )
129
129
{
130
- InsertRange ( Count , collection ) ;
130
+ return InsertRange ( Count , collection ) ;
131
131
}
132
132
133
133
/// <summary>
@@ -138,9 +138,10 @@ public void AddRange(IEnumerable<T> collection)
138
138
/// The collection whose elements should be inserted into the <see cref="List{T}"/>.
139
139
/// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
140
140
/// </param>
141
+ /// <returns>Returns the number of items successfully inserted.</returns>
141
142
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
142
143
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is not in the collection range.</exception>
143
- public void InsertRange ( int index , IEnumerable < T > collection )
144
+ public int InsertRange ( int index , IEnumerable < T > collection )
144
145
{
145
146
ArgumentNullException . ThrowIfNull ( nameof ( collection ) ) ;
146
147
@@ -165,14 +166,14 @@ public void InsertRange(int index, IEnumerable<T> collection)
165
166
166
167
if ( limitedCount == 0 )
167
168
{
168
- return ;
169
+ return 0 ;
169
170
}
170
171
171
172
if ( limitedCount == 1 )
172
173
{
173
174
Add ( collection . First ( ) ) ;
174
175
175
- return ;
176
+ return 1 ;
176
177
}
177
178
178
179
CheckReentrancy ( ) ;
@@ -184,15 +185,18 @@ public void InsertRange(int index, IEnumerable<T> collection)
184
185
OnEssentialPropertiesChanged ( ) ;
185
186
186
187
// changedItems cannot be IEnumerable(lazy type).
187
- OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Add , collection . ToList ( ) , index ) ) ;
188
+ var changedItems = collection . ToList ( ) ;
189
+ OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Add , changedItems , index ) ) ;
190
+
191
+ return changedItems . Count ;
188
192
}
189
193
190
194
/// <summary>
191
195
/// Iterates over the collection and removes all items that satisfy the specified match.
192
196
/// </summary>
193
197
/// <remarks>The complexity is O(n).</remarks>
194
- /// <param name="match">Match the item to be removed </param>
195
- /// <returns>Returns the number of elements that where </returns>
198
+ /// <param name="match">A function to test each element for a condition. </param>
199
+ /// <returns>Returns the number of items successfully removed. </returns>
196
200
/// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
197
201
public int RemoveAll ( Predicate < T > match )
198
202
{
@@ -206,8 +210,8 @@ public int RemoveAll(Predicate<T> match)
206
210
/// <remarks>The complexity is O(n).</remarks>
207
211
/// <param name="index">The index of where to start performing the search.</param>
208
212
/// <param name="count">The number of items to iterate on.</param>
209
- /// <param name="match">Match the item to be removed .</param>
210
- /// <returns>Returns the number of elements that where .</returns>
213
+ /// <param name="match">A function to test each element for a condition .</param>
214
+ /// <returns>Returns the number of items successfully removed .</returns>
211
215
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
212
216
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
213
217
/// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
@@ -225,7 +229,7 @@ public int RemoveAll(int index, int count, Predicate<T> match)
225
229
226
230
if ( index + count > Count )
227
231
{
228
- throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
232
+ throw new ArgumentException ( $ " { nameof ( index ) } + { nameof ( count ) } must be less than or equal to the ObservableCollection.Count." ) ;
229
233
}
230
234
231
235
ArgumentNullException . ThrowIfNull ( nameof ( match ) ) ;
@@ -292,42 +296,44 @@ public int RemoveAll(int index, int count, Predicate<T> match)
292
296
/// <para>NOTE: Removed items starting index is not set because items are not guaranteed to be consecutive.</para>
293
297
/// </summary>
294
298
/// <param name="collection">The items to remove.</param>
299
+ /// <returns>Returns the number of items successfully removed.</returns>
295
300
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
296
- public void RemoveRange ( IEnumerable < T > collection )
301
+ public int RemoveRange ( IEnumerable < T > collection )
297
302
{
298
303
ArgumentNullException . ThrowIfNull ( nameof ( collection ) ) ;
299
304
300
305
if ( Count == 0 )
301
306
{
302
- return ;
307
+ return 0 ;
303
308
}
304
309
305
310
int limitedCount = collection . Take ( 2 ) . Count ( ) ;
306
311
307
312
if ( limitedCount == 0 )
308
313
{
309
- return ;
314
+ return 0 ;
310
315
}
311
316
312
317
if ( limitedCount == 1 )
313
318
{
314
- Remove ( collection . First ( ) ) ;
319
+ bool removed = Remove ( collection . First ( ) ) ;
315
320
316
- return ;
321
+ return removed ? 1 : 0 ;
317
322
}
318
323
319
324
CheckReentrancy ( ) ;
320
325
321
- bool raiseEvents = false ;
326
+ int removedCount = 0 ;
322
327
323
328
foreach ( var item in collection )
324
329
{
325
- raiseEvents |= Items . Remove ( item ) ;
330
+ bool removed = Items . Remove ( item ) ;
331
+ removedCount += removed ? 1 : 0 ;
326
332
}
327
333
328
- if ( ! raiseEvents )
334
+ if ( removedCount == 0 )
329
335
{
330
- return ;
336
+ return 0 ;
331
337
}
332
338
333
339
OnEssentialPropertiesChanged ( ) ;
@@ -341,6 +347,8 @@ public void RemoveRange(IEnumerable<T> collection)
341
347
// changedItems cannot be IEnumerable(lazy type).
342
348
OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Remove , collection . ToList ( ) ) ) ;
343
349
}
350
+
351
+ return removedCount ;
344
352
}
345
353
346
354
/// <summary>
@@ -363,7 +371,7 @@ public void RemoveRange(int index, int count)
363
371
364
372
if ( index + count > Count )
365
373
{
366
- throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
374
+ throw new ArgumentException ( $ " { nameof ( index ) } + { nameof ( count ) } must be less than or equal to the ObservableCollection.Count." ) ;
367
375
}
368
376
369
377
if ( count == 0 )
@@ -378,6 +386,13 @@ public void RemoveRange(int index, int count)
378
386
return ;
379
387
}
380
388
389
+ if ( index == 0 && count == Count )
390
+ {
391
+ Clear ( ) ;
392
+
393
+ return ;
394
+ }
395
+
381
396
// Items will always be List<T>, see constructors.
382
397
var items = ( List < T > ) Items ;
383
398
var removedItems = items . GetRange ( index , count ) ;
@@ -388,14 +403,7 @@ public void RemoveRange(int index, int count)
388
403
389
404
OnEssentialPropertiesChanged ( ) ;
390
405
391
- if ( Count == 0 )
392
- {
393
- OnCollectionReset ( ) ;
394
- }
395
- else
396
- {
397
- OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Remove , removedItems , index ) ) ;
398
- }
406
+ OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Remove , removedItems , index ) ) ;
399
407
}
400
408
401
409
/// <summary>
@@ -419,7 +427,9 @@ public void ReplaceRange(IEnumerable<T> collection)
419
427
420
428
/// <summary>
421
429
/// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact.
430
+ /// <para>When index and count are equal to 0, it is equivalent to InsertRange(0, collection).</para>
422
431
/// </summary>
432
+ /// <remarks>This method is roughly equivalent to <see cref="RemoveRange(Int32, Int32)"/> then <see cref="InsertRange(Int32, IEnumerable{T})"/>.</remarks>
423
433
/// <param name="index">The index of where to start the replacement.</param>
424
434
/// <param name="count">The number of items to be replaced.</param>
425
435
/// <param name="collection">The collection to insert in that location.</param>
@@ -461,7 +471,7 @@ void OnRangeReplaced(int followingItemIndex, ICollection<T> newCluster, ICollect
461
471
462
472
if ( index + count > Count )
463
473
{
464
- throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
474
+ throw new ArgumentException ( $ " { nameof ( index ) } + { nameof ( count ) } must be less than or equal to the ObservableCollection.Count." ) ;
465
475
}
466
476
467
477
ArgumentNullException . ThrowIfNull ( nameof ( collection ) ) ;
@@ -597,10 +607,7 @@ protected override void ClearItems()
597
607
return ;
598
608
}
599
609
600
- CheckReentrancy ( ) ;
601
610
base . ClearItems ( ) ;
602
- OnEssentialPropertiesChanged ( ) ;
603
- OnCollectionReset ( ) ;
604
611
}
605
612
606
613
/// <summary>
@@ -659,12 +666,7 @@ protected override void SetItem(int index, T item)
659
666
return ;
660
667
}
661
668
662
- CheckReentrancy ( ) ;
663
- var oldItem = this [ index ] ;
664
669
base . SetItem ( index , item ) ;
665
-
666
- OnIndexerPropertyChanged ( ) ;
667
- OnCollectionChanged ( NotifyCollectionChangedAction . Replace , oldItem ! , item ! , index ) ;
668
670
}
669
671
670
672
#endregion Protected Methods
@@ -677,14 +679,6 @@ protected override void SetItem(int index, T item)
677
679
678
680
#region Private Methods
679
681
680
- /// <summary>
681
- /// Helper to raise CollectionChanged event to any listeners.
682
- /// </summary>
683
- private void OnCollectionChanged ( NotifyCollectionChangedAction action , object oldItem , object newItem , int index )
684
- {
685
- OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( action , newItem , oldItem , index ) ) ;
686
- }
687
-
688
682
/// <summary>
689
683
/// Helper to raise CollectionChanged event with action == Reset to any listeners.
690
684
/// </summary>
0 commit comments