Browse Source

Fix ListBox crash when scrolling after item removal (#21838)

* Add regression test for ListBox removal scroll crash

* Fix virtualized arrange after collection changes
pull/21852/head
Nathan Nguyen 2 months ago
committed by GitHub
parent
commit
4fc9848bba
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  2. 25
      tests/Avalonia.Controls.UnitTests/ListBoxVirtualizationIssueTests.cs

7
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -251,6 +251,13 @@ namespace Avalonia.Controls
var orientation = Orientation; var orientation = Orientation;
var u = _realizedElements!.StartU; var u = _realizedElements!.StartU;
// Collection changes before the realized range make its exact position unstable
// until the next measure. ScrollIntoView can intentionally defer that measure
// while waiting for an updated viewport, so use the same position estimate used
// when realizing an element instead of arranging the range at NaN.
if (double.IsNaN(u))
u = GetOrEstimateElementU(_realizedElements.FirstIndex);
for (var i = 0; i < _realizedElements.Count; ++i) for (var i = 0; i < _realizedElements.Count; ++i)
{ {
var e = _realizedElements.Elements[i]; var e = _realizedElements.Elements[i];

25
tests/Avalonia.Controls.UnitTests/ListBoxVirtualizationIssueTests.cs

@ -11,6 +11,31 @@ namespace Avalonia.Controls.UnitTests;
public class ListBoxVirtualizationIssueTests : ScopedTestBase public class ListBoxVirtualizationIssueTests : ScopedTestBase
{ {
[Fact]
public void Removing_First_Item_After_Scrolling_To_End_Should_Allow_Scrolling_To_Start()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var items = new ObservableCollection<int>(Enumerable.Range(0, 100));
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<int>((_, _) => new TextBlock { Height = 50 }),
ItemsPanel = new FuncTemplate<Panel?>(() => new VirtualizingStackPanel()),
};
Prepare(target);
target.ScrollIntoView(99);
items.RemoveAt(0);
target.ScrollIntoView(0);
var firstContainer = Assert.IsType<ListBoxItem>(target.ContainerFromIndex(0));
Assert.Equal(1, firstContainer.Content);
}
}
[Fact] [Fact]
public void Replaced_ItemsSource_Should_Not_Show_Old_Selected_Item_When_Scrolled_Back() public void Replaced_ItemsSource_Should_Not_Show_Old_Selected_Item_When_Scrolled_Back()
{ {

Loading…
Cancel
Save