mirror of
https://github.com/chylex/.NET-Community-Toolkit.git
synced 2025-04-10 11:15:45 +02:00
simplify implementation of ReadOnlyObservableGroupedCollection
This commit is contained in:
parent
cca93e0eaf
commit
f8285cd1d5
Microsoft.Toolkit/Collections
@ -2,6 +2,7 @@
|
||||
// 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.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
@ -35,6 +36,17 @@ public ReadOnlyObservableGroup(ObservableGroup<TKey, TValue> group)
|
||||
Key = group.Key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the group.</param>
|
||||
/// <param name="collection">The collection of items to add in the group.</param>
|
||||
public ReadOnlyObservableGroup(TKey key, IEnumerable<TValue> collection)
|
||||
: base(new ObservableCollection<TValue>(collection))
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TKey Key { get; }
|
||||
|
||||
|
@ -3,10 +3,9 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
@ -17,138 +16,70 @@ namespace Microsoft.Toolkit.Collections
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name = "TValue" > The type of the items in the collection.</typeparam>
|
||||
public sealed class ReadOnlyObservableGroupedCollection<TKey, TValue> :
|
||||
IReadOnlyCollection<ReadOnlyObservableGroup<TKey, TValue>>,
|
||||
IReadOnlyList<ReadOnlyObservableGroup<TKey, TValue>>,
|
||||
INotifyPropertyChanged,
|
||||
INotifyCollectionChanged,
|
||||
ICollection, // Implementing ICollection and IList is needed to allow ListView to monitor the INotifyCollectionChanged events...
|
||||
IList
|
||||
public sealed class ReadOnlyObservableGroupedCollection<TKey, TValue> : ReadOnlyObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>
|
||||
{
|
||||
private readonly ObservableGroupedCollection<TKey, TValue> _collection;
|
||||
private readonly IDictionary<ObservableGroup<TKey, TValue>, ReadOnlyObservableGroup<TKey, TValue>> _mapping;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The source collection to wrap.</param>
|
||||
public ReadOnlyObservableGroupedCollection(ObservableGroupedCollection<TKey, TValue> collection)
|
||||
: this(collection.Select(g => new ReadOnlyObservableGroup<TKey, TValue>(g)))
|
||||
{
|
||||
_collection = collection;
|
||||
_mapping = new Dictionary<ObservableGroup<TKey, TValue>, ReadOnlyObservableGroup<TKey, TValue>>(capacity: _collection.Count);
|
||||
|
||||
((INotifyPropertyChanged)_collection).PropertyChanged += OnCollectionPropertyChanged;
|
||||
((INotifyCollectionChanged)_collection).CollectionChanged += OnCollectionChanged;
|
||||
((INotifyCollectionChanged)collection).CollectionChanged += this.OnSourceCollectionChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlyObservableGroup<TKey, TValue> this[int index] => GetGroupAt(index);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Count => _collection.Count;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<ReadOnlyObservableGroup<TKey, TValue>> GetEnumerator() => _collection.Select(CreateOrGetReadOnlyObservableGroup).GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_collection.Select(CreateOrGetReadOnlyObservableGroup)).GetEnumerator();
|
||||
|
||||
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The initial data to add in the grouped collection.</param>
|
||||
public ReadOnlyObservableGroupedCollection(IEnumerable<ReadOnlyObservableGroup<TKey, TValue>> collection)
|
||||
: base(new ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>(collection))
|
||||
{
|
||||
// We force the evaluation to have all our instances ready before deleting the mapping.
|
||||
var sourceOldItems = (e.OldItems?.Cast<ObservableGroup<TKey, TValue>>() ?? Enumerable.Empty<ObservableGroup<TKey, TValue>>()).ToList();
|
||||
var oldItems = (IList)sourceOldItems.Select(CreateOrGetReadOnlyObservableGroup).ToList();
|
||||
var newItems = (IList)(e.NewItems?.Cast<ObservableGroup<TKey, TValue>>().Select(CreateOrGetReadOnlyObservableGroup) ?? Enumerable.Empty<ReadOnlyObservableGroup<TKey, TValue>>()).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The initial data to add in the grouped collection.</param>
|
||||
public ReadOnlyObservableGroupedCollection(IEnumerable<IGrouping<TKey, TValue>> collection)
|
||||
: this(collection.Select(g => new ReadOnlyObservableGroup<TKey, TValue>(g.Key, g)))
|
||||
{
|
||||
}
|
||||
|
||||
private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// Even if the NotifyCollectionChangedEventArgs allows multiple items, the actual implementation is only
|
||||
// reporting the changes one by one. We consider only this case for now.
|
||||
if (e.OldItems?.Count > 1 || e.NewItems?.Count > 1)
|
||||
{
|
||||
Debug.Fail("OldItems and NewItems should contain at most 1 item");
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
var newItem = e.NewItems?.Cast<ObservableGroup<TKey, TValue>>()?.FirstOrDefault();
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, e.NewStartingIndex));
|
||||
Items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup<TKey, TValue>(newItem));
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, newItems, e.NewStartingIndex, e.OldStartingIndex));
|
||||
// Our inner Items list is our own ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>> so we can safely cast Items to its concrete type here.
|
||||
((ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>)Items).Move(e.OldStartingIndex, e.NewStartingIndex);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
// We unmap the removed or replaced items.
|
||||
foreach (var sourceOldItem in sourceOldItems)
|
||||
{
|
||||
_mapping.Remove(sourceOldItem);
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, e.OldStartingIndex));
|
||||
Items.RemoveAt(e.OldStartingIndex);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
// We unmap the removed or replaced items.
|
||||
foreach (var sourceOldItem in sourceOldItems)
|
||||
{
|
||||
_mapping.Remove(sourceOldItem);
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems));
|
||||
Items[e.OldStartingIndex] = new ReadOnlyObservableGroup<TKey, TValue>(newItem);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
_mapping.Clear();
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
Items.Clear();
|
||||
break;
|
||||
default:
|
||||
Debug.Fail("unsupported value");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollectionPropertyChanged(object sender, PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
|
||||
|
||||
private ReadOnlyObservableGroup<TKey, TValue> CreateOrGetReadOnlyObservableGroup(ObservableGroup<TKey, TValue> observableGroup)
|
||||
{
|
||||
if (_mapping.TryGetValue(observableGroup, out var readOnlyGroup))
|
||||
{
|
||||
return readOnlyGroup;
|
||||
}
|
||||
|
||||
readOnlyGroup = new ReadOnlyObservableGroup<TKey, TValue>(observableGroup);
|
||||
_mapping.Add(observableGroup, readOnlyGroup);
|
||||
|
||||
return readOnlyGroup;
|
||||
}
|
||||
|
||||
private ReadOnlyObservableGroup<TKey, TValue> GetGroupAt(int index) => CreateOrGetReadOnlyObservableGroup(_collection[index]);
|
||||
|
||||
int ICollection.Count => _collection.Count;
|
||||
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
object ICollection.SyncRoot => this;
|
||||
|
||||
bool IList.IsFixedSize => ((IList)_collection).IsFixedSize;
|
||||
|
||||
bool IList.IsReadOnly => true;
|
||||
|
||||
object IList.this[int index]
|
||||
{
|
||||
get => GetGroupAt(index);
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection.CopyTo(Array array, int index) => throw new NotImplementedException();
|
||||
|
||||
int IList.Add(object value) => throw new NotImplementedException();
|
||||
|
||||
void IList.Clear() => throw new NotImplementedException();
|
||||
|
||||
bool IList.Contains(object value) => this.Any(group => group == value);
|
||||
|
||||
int IList.IndexOf(object value) => this.Select((group, i) => (group == value) ? i : -1).Max();
|
||||
|
||||
void IList.Insert(int index, object value) => throw new NotImplementedException();
|
||||
|
||||
void IList.Remove(object value) => throw new NotImplementedException();
|
||||
|
||||
void IList.RemoveAt(int index) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user