Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit da296db

Browse files
committed
Update
1 parent 0ba347d commit da296db

File tree

3 files changed

+58
-64
lines changed

3 files changed

+58
-64
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# WpfObservableRangeCollection
22

3-
Provides ObservableRangeCollection and its WPF version, including AddRange, InsertRange, RemoveRange, Replace/ReplaceRange methods for bulk operation, but only update the notification once.
3+
Provides ObservableRangeCollection and its WPF version, including AddRange, InsertRange, RemoveRange/RemoveAll, Replace/ReplaceRange methods for bulk operation to avoid frequent update notification events.
44

55
---
66

@@ -23,10 +23,10 @@ I've searched the web for some ObservableCollections that have *Range methods, b
2323
- System.InvalidOperationException: The "x" index in the collection change event is not valid for collections of size "y".
2424
- More? I'm not sure. I forgot.
2525

26-
If the `NotSupportedException` still occurred, try using `BindingOperations.EnableCollectionSynchronization(IEnumerable, Object)`.
27-
2826
In the end, I chose `weitzhandler/RangeObservableCollection` and `weitzhandler/WpfObservableRangeCollection` and made slight changes to the code, and finally, I didn't encounter any problems, for now.
2927

28+
> If the `NotSupportedException` still occurred, try using `BindingOperations.EnableCollectionSynchronization(IEnumerable, Object)`.
29+
3030
## Seealso
3131
- [Cysharp/ObservableCollections](https://github.com/Cysharp/ObservableCollections)
3232
- [ENikS/ObservableCollectionEx](https://github.com/ENikS/ObservableCollectionEx)

WpfObservableRangeCollection/ObservableRangeCollection.cs

Lines changed: 52 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@
99
namespace CodingNinja.Wpf.ObjectModel;
1010

1111
/// <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.
1513
/// </summary>
14+
/// <typeparam name="T"></typeparam>
1615
public class ObservableRangeCollection<T> : ObservableCollection<T>
1716
{
1817
//------------------------------------------------------
@@ -37,26 +36,26 @@ public class ObservableRangeCollection<T> : ObservableCollection<T>
3736
#region Constructors
3837

3938
/// <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.
4140
/// </summary>
4241
/// <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>
4443
public ObservableRangeCollection(bool allowDuplicates = true, EqualityComparer<T>? comparer = null)
4544
{
4645
AllowDuplicates = allowDuplicates;
4746
Comparer = comparer ?? EqualityComparer<T>.Default;
4847
}
4948

5049
/// <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
5251
/// elements copied from the specified collection and has sufficient capacity
5352
/// to accommodate the number of elements copied.
5453
/// </summary>
5554
/// <param name="collection">The collection whose elements are copied to the new list.</param>
5655
/// <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>
5857
/// <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
6059
/// same order they are read by the enumerator of the collection.
6160
/// </remarks>
6261
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference.</exception>
@@ -67,14 +66,14 @@ public ObservableRangeCollection(IEnumerable<T> collection, bool allowDuplicates
6766
}
6867

6968
/// <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
7170
/// that contains elements copied from the specified list.
7271
/// </summary>
7372
/// <param name="list">The list whose elements are copied to the new list.</param>
7473
/// <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>
7675
/// <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
7877
/// same order they are read by the enumerator of the list.
7978
/// </remarks>
8079
/// <exception cref="ArgumentNullException"><paramref name="list"/> is a null reference.</exception>
@@ -103,7 +102,7 @@ public ObservableRangeCollection(List<T> list, bool allowDuplicates = true, Equa
103102
public bool AllowDuplicates { get; set; } = true;
104103

105104
/// <summary>
106-
/// Support for <see cref="AllowDuplicates"/>.
105+
/// Supports for <see cref="AllowDuplicates"/>.
107106
/// </summary>
108107
public EqualityComparer<T> Comparer { get; }
109108

@@ -124,10 +123,11 @@ public ObservableRangeCollection(List<T> list, bool allowDuplicates = true, Equa
124123
/// The collection whose elements should be added to the end of the <see cref="ObservableCollection{T}"/>.
125124
/// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
126125
/// </param>
126+
/// <returns>Returns the number of items successfully added.</returns>
127127
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
128-
public void AddRange(IEnumerable<T> collection)
128+
public int AddRange(IEnumerable<T> collection)
129129
{
130-
InsertRange(Count, collection);
130+
return InsertRange(Count, collection);
131131
}
132132

133133
/// <summary>
@@ -138,9 +138,10 @@ public void AddRange(IEnumerable<T> collection)
138138
/// The collection whose elements should be inserted into the <see cref="List{T}"/>.
139139
/// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
140140
/// </param>
141+
/// <returns>Returns the number of items successfully inserted.</returns>
141142
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
142143
/// <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)
144145
{
145146
ArgumentNullException.ThrowIfNull(nameof(collection));
146147

@@ -165,14 +166,14 @@ public void InsertRange(int index, IEnumerable<T> collection)
165166

166167
if (limitedCount == 0)
167168
{
168-
return;
169+
return 0;
169170
}
170171

171172
if (limitedCount == 1)
172173
{
173174
Add(collection.First());
174175

175-
return;
176+
return 1;
176177
}
177178

178179
CheckReentrancy();
@@ -184,15 +185,18 @@ public void InsertRange(int index, IEnumerable<T> collection)
184185
OnEssentialPropertiesChanged();
185186

186187
// 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;
188192
}
189193

190194
/// <summary>
191195
/// Iterates over the collection and removes all items that satisfy the specified match.
192196
/// </summary>
193197
/// <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>
196200
/// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
197201
public int RemoveAll(Predicate<T> match)
198202
{
@@ -206,8 +210,8 @@ public int RemoveAll(Predicate<T> match)
206210
/// <remarks>The complexity is O(n).</remarks>
207211
/// <param name="index">The index of where to start performing the search.</param>
208212
/// <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>
211215
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
212216
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
213217
/// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
@@ -225,7 +229,7 @@ public int RemoveAll(int index, int count, Predicate<T> match)
225229

226230
if (index + count > Count)
227231
{
228-
throw new ArgumentOutOfRangeException(nameof(index));
232+
throw new ArgumentException($"{nameof(index)} + {nameof(count)} must be less than or equal to the ObservableCollection.Count.");
229233
}
230234

231235
ArgumentNullException.ThrowIfNull(nameof(match));
@@ -292,42 +296,44 @@ public int RemoveAll(int index, int count, Predicate<T> match)
292296
/// <para>NOTE: Removed items starting index is not set because items are not guaranteed to be consecutive.</para>
293297
/// </summary>
294298
/// <param name="collection">The items to remove.</param>
299+
/// <returns>Returns the number of items successfully removed.</returns>
295300
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
296-
public void RemoveRange(IEnumerable<T> collection)
301+
public int RemoveRange(IEnumerable<T> collection)
297302
{
298303
ArgumentNullException.ThrowIfNull(nameof(collection));
299304

300305
if (Count == 0)
301306
{
302-
return;
307+
return 0;
303308
}
304309

305310
int limitedCount = collection.Take(2).Count();
306311

307312
if (limitedCount == 0)
308313
{
309-
return;
314+
return 0;
310315
}
311316

312317
if (limitedCount == 1)
313318
{
314-
Remove(collection.First());
319+
bool removed = Remove(collection.First());
315320

316-
return;
321+
return removed ? 1 : 0;
317322
}
318323

319324
CheckReentrancy();
320325

321-
bool raiseEvents = false;
326+
int removedCount = 0;
322327

323328
foreach (var item in collection)
324329
{
325-
raiseEvents |= Items.Remove(item);
330+
bool removed = Items.Remove(item);
331+
removedCount += removed ? 1 : 0;
326332
}
327333

328-
if (!raiseEvents)
334+
if (removedCount == 0)
329335
{
330-
return;
336+
return 0;
331337
}
332338

333339
OnEssentialPropertiesChanged();
@@ -341,6 +347,8 @@ public void RemoveRange(IEnumerable<T> collection)
341347
// changedItems cannot be IEnumerable(lazy type).
342348
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, collection.ToList()));
343349
}
350+
351+
return removedCount;
344352
}
345353

346354
/// <summary>
@@ -363,7 +371,7 @@ public void RemoveRange(int index, int count)
363371

364372
if (index + count > Count)
365373
{
366-
throw new ArgumentOutOfRangeException(nameof(index));
374+
throw new ArgumentException($"{nameof(index)} + {nameof(count)} must be less than or equal to the ObservableCollection.Count.");
367375
}
368376

369377
if (count == 0)
@@ -378,6 +386,13 @@ public void RemoveRange(int index, int count)
378386
return;
379387
}
380388

389+
if (index == 0 && count == Count)
390+
{
391+
Clear();
392+
393+
return;
394+
}
395+
381396
// Items will always be List<T>, see constructors.
382397
var items = (List<T>)Items;
383398
var removedItems = items.GetRange(index, count);
@@ -388,14 +403,7 @@ public void RemoveRange(int index, int count)
388403

389404
OnEssentialPropertiesChanged();
390405

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));
399407
}
400408

