// Licensed to the .NET Foundation under one or more agreements.
// 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
namespace CommunityToolkit.Common.Collections;
///
/// A read-only list of groups.
///
/// The type of the group key.
/// The type of the items in the collection.
public sealed class ReadOnlyObservableGroupedCollection : ReadOnlyObservableCollection>
where TKey : notnull
{
///
/// Initializes a new instance of the class.
///
/// The source collection to wrap.
public ReadOnlyObservableGroupedCollection(ObservableGroupedCollection collection)
: this(collection.Select(static g => new ReadOnlyObservableGroup(g)))
{
((INotifyCollectionChanged)collection).CollectionChanged += OnSourceCollectionChanged;
}
///
/// Initializes a new instance of the class.
///
/// The initial data to add in the grouped collection.
public ReadOnlyObservableGroupedCollection(IEnumerable> collection)
: base(new ObservableCollection>(collection))
{
}
///
/// Initializes a new instance of the class.
///
/// The initial data to add in the grouped collection.
public ReadOnlyObservableGroupedCollection(IEnumerable> collection)
: this(collection.Select(static g => new ReadOnlyObservableGroup(g.Key, g)))
{
}
private void OnSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// Even if 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)
{
static void ThrowNotSupportedException()
{
throw new NotSupportedException(
"ReadOnlyObservableGroupedCollection doesn't support operations on multiple items at once.\n" +
"If this exception was thrown, it likely means support for batched item updates has been added to the " +
"underlying ObservableCollection type, and this implementation doesn't support that feature yet.\n" +
"Please consider opening an issue in https://aka.ms/windowstoolkit to report this.");
}
ThrowNotSupportedException();
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add or NotifyCollectionChangedAction.Replace:
// We only need to find the new item if the operation is either add or remove. In this
// case we just directly find the first item that was modified, or throw if it's not present.
// This normally never happens anyway - add and replace should always have a target element.
ObservableGroup newItem = e.NewItems!.Cast>().First();
if (e.Action == NotifyCollectionChangedAction.Add)
{
Items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup(newItem));
}
else
{
Items[e.OldStartingIndex] = new ReadOnlyObservableGroup(newItem);
}
break;
case NotifyCollectionChangedAction.Move:
// Our inner Items list is our own ObservableCollection> so we can safely cast Items to its concrete type here.
((ObservableCollection>)Items).Move(e.OldStartingIndex, e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
Items.RemoveAt(e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Reset:
Items.Clear();
break;
default:
Debug.Fail("unsupported value");
break;
}
}
}