Browse Source

Merge pull request #11421 from AvaloniaUI/fixes/virtualizing-null-recycle-key

Handle non-recyclable containers in VirtualizingStackPanel.
pull/11453/head
Steven Kirk 3 years ago
committed by GitHub
parent
commit
86991b640c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  2. 60
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

12
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -662,9 +662,12 @@ namespace Avalonia.Controls
_scrollViewer?.UnregisterAnchorCandidate(element);
var recycleKey = element.GetValue(RecycleKeyProperty);
Debug.Assert(recycleKey is not null);
if (recycleKey == s_itemIsItsOwnContainer)
if (recycleKey is null)
{
RemoveInternalChild(element);
}
else if (recycleKey == s_itemIsItsOwnContainer)
{
element.IsVisible = false;
}
@ -687,9 +690,8 @@ namespace Avalonia.Controls
Debug.Assert(ItemContainerGenerator is not null);
var recycleKey = element.GetValue(RecycleKeyProperty);
Debug.Assert(recycleKey is not null);
if (recycleKey == s_itemIsItsOwnContainer)
if (recycleKey is null || recycleKey == s_itemIsItsOwnContainer)
{
RemoveInternalChild(element);
}

60
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@ -646,7 +646,7 @@ namespace Avalonia.Controls.UnitTests
{
// Issue #11272
using var app = App();
var (_, _, itemsControl) = CreateUnrootedTarget();
var (_, _, itemsControl) = CreateUnrootedTarget<ItemsControl>();
var container = new Decorator { Margin = new Thickness(100) };
var root = new TestRoot(true, container);
@ -657,11 +657,49 @@ namespace Avalonia.Controls.UnitTests
root.LayoutManager.ExecuteLayoutPass();
}
[Fact]
public void Supports_Null_Recycle_Key_When_Scrolling()
{
using var app = App();
var (_, scroll, itemsControl) = CreateUnrootedTarget<NonRecyclingItemsControl>();
var root = CreateRoot(itemsControl);
root.LayoutManager.ExecuteInitialLayoutPass();
var firstItem = itemsControl.ContainerFromIndex(0)!;
scroll.Offset = new(0, 20);
Layout(itemsControl);
Assert.Null(firstItem.Parent);
Assert.Null(firstItem.VisualParent);
Assert.DoesNotContain(firstItem, itemsControl.ItemsPanelRoot!.Children);
}
[Fact]
public void Supports_Null_Recycle_Key_When_Clearing_Items()
{
using var app = App();
var (_, _, itemsControl) = CreateUnrootedTarget<NonRecyclingItemsControl>();
var root = CreateRoot(itemsControl);
root.LayoutManager.ExecuteInitialLayoutPass();
var firstItem = itemsControl.ContainerFromIndex(0)!;
itemsControl.ItemsSource = null;
Layout(itemsControl);
Assert.Null(firstItem.Parent);
Assert.Null(firstItem.VisualParent);
Assert.Empty(itemsControl.ItemsPanelRoot!.Children);
}
[Fact]
public void ScrollIntoView_On_Effectively_Invisible_Panel_Does_Not_Create_Ghost_Elements()
{
var items = new[] { "foo", "bar", "baz" };
var (target, _, itemsControl) = CreateUnrootedTarget(items: items);
var (target, _, itemsControl) = CreateUnrootedTarget<ItemsControl>(items: items);
var container = new Decorator { Margin = new Thickness(100), Child = itemsControl };
var root = new TestRoot(true, container);
@ -738,7 +776,7 @@ namespace Avalonia.Controls.UnitTests
Optional<IDataTemplate?> itemTemplate = default,
IEnumerable<Style>? styles = null)
{
var (target, scroll, itemsControl) = CreateUnrootedTarget(items, itemTemplate);
var (target, scroll, itemsControl) = CreateUnrootedTarget<ItemsControl>(items, itemTemplate);
var root = CreateRoot(itemsControl, styles);
root.LayoutManager.ExecuteInitialLayoutPass();
@ -746,9 +784,10 @@ namespace Avalonia.Controls.UnitTests
return (target, scroll, itemsControl);
}
private static (VirtualizingStackPanel, ScrollViewer, ItemsControl) CreateUnrootedTarget(
private static (VirtualizingStackPanel, ScrollViewer, T) CreateUnrootedTarget<T>(
IEnumerable<object>? items = null,
Optional<IDataTemplate?> itemTemplate = default)
where T : ItemsControl, new()
{
var target = new VirtualizingStackPanel();
@ -766,7 +805,7 @@ namespace Avalonia.Controls.UnitTests
Template = ScrollViewerTemplate(),
};
var itemsControl = new ItemsControl
var itemsControl = new T
{
ItemsSource = items,
Template = new FuncControlTemplate<ItemsControl>((_, ns) => scroll.RegisterInNameScope(ns)),
@ -840,5 +879,16 @@ namespace Avalonia.Controls.UnitTests
public event NotifyCollectionChangedEventHandler? CollectionChanged;
}
private class NonRecyclingItemsControl : ItemsControl
{
protected override Type StyleKeyOverride => typeof(ItemsControl);
protected internal override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
{
recycleKey = null;
return true;
}
}
}
}

Loading…
Cancel
Save