|
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