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 fef9bba1d7d..93836840ec5 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 @@ -2400,25 +2400,25 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) switch (args.Action) { case NotifyCollectionChangedAction.Add: - if (args.NewItems.Count != 1) + if (args.NewItems.Count < 1) throw new NotSupportedException(SR.RangeActionsNotSupported); - OnItemAdded(args.NewItems[0], args.NewStartingIndex); + OnItemsAdded(args.NewItems, args.NewStartingIndex); break; case NotifyCollectionChangedAction.Remove: - if (args.OldItems.Count != 1) + if (args.OldItems.Count < 1) throw new NotSupportedException(SR.RangeActionsNotSupported); - OnItemRemoved(args.OldItems[0], args.OldStartingIndex); + OnItemsRemoved(args.OldItems, args.OldStartingIndex); break; case NotifyCollectionChangedAction.Replace: // Don't check arguments if app targets 4.0, for compat ( 726682) if (!FrameworkCompatibilityPreferences.TargetsDesktop_V4_0) { - if (args.OldItems.Count != 1) + if (args.OldItems.Count < 1) throw new NotSupportedException(SR.RangeActionsNotSupported); } - OnItemReplaced(args.OldItems[0], args.NewItems[0], args.NewStartingIndex); + OnItemsReplaced(args.OldItems, args.NewItems, args.NewStartingIndex); break; case NotifyCollectionChangedAction.Move: @@ -2446,6 +2446,86 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) } } + // Called when an items are added to the items collection + void OnItemsAdded(IList items, int index) + { + if (_itemMap == null) + { + // reentrant call (from RemoveAllInternal) shouldn't happen, + // but if it does, don't crash + Debug.Assert(false, "unexpected reentrant call to OnItemAdded"); + return; + } + + ValidateAndCorrectIndex(items[0], ref index); + + GeneratorPosition position = new GeneratorPosition(-1, 0); + + // find the block containing the new item + ItemBlock block = _itemMap.Next; + int offsetFromBlockStart = index; + int unrealizedItemsSkipped = 0; // distance since last realized item + while (block != _itemMap && offsetFromBlockStart >= block.ItemCount) + { + offsetFromBlockStart -= block.ItemCount; + position.Index += block.ContainerCount; + unrealizedItemsSkipped = (block.ContainerCount > 0) ? 0 : unrealizedItemsSkipped + block.ItemCount; + block = block.Next; + } + + position.Offset = unrealizedItemsSkipped + offsetFromBlockStart + 1; + // the position is now correct, except when pointing into a realized block; + // that case is fixed below + + // if it's an unrealized block, add the item by bumping the count + UnrealizedItemBlock uib = block as UnrealizedItemBlock; + if (uib != null) + { + MoveItems(uib, offsetFromBlockStart, items.Count, uib, offsetFromBlockStart + 1, 0); + uib.ItemCount += items.Count; + } + + // if the item can be added to a previous unrealized block, do so + else if ((offsetFromBlockStart == 0 || block == _itemMap) && + ((uib = block.Prev as UnrealizedItemBlock) != null)) + { + uib.ItemCount += items.Count; + } + + // otherwise, create a new unrealized block + else + { + uib = new UnrealizedItemBlock(); + uib.ItemCount = items.Count; + + // split the current realized block, if necessary + RealizedItemBlock rib; + if (offsetFromBlockStart > 0 && (rib = block as RealizedItemBlock) != null) + { + RealizedItemBlock newBlock = new RealizedItemBlock(); + MoveItems(rib, offsetFromBlockStart, rib.ItemCount - offsetFromBlockStart, newBlock, 0, offsetFromBlockStart); + newBlock.InsertAfter(rib); + position.Index += block.ContainerCount; + position.Offset = 1; + block = newBlock; + } + + uib.InsertBefore(block); + } + + // tell generators what happened + if (MapChanged != null) + { + MapChanged(null, index, items.Count, uib, 0, 0); + } + + // tell layout what happened + if (ItemsChanged != null) + { + ItemsChanged(this, new ItemsChangedEventArgs(NotifyCollectionChangedAction.Add, position, items.Count, 0)); + } + } + // Called when an item is added to the items collection void OnItemAdded(object item, int index) { @@ -2526,6 +2606,15 @@ void OnItemAdded(object item, int index) } } + // Called when items are removed from the items collection + // TODO this could probably be improved + void OnItemsRemoved(IList items, int itemIndex) + { + foreach (var item in items) + { + OnItemRemoved(item, itemIndex); + } + } // Called when an item is removed from the items collection void OnItemRemoved(object item, int itemIndex) @@ -2592,6 +2681,33 @@ void OnItemRemoved(object item, int itemIndex) } } + // this could probably be optimized + void OnItemsReplaced(IList oldItems, IList newItems, int index) + { + // Handle replacements for the overlapping part + for (int i = 0; i < Math.Min(oldItems.Count, newItems.Count); i++) + { + OnItemReplaced(oldItems[i], newItems[i], index + i); + } + + // Handle extra removals (oldItems has more elements) + if (oldItems.Count > newItems.Count) + { + for (int i = oldItems.Count - 1; i >= newItems.Count; i--) + { + OnItemRemoved(oldItems[i], index + i); + } + } + // Handle extra additions (newItems has more elements) + else if (newItems.Count > oldItems.Count) + { + for (int i = oldItems.Count; i < newItems.Count; i++) + { + OnItemAdded(newItems[i], index + i); + } + } + } + void OnItemReplaced(object oldItem, object newItem, int index) { // search for the replaced item 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 d6b65399ac0..1f8c9cb7371 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 @@ -1890,6 +1890,8 @@ void ProcessCollectionChangedWithAdjustedIndex(NotifyCollectionChangedEventArgs object oldItem = (args.OldItems != null && args.OldItems.Count > 0) ? args.OldItems[0] : null; object newItem = (args.NewItems != null && args.NewItems.Count > 0) ? args.NewItems[0] : null; + IList oldItems = args.OldItems ?? Array.Empty(); + IList newItems = args.NewItems ?? Array.Empty(); LiveShapingList lsList = InternalList as LiveShapingList; LiveShapingItem lsi; @@ -1925,7 +1927,7 @@ void ProcessCollectionChangedWithAdjustedIndex(NotifyCollectionChangedEventArgs if (!IsGrouping) { AdjustCurrencyForAdd(adjustedNewIndex); - args = new NotifyCollectionChangedEventArgs(effectiveAction, newItem, adjustedNewIndex); + args = new NotifyCollectionChangedEventArgs(effectiveAction, newItems, adjustedNewIndex); } else { @@ -1963,7 +1965,7 @@ void ProcessCollectionChangedWithAdjustedIndex(NotifyCollectionChangedEventArgs if (!IsGrouping) { AdjustCurrencyForRemove(adjustedOldIndex); - args = new NotifyCollectionChangedEventArgs(effectiveAction, args.OldItems[0], adjustedOldIndex); + args = new NotifyCollectionChangedEventArgs(effectiveAction, oldItems, adjustedOldIndex); } else { @@ -1993,7 +1995,7 @@ void ProcessCollectionChangedWithAdjustedIndex(NotifyCollectionChangedEventArgs if (!IsGrouping) { AdjustCurrencyForReplace(adjustedOldIndex); - args = new NotifyCollectionChangedEventArgs(effectiveAction, args.NewItems[0], args.OldItems[0], adjustedOldIndex); + args = new NotifyCollectionChangedEventArgs(effectiveAction, newItems, oldItems, adjustedOldIndex); } else { @@ -2392,34 +2394,58 @@ internal void AdjustShadowCopy(NotifyCollectionChangedEventArgs e) case NotifyCollectionChangedAction.Add: if (e.NewStartingIndex > _unknownIndex) { - ShadowCollection.Insert(e.NewStartingIndex, e.NewItems[0]); + ShadowCollection.InsertRange(e.NewStartingIndex, e.NewItems); } else { - ShadowCollection.Add(e.NewItems[0]); + ShadowCollection.AddRange(e.NewItems); } break; case NotifyCollectionChangedAction.Remove: if (e.OldStartingIndex > _unknownIndex) { - ShadowCollection.RemoveAt(e.OldStartingIndex); + ShadowCollection.RemoveRange(e.OldStartingIndex, e.OldItems.Count); } else { - ShadowCollection.Remove(e.OldItems[0]); + for (int i = e.OldStartingIndex; i < e.OldItems.Count; i++) + { + ShadowCollection.Remove(e.OldItems[i]); + } } break; case NotifyCollectionChangedAction.Replace: if (e.OldStartingIndex > _unknownIndex) { - ShadowCollection[e.OldStartingIndex] = e.NewItems[0]; + if (e.NewItems.Count == e.OldItems.Count) + { + for (int i = 0; i < e.NewItems.Count; i++) + { + ShadowCollection[e.OldStartingIndex + i] = e.NewItems[i]; + } + } + else + { + if (e.OldItems.Count > 0) + { + ShadowCollection.RemoveRange(e.OldStartingIndex, e.OldItems.Count); + } + + if (e.NewItems.Count > 0) + { + ShadowCollection.InsertRange(e.OldStartingIndex, e.NewItems); + } + } } else { // allow the ShadowCollection to throw the IndexOutOfRangeException // if the item is not found. - tempIndex = ShadowCollection.IndexOf(e.OldItems[0]); - ShadowCollection[tempIndex] = e.NewItems[0]; + for (int i = 0; i < e.OldItems.Count; i++) + { + tempIndex = ShadowCollection.IndexOf(e.OldItems[i]); + ShadowCollection[tempIndex] = e.NewItems[i]; + } } break; case NotifyCollectionChangedAction.Move: @@ -2522,17 +2548,17 @@ private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs switch (e.Action) { case NotifyCollectionChangedAction.Add: - if (e.NewItems.Count != 1) + if (e.NewItems.Count < 1) throw new NotSupportedException(SR.RangeActionsNotSupported); break; case NotifyCollectionChangedAction.Remove: - if (e.OldItems.Count != 1) + if (e.OldItems.Count < 1) throw new NotSupportedException(SR.RangeActionsNotSupported); break; case NotifyCollectionChangedAction.Replace: - if (e.NewItems.Count != 1 || e.OldItems.Count != 1) + if (e.NewItems.Count < 1 || e.OldItems.Count < 1) throw new NotSupportedException(SR.RangeActionsNotSupported); break;