|
5 | 5 | using System; |
6 | 6 | using System.Collections.ObjectModel; |
7 | 7 | using System.Collections.Specialized; |
8 | | -using System.Diagnostics; |
| 8 | +using System.Diagnostics.CodeAnalysis; |
9 | 9 | using System.Linq; |
10 | 10 |
|
11 | 11 | namespace CommunityToolkit.Mvvm.Collections; |
@@ -37,56 +37,93 @@ public ReadOnlyObservableGroupedCollection(ObservableCollection<ReadOnlyObservab |
37 | 37 | { |
38 | 38 | } |
39 | 39 |
|
| 40 | + /// <summary> |
| 41 | + /// Forwards the <see cref="INotifyCollectionChanged.CollectionChanged"/> event whenever it is raised by the wrapped collection. |
| 42 | + /// </summary> |
| 43 | + /// <param name="sender">The wrapped collection (an <see cref="ObservableCollection{T}"/> of <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> instance).</param> |
| 44 | + /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> arguments.</param> |
| 45 | + /// <exception cref="NotSupportedException">Thrown if a range operation is requested.</exception> |
40 | 46 | private void OnSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) |
41 | 47 | { |
42 | 48 | // Even if NotifyCollectionChangedEventArgs allows multiple items, the actual implementation |
43 | | - // is only reporting the changes one by one. We consider only this case for now. |
44 | | - if (e.OldItems?.Count > 1 || e.NewItems?.Count > 1) |
| 49 | + // is only reporting the changes one by one. We consider only this case for now. If this is |
| 50 | + // added in a new version of .NET, this type will need to be updated accordingly in a new version. |
| 51 | + [DoesNotReturn] |
| 52 | + static void ThrowNotSupportedExceptionForRangeOperation() |
45 | 53 | { |
46 | | - static void ThrowNotSupportedException() |
47 | | - { |
48 | | - throw new NotSupportedException( |
49 | | - "ReadOnlyObservableGroupedCollection<TKey, TValue> doesn't support operations on multiple items at once.\n" + |
50 | | - "If this exception was thrown, it likely means support for batched item updates has been added to the " + |
51 | | - "underlying ObservableCollection<T> type, and this implementation doesn't support that feature yet.\n" + |
52 | | - "Please consider opening an issue in https://aka.ms/windowstoolkit to report this."); |
53 | | - } |
54 | | - |
55 | | - ThrowNotSupportedException(); |
| 54 | + throw new NotSupportedException( |
| 55 | + "ReadOnlyObservableGroupedCollection<TKey, TValue> doesn't support operations on multiple items at once.\n" + |
| 56 | + "If this exception was thrown, it likely means support for batched item updates has been added to the " + |
| 57 | + "underlying ObservableCollection<T> type, and this implementation doesn't support that feature yet.\n" + |
| 58 | + "Please consider opening an issue in https://aka.ms/toolkit/dotnet to report this."); |
56 | 59 | } |
57 | 60 |
|
| 61 | + // The inner Items list is ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>, so doing a direct cast here will always succeed |
| 62 | + ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>> items = (ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>)Items; |
| 63 | + |
58 | 64 | switch (e.Action) |
59 | 65 | { |
60 | | - case NotifyCollectionChangedAction.Add or NotifyCollectionChangedAction.Replace: |
| 66 | + // Insert a single item for an "Add" operation, fail if multiple items are added |
| 67 | + case NotifyCollectionChangedAction.Add: |
| 68 | + if (e.NewItems!.Count == 1) |
| 69 | + { |
| 70 | + ObservableGroup<TKey, TValue> newItem = (ObservableGroup<TKey, TValue>)e.NewItems![0]!; |
61 | 71 |
|
62 | | - // We only need to find the new item if the operation is either add or remove. In this |
63 | | - // case we just directly find the first item that was modified, or throw if it's not present. |
64 | | - // This normally never happens anyway - add and replace should always have a target element. |
65 | | - ObservableGroup<TKey, TValue> newItem = e.NewItems!.Cast<ObservableGroup<TKey, TValue>>().First(); |
| 72 | + items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup<TKey, TValue>(newItem)); |
| 73 | + } |
| 74 | + else if (e.NewItems!.Count > 1) |
| 75 | + { |
| 76 | + ThrowNotSupportedExceptionForRangeOperation(); |
| 77 | + } |
66 | 78 |
|
67 | | - if (e.Action == NotifyCollectionChangedAction.Add) |
| 79 | + break; |
| 80 | + |
| 81 | + // Remove a single item at offset for a "Remove" operation, fail if multiple items are removed |
| 82 | + case NotifyCollectionChangedAction.Remove: |
| 83 | + if (e.OldItems!.Count == 1) |
68 | 84 | { |
69 | | - Items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup<TKey, TValue>(newItem)); |
| 85 | + items.RemoveAt(e.OldStartingIndex); |
70 | 86 | } |
71 | | - else |
| 87 | + else if (e.OldItems!.Count > 1) |
72 | 88 | { |
73 | | - Items[e.OldStartingIndex] = new ReadOnlyObservableGroup<TKey, TValue>(newItem); |
| 89 | + ThrowNotSupportedExceptionForRangeOperation(); |
74 | 90 | } |
75 | 91 |
|
76 | 92 | break; |
77 | | - case NotifyCollectionChangedAction.Move: |
78 | 93 |
|
79 | | - // Our inner Items list is our own ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>> so we can safely cast Items to its concrete type here. |
80 | | - ((ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>)Items).Move(e.OldStartingIndex, e.NewStartingIndex); |
| 94 | + // Replace a single item at offset for a "Replace" operation, fail if multiple items are replaced |
| 95 | + case NotifyCollectionChangedAction.Replace: |
| 96 | + if (e.OldItems!.Count == 1 && e.NewItems!.Count == 1) |
| 97 | + { |
| 98 | + ObservableGroup<TKey, TValue> replacedItem = (ObservableGroup<TKey, TValue>)e.NewItems![0]!; |
| 99 | + |
| 100 | + items[e.OldStartingIndex] = new ReadOnlyObservableGroup<TKey, TValue>(replacedItem); |
| 101 | + } |
| 102 | + else if (e.OldItems!.Count > 1 || e.NewItems!.Count > 1) |
| 103 | + { |
| 104 | + ThrowNotSupportedExceptionForRangeOperation(); |
| 105 | + } |
| 106 | + |
81 | 107 | break; |
82 | | - case NotifyCollectionChangedAction.Remove: |
83 | | - Items.RemoveAt(e.OldStartingIndex); |
| 108 | + |
| 109 | + // Move a single item between offsets for a "Move" operation, fail if multiple items are moved |
| 110 | + case NotifyCollectionChangedAction.Move: |
| 111 | + if (e.OldItems!.Count == 1 && e.NewItems!.Count == 1) |
| 112 | + { |
| 113 | + items.Move(e.OldStartingIndex, e.NewStartingIndex); |
| 114 | + } |
| 115 | + else if (e.OldItems!.Count > 1 || e.NewItems!.Count > 1) |
| 116 | + { |
| 117 | + ThrowNotSupportedExceptionForRangeOperation(); |
| 118 | + } |
| 119 | + |
84 | 120 | break; |
| 121 | + |
| 122 | + // A "Reset" operation is just forwarded normally |
85 | 123 | case NotifyCollectionChangedAction.Reset: |
86 | | - Items.Clear(); |
| 124 | + items.Clear(); |
87 | 125 | break; |
88 | 126 | default: |
89 | | - Debug.Fail("unsupported value"); |
90 | 127 | break; |
91 | 128 | } |
92 | 129 | } |
|
0 commit comments