401409
/// <summary>
@@ -419,7 +427,9 @@ public void ReplaceRange(IEnumerable<T> collection)
419427

420428
/// <summary>
421429
/// 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>
422431
/// </summary>
432+
/// <remarks>This method is roughly equivalent to <see cref="RemoveRange(Int32, Int32)"/> then <see cref="InsertRange(Int32, IEnumerable{T})"/>.</remarks>
423433
/// <param name="index">The index of where to start the replacement.</param>
424434
/// <param name="count">The number of items to be replaced.</param>
425435
/// <param name="collection">The collection to insert in that location.</param>
@@ -461,7 +471,7 @@ void OnRangeReplaced(int followingItemIndex, ICollection<T> newCluster, ICollect
461471

462472
if (index + count > Count)
463473
{
464-
throw new ArgumentOutOfRangeException(nameof(index));
474+
throw new ArgumentException($"{nameof(index)} + {nameof(count)} must be less than or equal to the ObservableCollection.Count.");
465475
}
466476

467477
ArgumentNullException.ThrowIfNull(nameof(collection));
@@ -597,10 +607,7 @@ protected override void ClearItems()
597607
return;
598608
}
599609

600-
CheckReentrancy();
601610
base.ClearItems();
602-
OnEssentialPropertiesChanged();
603-
OnCollectionReset();
604611
}
605612

