diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Data/CompositeCollectionView.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Data/CompositeCollectionView.cs index ffa95ce1fa3..86d7314870c 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Data/CompositeCollectionView.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Data/CompositeCollectionView.cs @@ -384,9 +384,16 @@ protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArg { ValidateCollectionChangedEventArgs(args); + bool isRangeAction = IsCollectionChangedRangeAction(args); + bool moveCurrencyOffDeletedElement = false; - switch (args.Action) + // If we have a range operation, treat it as a refresh for now + NotifyCollectionChangedAction effectiveAction = isRangeAction + ? NotifyCollectionChangedAction.Reset + : args.Action; + + switch (effectiveAction) { case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Remove: @@ -608,7 +615,7 @@ protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArg case NotifyCollectionChangedAction.Reset: { - _traceLog?.Add("ProcessCollectionChanged action = {0}", args.Action); + _traceLog?.Add("ProcessCollectionChanged action = {0}", effectiveAction); if (_collection.Count != 0) { @@ -1430,23 +1437,23 @@ private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs switch (e.Action) { case NotifyCollectionChangedAction.Add: - if (e.NewItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Remove: - if (e.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.OldItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Replace: - if (e.NewItems.Count != 1 || e.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1 || e.OldItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Move: - if (e.NewItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); if (e.NewStartingIndex < 0) throw new InvalidOperationException(SR.CannotMoveToUnknownPosition); break; @@ -1459,6 +1466,15 @@ private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs } } + private bool IsCollectionChangedRangeAction(NotifyCollectionChangedEventArgs e) + { + return + (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count > 1) || + (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems.Count > 1) || + (e.Action == NotifyCollectionChangedAction.Replace && (e.OldItems.Count > 1 || e.NewItems.Count > 1)) || + (e.Action == NotifyCollectionChangedAction.Move && (e.OldItems.Count > 1 || e.NewItems.Count > 1)); + } + /// /// Helper to raise a PropertyChanged event />). /// diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemContainerGenerator.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemContainerGenerator.cs index 8eb991d009e..85554796a1c 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemContainerGenerator.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemContainerGenerator.cs @@ -2388,35 +2388,46 @@ private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs switch (args.Action) { case NotifyCollectionChangedAction.Add: + + // Treat range operations as refresh operations, for minimum support. + // This can be expanded later with a better implementation, if needed. if (args.NewItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); - OnItemAdded(args.NewItems[0], args.NewStartingIndex); + OnRefresh(); + else + OnItemAdded(args.NewItems[0], args.NewStartingIndex); break; case NotifyCollectionChangedAction.Remove: if (args.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); - OnItemRemoved(args.OldItems[0], args.OldStartingIndex); + OnRefresh(); + else + OnItemRemoved(args.OldItems[0], args.OldStartingIndex); break; case NotifyCollectionChangedAction.Replace: - // Don't check arguments if app targets 4.0, for compat ( 726682) + // Don't check arguments if app targets 4.0, for compat (726682) if (!FrameworkCompatibilityPreferences.TargetsDesktop_V4_0) { if (args.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + OnRefresh(); + else + OnItemReplaced(args.OldItems[0], args.NewItems[0], args.NewStartingIndex); } - OnItemReplaced(args.OldItems[0], args.NewItems[0], args.NewStartingIndex); + else + OnItemReplaced(args.OldItems[0], args.NewItems[0], args.NewStartingIndex); break; case NotifyCollectionChangedAction.Move: - // Don't check arguments if app targets 4.0, for compat ( 726682) + // Don't check arguments if app targets 4.0, for compat (726682) if (!FrameworkCompatibilityPreferences.TargetsDesktop_V4_0) { if (args.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + OnRefresh(); + else + OnItemMoved(args.OldItems[0], args.OldStartingIndex, args.NewStartingIndex); } - OnItemMoved(args.OldItems[0], args.OldStartingIndex, args.NewStartingIndex); + else + OnItemMoved(args.OldItems[0], args.OldStartingIndex, args.NewStartingIndex); break; case NotifyCollectionChangedAction.Reset: diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/Selector.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/Selector.cs index 6b480a12b71..181ec869992 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/Selector.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/Selector.cs @@ -862,11 +862,16 @@ private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionCha throw new InvalidOperationException(SR.ChangingCollectionNotSupported); } + // If we have a range operation, treat it as a refresh for now + NotifyCollectionChangedAction effectiveAction = IsCollectionChangedRangeAction(e) + ? NotifyCollectionChangedAction.Reset + : e.Action; + SelectionChange.Begin(); bool succeeded=false; try { - switch (e.Action) + switch (effectiveAction) { case NotifyCollectionChangedAction.Add: if (e.NewItems.Count != 1) @@ -924,6 +929,15 @@ private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionCha } } + private bool IsCollectionChangedRangeAction(NotifyCollectionChangedEventArgs e) + { + return + (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count > 1) || + (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems.Count > 1) || + (e.Action == NotifyCollectionChangedAction.Replace && (e.OldItems.Count > 1 || e.NewItems.Count > 1)) || + (e.Action == NotifyCollectionChangedAction.Move && (e.OldItems.Count > 1 || e.NewItems.Count > 1)); + } + #endregion diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingListCollectionView.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingListCollectionView.cs index f69b43febe5..fb90ac69291 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingListCollectionView.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingListCollectionView.cs @@ -1493,7 +1493,12 @@ protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArg bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; bool moveCurrency = false; - switch (args.Action) + // If we have a range operation, treat it as a refresh for now + NotifyCollectionChangedAction effectiveAction = IsCollectionChangedRangeAction(args) + ? NotifyCollectionChangedAction.Reset + : args.Action; + + switch (effectiveAction) { case NotifyCollectionChangedAction.Add: if (_newItemIndex == -2) @@ -1806,6 +1811,12 @@ private IEnumerator InternalGetEnumerator() // Data Collection immediately after the CollectionChangeEvent private void AdjustShadowCopy(NotifyCollectionChangedEventArgs e) { + // No change needed for range operations, as we just reset the collection + if (IsCollectionChangedRangeAction(e)) + { + return; + } + switch (e.Action) { case NotifyCollectionChangedAction.Add: @@ -2430,23 +2441,23 @@ private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs switch (e.Action) { case NotifyCollectionChangedAction.Add: - if (e.NewItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Remove: - if (e.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.OldItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Replace: - if (e.NewItems.Count != 1 || e.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1 || e.OldItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Move: - if (e.NewItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); if (e.NewStartingIndex < 0) throw new InvalidOperationException(SR.CannotMoveToUnknownPosition); break; @@ -2459,6 +2470,15 @@ private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs } } + private bool IsCollectionChangedRangeAction(NotifyCollectionChangedEventArgs e) + { + return + (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count > 1) || + (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems.Count > 1) || + (e.Action == NotifyCollectionChangedAction.Replace && (e.OldItems.Count > 1 || e.NewItems.Count > 1)) || + (e.Action == NotifyCollectionChangedAction.Move && (e.OldItems.Count > 1 || e.NewItems.Count > 1)); + } + /// /// Helper to raise a PropertyChanged event />). /// diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/CollectionView.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/CollectionView.cs index eb1690348bf..2a0ffae42f3 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/CollectionView.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/CollectionView.cs @@ -1075,7 +1075,12 @@ protected virtual void ProcessCollectionChanged(NotifyCollectionChangedEventArgs int oldCurrentPosition = _currentPosition; bool raiseChanged = false; - switch (args.Action) + // If we have a range operation, treat it as a refresh for now + NotifyCollectionChangedAction effectiveAction = IsCollectionChangedRangeAction(args) + ? NotifyCollectionChangedAction.Reset + : args.Action; + + switch (effectiveAction) { case NotifyCollectionChangedAction.Add: if (PassesFilter(args.NewItems[0])) @@ -1948,25 +1953,25 @@ private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs switch (e.Action) { case NotifyCollectionChangedAction.Add: - if (e.NewItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Remove: - if (e.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.OldItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); if (e.OldStartingIndex < 0) throw new InvalidOperationException(SR.RemovedItemNotFound); break; case NotifyCollectionChangedAction.Replace: - if (e.NewItems.Count != 1 || e.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1 || e.OldItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Move: - if (e.NewItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); if (e.NewStartingIndex < 0) throw new InvalidOperationException(SR.CannotMoveToUnknownPosition); break; @@ -1979,6 +1984,15 @@ private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs } } + private bool IsCollectionChangedRangeAction(NotifyCollectionChangedEventArgs e) + { + return + (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count > 1) || + (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems.Count > 1) || + (e.Action == NotifyCollectionChangedAction.Replace && (e.OldItems.Count > 1 || e.NewItems.Count > 1)) || + (e.Action == NotifyCollectionChangedAction.Move && (e.OldItems.Count > 1 || e.NewItems.Count > 1)); + } + // fix up CurrentPosition and CurrentItem after a collection change private void AdjustCurrencyForAdd(int index) { diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/ListCollectionView.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/ListCollectionView.cs index ce8a01d0b8e..ab8a075d6f2 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/ListCollectionView.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/ListCollectionView.cs @@ -1657,6 +1657,8 @@ protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArg ValidateCollectionChangedEventArgs(args); + bool isRangeAction = IsCollectionChangedRangeAction(args); + // adding or replacing an item can change CanAddNew, by providing a // non-null representative if (!_isItemConstructorValid) @@ -1677,7 +1679,8 @@ protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArg // apply the change to the shadow copy if (AllowsCrossThreadChanges) { - if (args.Action != NotifyCollectionChangedAction.Reset) + // ignore when we have a range action, as we'll just reset the collection + if (args.Action != NotifyCollectionChangedAction.Reset && !isRangeAction) { if (args.Action != NotifyCollectionChangedAction.Remove && args.NewStartingIndex < 0 || args.Action != NotifyCollectionChangedAction.Add && args.OldStartingIndex < 0) @@ -1693,7 +1696,8 @@ protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArg } // If the Action is Reset then we do a Refresh. - if (args.Action == NotifyCollectionChangedAction.Reset) + // Also treat range actions as Reset, for now. + if (args.Action == NotifyCollectionChangedAction.Reset || isRangeAction) { // implicitly cancel EditItem transactions if (IsEditingItem) @@ -2497,23 +2501,23 @@ private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs switch (e.Action) { case NotifyCollectionChangedAction.Add: - if (e.NewItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Remove: - if (e.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.OldItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Replace: - if (e.NewItems.Count != 1 || e.OldItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1 || e.OldItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); break; case NotifyCollectionChangedAction.Move: - if (e.NewItems.Count != 1) - throw new NotSupportedException(SR.RangeActionsNotSupported); + if (e.NewItems.Count < 1) + throw new NotSupportedException(SR.UnexpectedCollectionChangeAction); if (e.NewStartingIndex < 0) throw new InvalidOperationException(SR.CannotMoveToUnknownPosition); break; @@ -2526,6 +2530,15 @@ private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs } } + private bool IsCollectionChangedRangeAction(NotifyCollectionChangedEventArgs e) + { + return + (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count > 1) || + (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems.Count > 1) || + (e.Action == NotifyCollectionChangedAction.Replace && (e.OldItems.Count > 1 || e.NewItems.Count > 1)) || + (e.Action == NotifyCollectionChangedAction.Move && (e.OldItems.Count > 1 || e.NewItems.Count > 1)); + } + /// /// Create, filter and sort the local index array.