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