diff --git a/src/Avalonia.Controls/Utils/RealizedStackElements.cs b/src/Avalonia.Controls/Utils/RealizedStackElements.cs index 11bbaa11c4..18cba5b123 100644 --- a/src/Avalonia.Controls/Utils/RealizedStackElements.cs +++ b/src/Avalonia.Controls/Utils/RealizedStackElements.cs @@ -294,6 +294,7 @@ namespace Avalonia.Controls.Utils { // The insertion point was before the first element, update the first index. _firstIndex += count; + _startUUnstable = true; } else { diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs index 4cbf8d2142..d0e139bedf 100644 --- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs @@ -843,6 +843,28 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, panel.VisualChildren.Count); } + [Fact] + public void Inserting_Item_Before_Viewport_Preserves_FirstRealizedIndex() + { + // Issue #12744 + using var app = App(); + var (target, scroll, itemsControl) = CreateTarget(); + var items = (IList)itemsControl.ItemsSource!; + + // Scroll down 20 items. + scroll.Offset = new Vector(0, 200); + target.UpdateLayout(); + Assert.Equal(20, target.FirstRealizedIndex); + + // Insert an item at the beginning. + items.Insert(0, "New Item"); + target.UpdateLayout(); + + // The first realized index should still be 20 as the scroll should be unchanged. + Assert.Equal(20, target.FirstRealizedIndex); + Assert.Equal(new(0, 200), scroll.Offset); + } + private static IReadOnlyList GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl) { return target.GetRealizedElements()