606613
/// <summary>
@@ -659,12 +666,7 @@ protected override void SetItem(int index, T item)
659666
return;
660667
}
661668

662-
CheckReentrancy();
663-
var oldItem = this[index];
664669
base.SetItem(index, item);
665-
666-
OnIndexerPropertyChanged();
667-
OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem!, item!, index);
668670
}
669671

670672
#endregion Protected Methods
@@ -677,14 +679,6 @@ protected override void SetItem(int index, T item)
677679

678680
#region Private Methods
679681

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-
688682
/// <summary>
689683
/// Helper to raise CollectionChanged event with action == Reset to any listeners.
690684
/// </summary>

WpfObservableRangeCollection/WpfObservableRangeCollection.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>net6.0-windows</TargetFramework>
@@ -8,13 +8,13 @@
88
<GenerateDocumentationFile>True</GenerateDocumentationFile>
99
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
1010
<PackageLicenseExpression>MIT</PackageLicenseExpression>
11-
<Description>Provides ObservableRangeCollection and its WPF version, including AddRange, InsertRange, RemoveRange, Replace/ReplaceRange methods for bulk operation, but only update the notification once.</Description>
11+
<Description>Provides ObservableRangeCollection and its WPF version, including AddRange, InsertRange, RemoveRange/RemoveAll, Replace/ReplaceRange methods for bulk operation to avoid frequent update notification events.</Description>
1212
<PackageProjectUrl>https://github.com/CodingOctocat/WpfObservableRangeCollection</PackageProjectUrl>
1313
<Authors>CodingNinja</Authors>
1414
<Title>$(AssemblyName)</Title>
1515
<PackageTags>WpfObservableRangeCollection;ObservableRangeCollection;ObservableCollection;CollectionView;Wpf</PackageTags>
1616
<PackageReadmeFile>README.md</PackageReadmeFile>
17-
<Version>2.0.1</Version>
17+
<Version>2.1.0</Version>
1818
<RepositoryUrl>$(PackageProjectUrl)</RepositoryUrl>
1919
</PropertyGroup>
2020

0 commit comments

Comments
 (0)