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

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

@ -646,7 +646,7 @@ namespace Avalonia.Controls.UnitTests
{ {
// Issue #11272 // Issue #11272
using var app = App(); using var app = App();
var (_, _, itemsControl) = CreateUnrootedTarget(); var (_, _, itemsControl) = CreateUnrootedTarget<ItemsControl>();
var container = new Decorator { Margin = new Thickness(100) }; var container = new Decorator { Margin = new Thickness(100) };
var root = new TestRoot(true, container); var root = new TestRoot(true, container);
@ -657,11 +657,49 @@ namespace Avalonia.Controls.UnitTests
root.LayoutManager.ExecuteLayoutPass(); 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] [Fact]
public void ScrollIntoView_On_Effectively_Invisible_Panel_Does_Not_Create_Ghost_Elements() public void ScrollIntoView_On_Effectively_Invisible_Panel_Does_Not_Create_Ghost_Elements()
{ {
var items = new[] { "foo", "bar", "baz" }; 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 container = new Decorator { Margin = new Thickness(100), Child = itemsControl };
var root = new TestRoot(true, container); var root = new TestRoot(true, container);
@ -738,7 +776,7 @@ namespace Avalonia.Controls.UnitTests
Optional<IDataTemplate?> itemTemplate = default, Optional<IDataTemplate?> itemTemplate = default,
IEnumerable<Style>? styles = null) IEnumerable<Style>? styles = null)
{ {
var (target, scroll, itemsControl) = CreateUnrootedTarget(items, itemTemplate); var (target, scroll, itemsControl) = CreateUnrootedTarget<ItemsControl>(items, itemTemplate);
var root = CreateRoot(itemsControl, styles); var root = CreateRoot(itemsControl, styles);
root.LayoutManager.ExecuteInitialLayoutPass(); root.LayoutManager.ExecuteInitialLayoutPass();
@ -746,9 +784,10 @@ namespace Avalonia.Controls.UnitTests
return (target, scroll, itemsControl); return (target, scroll, itemsControl);
} }
private static (VirtualizingStackPanel, ScrollViewer, ItemsControl) CreateUnrootedTarget( private static (VirtualizingStackPanel, ScrollViewer, T) CreateUnrootedTarget<T>(
IEnumerable<object>? items = null, IEnumerable<object>? items = null,
Optional<IDataTemplate?> itemTemplate = default) Optional<IDataTemplate?> itemTemplate = default)
where T : ItemsControl, new()
{ {
var target = new VirtualizingStackPanel(); var target = new VirtualizingStackPanel();
@ -766,7 +805,7 @@ namespace Avalonia.Controls.UnitTests
Template = ScrollViewerTemplate(), Template = ScrollViewerTemplate(),
}; };
var itemsControl = new ItemsControl var itemsControl = new T
{ {
ItemsSource = items, ItemsSource = items,
Template = new FuncControlTemplate<ItemsControl>((_, ns) => scroll.RegisterInNameScope(ns)), Template = new FuncControlTemplate<ItemsControl>((_, ns) => scroll.RegisterInNameScope(ns)),
@ -840,5 +879,16 @@ namespace Avalonia.Controls.UnitTests
public event NotifyCollectionChangedEventHandler? CollectionChanged; 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