From a9ca85230002e7c88299da447209f1d1ee531475 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 30 Nov 2022 23:29:06 +0100 Subject: [PATCH] Notify ItemsControl when container index changes. --- .../Generators/ItemContainerGenerator.cs | 10 +++++++ src/Avalonia.Controls/ItemsControl.cs | 16 +++++++++++ .../Presenters/PanelContainerGenerator.cs | 27 ++++++++++++++----- .../Primitives/SelectingItemsControl.cs | 6 +++++ .../VirtualizingStackPanel.cs | 22 +++++++++------ 5 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index 0598bb1a63..1cf40d335f 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs @@ -56,6 +56,16 @@ namespace Avalonia.Controls.Generators public void PrepareItemContainer(Control container, object? item, int index) => _owner.PrepareItemContainer(container, item, index); + /// + /// Called when the index for a container changes due to an insertion or removal in the + /// items collection. + /// + /// The container whose index changed. + /// The old index. + /// The new index. + public void ItemContainerIndexChanged(Control container, int oldIndex, int newIndex) => + _owner.ItemContainerIndexChanged(container, oldIndex, newIndex); + /// /// Undoes the effects of the method. /// diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index b7a63811c4..7c24717a06 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -298,6 +298,17 @@ namespace Avalonia.Controls container.SetValue(ThemeProperty, ItemContainerTheme, BindingPriority.Template); } + /// + /// Called when the index for a container changes due to an insertion or removal in the + /// items collection. + /// + /// The container whose index changed. + /// The old index. + /// The new index. + protected virtual void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex) + { + } + /// /// Undoes the effects of the method. /// @@ -474,6 +485,11 @@ namespace Avalonia.Controls PrepareContainerForItemOverride(container, item, index); } + internal void ItemContainerIndexChanged(Control container, int oldIndex, int newIndex) + { + ContainerIndexChangedOverride(container, oldIndex, newIndex); + } + /// /// Given a collection of items, adds those that are controls to the logical children. /// diff --git a/src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs b/src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs index 56e1901117..4a31017fbd 100644 --- a/src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs +++ b/src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs @@ -63,25 +63,40 @@ namespace Avalonia.Controls.Presenters return; var itemsControl = _presenter.ItemsControl; - var panel = _presenter.Panel; + var generator = itemsControl.ItemContainerGenerator; + var children = _presenter.Panel.Children; void Add(int index, IEnumerable items) { var i = index; foreach (var item in items) { - panel.Children.Insert(i, CreateContainer(itemsControl, item, i)); + children.Insert(i, CreateContainer(itemsControl, item, i)); ++i; } + + var childCount = children.Count; + var delta = i - index; + + for (; i < childCount; ++i) + generator.ItemContainerIndexChanged(children[i], i - delta, i); } - + void Remove(int index, int count) { for (var i = 0; i < count; ++i) { - itemsControl.RemoveLogicalChild(panel.Children[i + index]); - panel.Children.RemoveAt(i + index); + var c = children[i]; + if (!c.IsSet(ItemIsOwnContainerProperty)) + itemsControl.RemoveLogicalChild(children[i + index]); } + + children.RemoveRange(index, count); + + var childCount = children.Count; + + for (var i = index; i < childCount; ++i) + generator.ItemContainerIndexChanged(children[i], i - count, i); } switch (e.Action) @@ -102,7 +117,7 @@ namespace Avalonia.Controls.Presenters break; case NotifyCollectionChangedAction.Reset: ClearItemsControlLogicalChildren(); - panel.Children.Clear(); + children.Clear(); if (_presenter.ItemsControl?.Items is { } items) Add(0, items); break; diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index a49705d754..253726c5aa 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -449,6 +449,12 @@ namespace Avalonia.Controls.Primitives } } + protected override void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex) + { + base.ContainerIndexChangedOverride(container, oldIndex, newIndex); + MarkContainerSelected(container, Selection.IsSelected(newIndex)); + } + protected internal override void ClearContainerForItemOverride(Control element) { base.ClearContainerForItemOverride(element); diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index 76cbf94f09..6373d5f3bd 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -25,7 +25,7 @@ namespace Avalonia.Controls private static readonly Rect s_invalidViewport = new(double.PositiveInfinity, double.PositiveInfinity, 0, 0); private readonly Action _recycleElement; - private readonly Action _updateElementIndex; + private readonly Action _updateElementIndex; private int _anchorIndex = -1; private Control? _anchorElement; private bool _isInLayout; @@ -513,9 +513,9 @@ namespace Avalonia.Controls } } - private void UpdateElementIndex(Control element, int index) + private void UpdateElementIndex(Control element, int oldIndex, int newIndex) { - // TODO: Implement this after we refactor ItemContainerGenerator. + ItemsControl!.ItemContainerGenerator.ItemContainerIndexChanged(element, oldIndex, newIndex); } private void OnEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e) @@ -725,7 +725,7 @@ namespace Avalonia.Controls /// The index in the source collection of the insert. /// The number of items inserted. /// A method used to update the element indexes. - public void ItemsInserted(int modelIndex, int count, Action updateElementIndex) + public void ItemsInserted(int modelIndex, int count, Action updateElementIndex) { if (modelIndex < 0) throw new ArgumentOutOfRangeException(nameof(modelIndex)); @@ -742,11 +742,13 @@ namespace Avalonia.Controls // elements after the insertion point. var elementCount = _elements.Count; var start = Math.Max(index, 0); + var newIndex = first + count; for (var i = start; i < elementCount; ++i) { if (_elements[i] is Control element) - updateElementIndex(element, first + i + count); + updateElementIndex(element, newIndex - count, newIndex); + ++newIndex; } if (index <= 0) @@ -774,7 +776,7 @@ namespace Avalonia.Controls public void ItemsRemoved( int modelIndex, int count, - Action updateElementIndex, + Action updateElementIndex, Action recycleElement) { if (modelIndex < 0) @@ -794,10 +796,12 @@ namespace Avalonia.Controls // the indexes of the realized elements. _firstIndex -= count; + var newIndex = _firstIndex; for (var i = 0; i < _elements.Count; ++i) { if (_elements[i] is Control element) - updateElementIndex(element, _firstIndex + i); + updateElementIndex(element, newIndex - count, newIndex); + ++newIndex; } } else if (startIndex < _elements.Count) @@ -822,10 +826,12 @@ namespace Avalonia.Controls // Update the indexes of the elements after the removed range. end = _elements.Count; + var newIndex = first; for (var i = start; i < end; ++i) { if (_elements[i] is Control element) - updateElementIndex(element, first + i); + updateElementIndex(element, newIndex - count, newIndex); + ++newIndex; } } }