1
0
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:
Vincent 2020-04-02 22:54:56 +02:00
parent cca93e0eaf
commit f8285cd1d5
2 changed files with 50 additions and 107 deletions

View File

@ -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; }

View File

@ -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();
}
}