-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
526 additions
and
0 deletions.
There are no files selected for viewing
174 changes: 174 additions & 0 deletions
174
src/MyNet.Utilities/Collections/ExtendedObservableCollection.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.ObjectModel; | ||
using System.Collections.Specialized; | ||
using System.ComponentModel; | ||
using MyNet.Utilities.Deferring; | ||
|
||
namespace MyNet.Utilities.Collections; | ||
|
||
/// <summary> | ||
/// An override of observable collection which allows the suspension of notifications. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the item.</typeparam> | ||
public class ExtendedObservableCollection<T> : ObservableCollection<T> | ||
{ | ||
private bool _suspendCount; | ||
|
||
private bool _suspendNotifications; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ObservableCollectionExtended{T}"/> class. | ||
/// </summary> | ||
public ExtendedObservableCollection() | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ObservableCollectionExtended{T}"/> class that contains elements copied from the specified list. | ||
/// </summary> | ||
/// <param name="list">The list from which the elements are copied.</param><exception cref="ArgumentNullException">The <paramref name="list"/> parameter cannot be null.</exception> | ||
public ExtendedObservableCollection(List<T> list) | ||
: base(list) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ObservableCollectionExtended{T}"/> class that contains elements copied from the specified collection. | ||
/// </summary> | ||
/// <param name="collection">The collection from which the elements are copied.</param><exception cref="ArgumentNullException">The <paramref name="collection"/> parameter cannot be null.</exception> | ||
public ExtendedObservableCollection(IEnumerable<T> collection) | ||
: base(collection) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Adds the elements of the specified collection to the end of the collection. | ||
/// </summary> | ||
/// <param name="collection">The collection whose elements should be added to the end of the List. The collection itself cannot be null, but it can contain elements that are null.</param> | ||
/// <exception cref="ArgumentNullException"><paramref name="collection" /> is null.</exception> | ||
public void AddRange(IEnumerable<T> collection) | ||
{ | ||
ArgumentNullException.ThrowIfNull(collection); | ||
|
||
foreach (var item in collection) | ||
{ | ||
Add(item); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Inserts the elements of a collection into the <see cref="ObservableCollectionExtended{T}" /> at the specified index. | ||
/// </summary> | ||
/// <param name="collection">Inserts the items at the specified index.</param> | ||
/// <param name="index">The zero-based index at which the new elements should be inserted.</param> | ||
/// <exception cref="ArgumentNullException"><paramref name="collection" /> is null.</exception> | ||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index" /> is less than 0.-or-<paramref name="index" /> is greater than Count.</exception> | ||
public void InsertRange(IEnumerable<T> collection, int index) | ||
{ | ||
ArgumentNullException.ThrowIfNull(collection); | ||
|
||
foreach (var item in collection) | ||
{ | ||
InsertItem(index++, item); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Clears the list and Loads the specified items. | ||
/// </summary> | ||
/// <param name="items">The items.</param> | ||
public void Load(IEnumerable<T> items) | ||
{ | ||
ArgumentNullException.ThrowIfNull(items); | ||
|
||
CheckReentrancy(); | ||
Clear(); | ||
|
||
foreach (var item in items) | ||
{ | ||
Add(item); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Removes a range of elements from the <see cref="ObservableCollectionExtended{T}"/>. | ||
/// </summary> | ||
/// <param name="index">The zero-based starting index of the range of elements to remove.</param><param name="count">The number of elements to remove.</param><exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.-or-<paramref name="count"/> is less than 0.</exception><exception cref="ArgumentException"><paramref name="index"/> and <paramref name="count"/> do not denote a valid range of elements in the <see cref="List{T}"/>.</exception> | ||
public void RemoveRange(int index, int count) | ||
{ | ||
for (var i = 0; i < count; i++) | ||
{ | ||
RemoveAt(index); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Suspends count notifications. | ||
/// </summary> | ||
/// <returns>A disposable when disposed will reset the count.</returns> | ||
public IDisposable SuspendCount() | ||
{ | ||
var count = Count; | ||
_suspendCount = true; | ||
return new Deferrer( | ||
() => | ||
{ | ||
_suspendCount = false; | ||
if (Count != count) | ||
{ | ||
OnPropertyChanged(new PropertyChangedEventArgs("Count")); | ||
} | ||
}).Defer(); | ||
} | ||
|
||
/// <summary> | ||
/// Suspends notifications. When disposed, a reset notification is fired. | ||
/// </summary> | ||
/// <returns>A disposable when disposed will reset notifications.</returns> | ||
public IDisposable SuspendNotifications() | ||
{ | ||
_suspendCount = true; | ||
_suspendNotifications = true; | ||
|
||
return new Deferrer( | ||
() => | ||
{ | ||
_suspendCount = false; | ||
_suspendNotifications = false; | ||
OnPropertyChanged(new PropertyChangedEventArgs("Count")); | ||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); | ||
}).Defer(); | ||
} | ||
|
||
/// <summary> | ||
/// Raises the <see cref="INotifyCollectionChanged.CollectionChanged" /> event. | ||
/// </summary> | ||
/// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param> | ||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) | ||
{ | ||
if (_suspendNotifications) | ||
{ | ||
return; | ||
} | ||
|
||
base.OnCollectionChanged(e); | ||
} | ||
|
||
/// <summary> | ||
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged" /> event. | ||
/// </summary> | ||
/// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param> | ||
protected override void OnPropertyChanged(PropertyChangedEventArgs e) | ||
{ | ||
ArgumentNullException.ThrowIfNull(e); | ||
|
||
if (_suspendCount && e.PropertyName == "Count") | ||
{ | ||
return; | ||
} | ||
|
||
base.OnPropertyChanged(e); | ||
} | ||
} |
209 changes: 209 additions & 0 deletions
209
src/MyNet.Utilities/Collections/ObservableKeyedCollection.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
// Copyright (c) Stéphane ANDRE. All Right Reserved. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel; | ||
using System.Linq; | ||
|
||
namespace MyNet.Utilities.Collections | ||
{ | ||
public abstract class ObservableKeyedCollection<TKey, T> : SortableObservableCollection<T> | ||
where TKey : notnull | ||
{ | ||
private Dictionary<TKey, T>? _dict; | ||
|
||
protected ObservableKeyedCollection(IEnumerable<T> list) : this(list, null) { } | ||
|
||
protected ObservableKeyedCollection() : this([], null) { } | ||
|
||
protected ObservableKeyedCollection(IEqualityComparer<TKey> comparer) : this([], comparer) { } | ||
|
||
protected ObservableKeyedCollection(Func<T, object> sortSelector, ListSortDirection direction = ListSortDirection.Ascending) : base(sortSelector, direction) => Comparer = EqualityComparer<TKey>.Default; | ||
|
||
protected ObservableKeyedCollection(IEnumerable<T> list, IEqualityComparer<TKey>? comparer) : base(list) | ||
{ | ||
comparer ??= EqualityComparer<TKey>.Default; | ||
|
||
Comparer = comparer; | ||
} | ||
|
||
public IEqualityComparer<TKey> Comparer { get; } | ||
|
||
public T? this[TKey key] | ||
=> key switch | ||
{ | ||
null => throw new ArgumentNullException(nameof(key)), | ||
_ => _dict is not null && _dict.TryGetValue(key, out var value) ? value : Items.FirstOrDefault(x => Comparer.Equals(GetKeyForItem(x), key)) | ||
}; | ||
|
||
public bool Contains(TKey key) | ||
=> key switch | ||
{ | ||
null => throw new ArgumentNullException(nameof(key)), | ||
_ => _dict is not null ? _dict.ContainsKey(key) : Items.Any(x => Comparer.Equals(GetKeyForItem(x), key)) | ||
}; | ||
|
||
private bool ContainsItem(T item) | ||
{ | ||
TKey key; | ||
if (_dict is null || (key = GetKeyForItem(item)) is null) | ||
{ | ||
return Items.Contains(item); | ||
} | ||
|
||
var exist = _dict.TryGetValue(key, out var itemInDict); | ||
return exist && EqualityComparer<T>.Default.Equals(itemInDict, item); | ||
} | ||
|
||
public bool TryAdd(T item) | ||
{ | ||
var key = GetKeyForItem(item); | ||
if (key is null || _dict is null || _dict.ContainsKey(key)) return false; | ||
|
||
Add(item); | ||
|
||
return true; | ||
} | ||
|
||
public bool Remove(TKey key) | ||
{ | ||
if (key is null) | ||
{ | ||
throw new ArgumentNullException(nameof(key)); | ||
} | ||
|
||
if (_dict is not null) | ||
{ | ||
return _dict.ContainsKey(key) && Remove(_dict[key]); | ||
} | ||
|
||
for (var i = 0; i < Items.Count; i++) | ||
{ | ||
if (Comparer.Equals(GetKeyForItem(Items[i]), key)) | ||
{ | ||
RemoveItem(i); | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
protected IDictionary<TKey, T>? Dictionary => _dict; | ||
|
||
protected void ChangeItemKey(T item, TKey newKey) | ||
{ | ||
// check if the item exists in the collection | ||
if (!ContainsItem(item)) | ||
{ | ||
return; | ||
} | ||
|
||
var oldKey = GetKeyForItem(item); | ||
if (!Comparer.Equals(oldKey, newKey)) | ||
{ | ||
if (newKey is not null) | ||
{ | ||
AddKey(newKey, item); | ||
} | ||
|
||
if (oldKey is not null) | ||
{ | ||
RemoveKey(oldKey); | ||
} | ||
} | ||
} | ||
|
||
protected override void ClearItems() | ||
{ | ||
_dict?.Clear(); | ||
|
||
base.ClearItems(); | ||
} | ||
|
||
protected abstract TKey GetKeyForItem(T item); | ||
|
||
protected override void InsertItem(int index, T item) | ||
{ | ||
var key = GetKeyForItem(item); | ||
if (key is not null) | ||
{ | ||
AddKey(key, item); | ||
} | ||
base.InsertItem(index, item); | ||
} | ||
|
||
protected void InsertItemInItems(int index, T item) => base.InsertItem(index, item); | ||
|
||
protected override void RemoveItem(int index) | ||
{ | ||
var key = GetKeyForItem(Items[index]); | ||
if (key is not null) | ||
{ | ||
RemoveKey(key); | ||
} | ||
base.RemoveItem(index); | ||
} | ||
|
||
protected override void SetItem(int index, T item) | ||
=> ExecuteThreadSafe(() => | ||
{ | ||
var newKey = GetKeyForItem(item); | ||
var oldKey = GetKeyForItem(Items[index]); | ||
if (Comparer.Equals(oldKey, newKey)) | ||
{ | ||
if (newKey is not null && _dict is not null) | ||
{ | ||
_dict[newKey] = item; | ||
} | ||
} | ||
else | ||
{ | ||
if (newKey is not null) | ||
{ | ||
AddKey(newKey, item); | ||
} | ||
if (oldKey is not null) | ||
{ | ||
RemoveKey(oldKey); | ||
} | ||
} | ||
base.SetItem(index, item); | ||
}); | ||
|
||
private void AddKey(TKey key, T item) | ||
=> ExecuteThreadSafe(() => | ||
{ | ||
if (_dict is null) | ||
{ | ||
CreateDictionary(); | ||
} | ||
_dict?.Add(key, item); | ||
}); | ||
|
||
private void CreateDictionary() | ||
{ | ||
_dict = new Dictionary<TKey, T>(Comparer); | ||
foreach (var item in Items) | ||
{ | ||
var key = GetKeyForItem(item); | ||
if (key is not null) | ||
{ | ||
_dict.Add(key, item); | ||
} | ||
} | ||
} | ||
|
||
private void RemoveKey(TKey key) | ||
=> ExecuteThreadSafe(() => | ||
{ | ||
if (_dict is not null) | ||
{ | ||
_ = _dict.Remove(key); | ||
} | ||
}); | ||
|
||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/MyNet.Utilities/Collections/ReadOnlyObservableKeyedCollection.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Copyright (c) Stéphane ANDRE. All Right Reserved. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Collections.ObjectModel; | ||
|
||
namespace MyNet.Utilities.Collections | ||
{ | ||
public class ReadOnlyObservableKeyedCollection<TKey, T>(ObservableKeyedCollection<TKey, T> list) : ReadOnlyObservableCollection<T>(list) | ||
where TKey : notnull | ||
{ | ||
public T? this[TKey key] => ((ObservableKeyedCollection<TKey, T>)Items)[key]; | ||
} | ||
} |
Oops, something went wrong.