| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894 |
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Collections.Specialized;
- using System.Linq;
- using Avalonia.Collections;
- using Avalonia.Controls.Presenters;
- using Avalonia.Controls.Templates;
- using Avalonia.Data;
- using Avalonia.Layout;
- using Avalonia.Media;
- using Avalonia.Styling;
- using Avalonia.UnitTests;
- using Avalonia.VisualTree;
- using Xunit;
- #nullable enable
- namespace Avalonia.Controls.UnitTests
- {
- public class VirtualizingStackPanelTests
- {
- [Fact]
- public void Creates_Initial_Items()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- Assert.Equal(1000, scroll.Extent.Height);
- AssertRealizedItems(target, itemsControl, 0, 10);
- }
- [Fact]
- public void Initializes_Initial_Control_Items()
- {
- using var app = App();
- var items = Enumerable.Range(0, 100).Select(x => new Button { Width = 25, Height = 10});
- var (target, scroll, itemsControl) = CreateTarget(items: items, itemTemplate: null);
- Assert.Equal(1000, scroll.Extent.Height);
- AssertRealizedControlItems<Button>(target, itemsControl, 0, 10);
- }
- [Fact]
- public void Creates_Reassigned_Items()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget(items: Array.Empty<object>());
- Assert.Empty(itemsControl.GetRealizedContainers());
- itemsControl.ItemsSource = new[] { "foo", "bar" };
- Layout(target);
- AssertRealizedItems(target, itemsControl, 0, 2);
- }
- [Fact]
- public void Scrolls_Down_One_Item()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- scroll.Offset = new Vector(0, 10);
- Layout(target);
- AssertRealizedItems(target, itemsControl, 1, 10);
- }
- [Fact]
- public void Scrolls_Down_More_Than_A_Page()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- scroll.Offset = new Vector(0, 200);
- Layout(target);
- AssertRealizedItems(target, itemsControl, 20, 10);
- }
- [Fact]
- public void Scrolls_Down_To_Index()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- target.ScrollIntoView(20);
- AssertRealizedItems(target, itemsControl, 11, 10);
- }
- [Fact]
- public void Scrolls_Up_To_Index()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- scroll.ScrollToEnd();
- Layout(target);
- Assert.Equal(90, target.FirstRealizedIndex);
- target.ScrollIntoView(20);
- AssertRealizedItems(target, itemsControl, 20, 10);
- }
- [Fact]
- public void Scrolling_Up_To_Index_Does_Not_Create_A_Page_Of_Unrealized_Elements()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- scroll.ScrollToEnd();
- Layout(target);
- target.ScrollIntoView(20);
- Assert.Equal(11, target.Children.Count);
- }
- [Fact]
- public void Creates_Elements_On_Item_Insert_1()
- {
- using var app = App();
- var (target, _, itemsControl) = CreateTarget();
- var items = (IList)itemsControl.ItemsSource!;
- Assert.Equal(10, target.GetRealizedElements().Count);
- items.Insert(0, "new");
- Assert.Equal(11, target.GetRealizedElements().Count);
- var indexes = GetRealizedIndexes(target, itemsControl);
- // Blank space inserted in realized elements and subsequent indexes updated.
- Assert.Equal(new[] { -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, indexes);
- var elements = target.GetRealizedElements().ToList();
- Layout(target);
- indexes = GetRealizedIndexes(target, itemsControl);
- // After layout an element for the new element is created.
- Assert.Equal(Enumerable.Range(0, 10), indexes);
- // But apart from the new element and the removed last element, all existing elements
- // should be the same.
- elements[0] = target.GetRealizedElements().ElementAt(0);
- elements.RemoveAt(elements.Count - 1);
- Assert.Equal(elements, target.GetRealizedElements());
- }
- [Fact]
- public void Creates_Elements_On_Item_Insert_2()
- {
- using var app = App();
- var (target, _, itemsControl) = CreateTarget();
- var items = (IList)itemsControl.ItemsSource!;
- Assert.Equal(10, target.GetRealizedElements().Count);
- items.Insert(2, "new");
- Assert.Equal(11, target.GetRealizedElements().Count);
- var indexes = GetRealizedIndexes(target, itemsControl);
- // Blank space inserted in realized elements and subsequent indexes updated.
- Assert.Equal(new[] { 0, 1, -1, 3, 4, 5, 6, 7, 8, 9, 10 }, indexes);
- var elements = target.GetRealizedElements().ToList();
- Layout(target);
- indexes = GetRealizedIndexes(target, itemsControl);
- // After layout an element for the new element is created.
- Assert.Equal(Enumerable.Range(0, 10), indexes);
- // But apart from the new element and the removed last element, all existing elements
- // should be the same.
- elements[2] = target.GetRealizedElements().ElementAt(2);
- elements.RemoveAt(elements.Count - 1);
- Assert.Equal(elements, target.GetRealizedElements());
- }
- [Fact]
- public void Updates_Elements_On_Item_Remove()
- {
- using var app = App();
- var (target, _, itemsControl) = CreateTarget();
- var items = (IList)itemsControl.ItemsSource!;
- Assert.Equal(10, target.GetRealizedElements().Count);
- var toRecycle = target.GetRealizedElements().ElementAt(2);
- items.RemoveAt(2);
- var indexes = GetRealizedIndexes(target, itemsControl);
- // Item removed from realized elements and subsequent row indexes updated.
- Assert.Equal(Enumerable.Range(0, 9), indexes);
- var elements = target.GetRealizedElements().ToList();
- Layout(target);
- indexes = GetRealizedIndexes(target, itemsControl);
- // After layout an element for the newly visible last row is created and indexes updated.
- Assert.Equal(Enumerable.Range(0, 10), indexes);
- // And the removed row should now have been recycled as the last row.
- elements.Add(toRecycle);
- Assert.Equal(elements, target.GetRealizedElements());
- }
- [Fact]
- public void Updates_Elements_On_Item_Replace()
- {
- using var app = App();
- var (target, _, itemsControl) = CreateTarget();
- var items = (ObservableCollection<string>)itemsControl.ItemsSource!;
- Assert.Equal(10, target.GetRealizedElements().Count);
- var toReplace = target.GetRealizedElements().ElementAt(2);
- items[2] = "new";
- // Container being replaced should have been recycled.
- Assert.DoesNotContain(toReplace, target.GetRealizedElements());
- Assert.False(toReplace!.IsVisible);
- var indexes = GetRealizedIndexes(target, itemsControl);
- // Item removed from realized elements at old position and space inserted at new position.
- Assert.Equal(new[] { 0, 1, -1, 3, 4, 5, 6, 7, 8, 9 }, indexes);
- Layout(target);
- indexes = GetRealizedIndexes(target, itemsControl);
- // After layout the missing container should have been created.
- Assert.Equal(Enumerable.Range(0, 10), indexes);
- }
- [Fact]
- public void Updates_Elements_On_Item_Move()
- {
- using var app = App();
- var (target, _, itemsControl) = CreateTarget();
- var items = (ObservableCollection<string>)itemsControl.ItemsSource!;
- Assert.Equal(10, target.GetRealizedElements().Count);
- var toMove = target.GetRealizedElements().ElementAt(2);
- items.Move(2, 6);
- // Container being moved should have been recycled.
- Assert.DoesNotContain(toMove, target.GetRealizedElements());
- Assert.False(toMove!.IsVisible);
- var indexes = GetRealizedIndexes(target, itemsControl);
- // Item removed from realized elements at old position and space inserted at new position.
- Assert.Equal(new[] { 0, 1, 2, 3, 4, 5, -1, 7, 8, 9 }, indexes);
- Layout(target);
- indexes = GetRealizedIndexes(target, itemsControl);
- // After layout the missing container should have been created.
- Assert.Equal(Enumerable.Range(0, 10), indexes);
- }
- [Fact]
- public void Removes_Control_Items_From_Panel_On_Item_Remove()
- {
- using var app = App();
- var items = new ObservableCollection<Button>(Enumerable.Range(0, 100).Select(x => new Button { Width = 25, Height = 10 }));
- var (target, scroll, itemsControl) = CreateTarget(items: items, itemTemplate: null);
- Assert.Equal(1000, scroll.Extent.Height);
- var removed = items[1];
- items.RemoveAt(1);
- Assert.Null(removed.Parent);
- Assert.Null(removed.VisualParent);
- }
- [Fact]
- public void Does_Not_Recycle_Focused_Element()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- var focused = target.GetRealizedElements().First()!;
- focused.Focusable = true;
- focused.Focus();
- Assert.True(target.GetRealizedElements().First()!.IsKeyboardFocusWithin);
- scroll.Offset = new Vector(0, 200);
- Layout(target);
- Assert.All(target.GetRealizedElements(), x => Assert.False(x!.IsKeyboardFocusWithin));
- }
- [Fact]
- public void Removing_Item_Of_Focused_Element_Clears_Focus()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- var focused = target.GetRealizedElements().First()!;
- focused.Focusable = true;
- focused.Focus();
- Assert.True(focused.IsKeyboardFocusWithin);
- scroll.Offset = new Vector(0, 200);
- Layout(target);
- Assert.All(target.GetRealizedElements(), x => Assert.False(x!.IsKeyboardFocusWithin));
- Assert.All(target.GetRealizedElements(), x => Assert.NotSame(focused, x));
- }
- [Fact]
- public void Scrolling_Back_To_Focused_Element_Uses_Correct_Element()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- var focused = target.GetRealizedElements().First()!;
- focused.Focusable = true;
- focused.Focus();
- Assert.True(focused.IsKeyboardFocusWithin);
- scroll.Offset = new Vector(0, 200);
- Layout(target);
- scroll.Offset = new Vector(0, 0);
- Layout(target);
- Assert.Same(focused, target.GetRealizedElements().First());
- }
- [Fact]
- public void Focusing_Another_Element_Recycles_Original_Focus_Element()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- var originalFocused = target.GetRealizedElements().First()!;
- originalFocused.Focusable = true;
- originalFocused.Focus();
- scroll.Offset = new Vector(0, 500);
- Layout(target);
- var newFocused = target.GetRealizedElements().First()!;
- newFocused.Focusable = true;
- newFocused.Focus();
- Assert.False(originalFocused.IsVisible);
- }
- [Fact]
- public void Removing_Range_When_Scrolled_To_End_Updates_Viewport()
- {
- using var app = App();
- var items = new AvaloniaList<string>(Enumerable.Range(0, 100).Select(x => $"Item {x}"));
- var (target, scroll, itemsControl) = CreateTarget(items: items);
- scroll.Offset = new Vector(0, 900);
- Layout(target);
- AssertRealizedItems(target, itemsControl, 90, 10);
- items.RemoveRange(0, 80);
- Layout(target);
- AssertRealizedItems(target, itemsControl, 10, 10);
- Assert.Equal(new Vector(0, 100), scroll.Offset);
- }
- [Fact]
- public void Removing_Range_To_Have_Less_Than_A_Page_Of_Items_When_Scrolled_To_End_Updates_Viewport()
- {
- using var app = App();
- var items = new AvaloniaList<string>(Enumerable.Range(0, 100).Select(x => $"Item {x}"));
- var (target, scroll, itemsControl) = CreateTarget(items: items);
- scroll.Offset = new Vector(0, 900);
- Layout(target);
- AssertRealizedItems(target, itemsControl, 90, 10);
- items.RemoveRange(0, 95);
- Layout(target);
- AssertRealizedItems(target, itemsControl, 0, 5);
- Assert.Equal(new Vector(0, 0), scroll.Offset);
- }
- [Fact]
- public void Resetting_Collection_To_Have_Less_Items_When_Scrolled_To_End_Updates_Viewport()
- {
- using var app = App();
- var items = new ResettingCollection(Enumerable.Range(0, 100).Select(x => $"Item {x}"));
- var (target, scroll, itemsControl) = CreateTarget(items: items);
- scroll.Offset = new Vector(0, 900);
- Layout(target);
- AssertRealizedItems(target, itemsControl, 90, 10);
- items.Reset(Enumerable.Range(0, 20).Select(x => $"Item {x}"));
- Layout(target);
- AssertRealizedItems(target, itemsControl, 10, 10);
- Assert.Equal(new Vector(0, 100), scroll.Offset);
- }
- [Fact]
- public void Resetting_Collection_To_Have_Less_Than_A_Page_Of_Items_When_Scrolled_To_End_Updates_Viewport()
- {
- using var app = App();
- var items = new ResettingCollection(Enumerable.Range(0, 100).Select(x => $"Item {x}"));
- var (target, scroll, itemsControl) = CreateTarget(items: items);
- scroll.Offset = new Vector(0, 900);
- Layout(target);
- AssertRealizedItems(target, itemsControl, 90, 10);
- items.Reset(Enumerable.Range(0, 5).Select(x => $"Item {x}"));
- Layout(target);
- AssertRealizedItems(target, itemsControl, 0, 5);
- Assert.Equal(new Vector(0, 0), scroll.Offset);
- }
- [Fact]
- public void NthChild_Selector_Works()
- {
- using var app = App();
-
- var style = new Style(x => x.OfType<ContentPresenter>().NthChild(5, 0))
- {
- Setters = { new Setter(ListBoxItem.BackgroundProperty, Brushes.Red) },
- };
- var (target, _, _) = CreateTarget(styles: new[] { style });
- var realized = target.GetRealizedContainers()!.Cast<ContentPresenter>().ToList();
-
- Assert.Equal(10, realized.Count);
-
- for (var i = 0; i < 10; ++i)
- {
- var container = realized[i];
- var index = target.IndexFromContainer(container);
- var expectedBackground = (i == 4 || i == 9) ? Brushes.Red : null;
- Assert.Equal(i, index);
- Assert.Equal(expectedBackground, container.Background);
- }
- }
- [Fact]
- public void NthLastChild_Selector_Works()
- {
- using var app = App();
- var style = new Style(x => x.OfType<ContentPresenter>().NthLastChild(5, 0))
- {
- Setters = { new Setter(ListBoxItem.BackgroundProperty, Brushes.Red) },
- };
- var (target, _, _) = CreateTarget(styles: new[] { style });
- var realized = target.GetRealizedContainers()!.Cast<ContentPresenter>().ToList();
- Assert.Equal(10, realized.Count);
- for (var i = 0; i < 10; ++i)
- {
- var container = realized[i];
- var index = target.IndexFromContainer(container);
- var expectedBackground = (i == 0 || i == 5) ? Brushes.Red : null;
- Assert.Equal(i, index);
- Assert.Equal(expectedBackground, container.Background);
- }
- }
- [Fact]
- public void ContainerPrepared_Is_Raised_When_Scrolling()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- var raised = 0;
- itemsControl.ContainerPrepared += (s, e) => ++raised;
- scroll.Offset = new Vector(0, 200);
- Layout(target);
- Assert.Equal(10, raised);
- }
- [Fact]
- public void ContainerClearing_Is_Raised_When_Scrolling()
- {
- using var app = App();
- var (target, scroll, itemsControl) = CreateTarget();
- var raised = 0;
- itemsControl.ContainerClearing += (s, e) => ++raised;
- scroll.Offset = new Vector(0, 200);
- Layout(target);
- Assert.Equal(10, raised);
- }
- [Fact]
- public void Scrolling_Down_With_Larger_Element_Does_Not_Cause_Jump_And_Arrives_At_End()
- {
- using var app = App();
- var items = Enumerable.Range(0, 1000).Select(x => new ItemWithHeight(x)).ToList();
- items[20].Height = 200;
- var itemTemplate = new FuncDataTemplate<ItemWithHeight>((x, _) =>
- new Canvas
- {
- Width = 100,
- [!Canvas.HeightProperty] = new Binding("Height"),
- });
- var (target, scroll, itemsControl) = CreateTarget(items: items, itemTemplate: itemTemplate);
- var index = target.FirstRealizedIndex;
- // Scroll down to the larger element.
- while (target.LastRealizedIndex < items.Count - 1)
- {
- scroll.LineDown();
- Layout(target);
- Assert.True(
- target.FirstRealizedIndex >= index,
- $"{target.FirstRealizedIndex} is not greater or equal to {index}");
- if (scroll.Offset.Y + scroll.Viewport.Height == scroll.Extent.Height)
- Assert.Equal(items.Count - 1, target.LastRealizedIndex);
- index = target.FirstRealizedIndex;
- }
- }
- [Fact]
- public void Scrolling_Up_To_Larger_Element_Does_Not_Cause_Jump()
- {
- using var app = App();
- var items = Enumerable.Range(0, 100).Select(x => new ItemWithHeight(x)).ToList();
- items[20].Height = 200;
- var itemTemplate = new FuncDataTemplate<ItemWithHeight>((x, _) =>
- new Canvas
- {
- Width = 100,
- [!Canvas.HeightProperty] = new Binding("Height"),
- });
- var (target, scroll, itemsControl) = CreateTarget(items: items, itemTemplate: itemTemplate);
- // Scroll past the larger element.
- scroll.Offset = new Vector(0, 600);
- Layout(target);
- // Precondition checks
- Assert.True(target.FirstRealizedIndex > 20);
- var index = target.FirstRealizedIndex;
- // Scroll up to the top.
- while (scroll.Offset.Y > 0)
- {
- scroll.LineUp();
- Layout(target);
- Assert.True(target.FirstRealizedIndex <= index, $"{target.FirstRealizedIndex} is not less than {index}");
- index = target.FirstRealizedIndex;
- }
- }
-
- [Fact]
- public void Scrolling_Up_To_Smaller_Element_Does_Not_Cause_Jump()
- {
- using var app = App();
- var items = Enumerable.Range(0, 100).Select(x => new ItemWithHeight(x, 30)).ToList();
- items[20].Height = 25;
- var itemTemplate = new FuncDataTemplate<ItemWithHeight>((x, _) =>
- new Canvas
- {
- Width = 100,
- [!Canvas.HeightProperty] = new Binding("Height"),
- });
- var (target, scroll, itemsControl) = CreateTarget(items: items, itemTemplate: itemTemplate);
- // Scroll past the larger element.
- scroll.Offset = new Vector(0, 25 * items[0].Height);
- Layout(target);
- // Precondition checks
- Assert.True(target.FirstRealizedIndex > 20);
- var index = target.FirstRealizedIndex;
- // Scroll up to the top.
- while (scroll.Offset.Y > 0)
- {
- scroll.Offset = scroll.Offset - new Vector(0, 5);
- Layout(target);
- Assert.True(
- target.FirstRealizedIndex <= index,
- $"{target.FirstRealizedIndex} is not less than {index}");
- Assert.True(
- index - target.FirstRealizedIndex <= 1,
- $"FirstIndex changed from {index} to {target.FirstRealizedIndex}");
-
- index = target.FirstRealizedIndex;
- }
- }
- [Fact]
- public void Does_Not_Throw_When_Estimating_Viewport_With_Ancestor_Margin()
- {
- // Issue #11272
- using var app = App();
- var (_, _, itemsControl) = CreateUnrootedTarget<ItemsControl>();
- var container = new Decorator { Margin = new Thickness(100) };
- var root = new TestRoot(true, container);
-
- root.LayoutManager.ExecuteInitialLayoutPass();
- container.Child = itemsControl;
- 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<ItemsControl>(items: items);
- var container = new Decorator { Margin = new Thickness(100), Child = itemsControl };
- var root = new TestRoot(true, container);
- root.LayoutManager.ExecuteInitialLayoutPass();
- // Clear the items and do a layout to recycle all elements.
- itemsControl.ItemsSource = null;
- root.LayoutManager.ExecuteLayoutPass();
- // Should have no realized elements and 3 unrealized elements.
- Assert.Equal(0, target.GetRealizedElements().Count);
- Assert.Equal(3, target.Children.Count);
- // Make the panel effectively invisible and set items.
- container.IsVisible = false;
- itemsControl.ItemsSource = items;
- // Try to scroll into view while effectively invisible.
- target.ScrollIntoView(0);
- // Make the panel visible and layout.
- container.IsVisible = true;
- root.LayoutManager.ExecuteLayoutPass();
- // Should have 3 realized elements and no unrealized elements.
- Assert.Equal(3, target.GetRealizedElements().Count);
- Assert.Equal(3, target.Children.Count);
- }
- private static IReadOnlyList<int> GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl)
- {
- return target.GetRealizedElements()
- .Select(x => x is null ? -1 : itemsControl.IndexFromContainer((Control)x))
- .ToList();
- }
- private static void AssertRealizedItems(
- VirtualizingStackPanel target,
- ItemsControl itemsControl,
- int firstIndex,
- int count)
- {
- Assert.All(target.GetRealizedContainers()!, x => Assert.Same(target, x.VisualParent));
- Assert.All(target.GetRealizedContainers()!, x => Assert.Same(itemsControl, x.Parent));
- var childIndexes = target.GetRealizedContainers()!
- .Select(x => itemsControl.IndexFromContainer(x))
- .Where(x => x >= 0)
- .OrderBy(x => x)
- .ToList();
- Assert.Equal(Enumerable.Range(firstIndex, count), childIndexes);
- }
- private static void AssertRealizedControlItems<TContainer>(
- VirtualizingStackPanel target,
- ItemsControl itemsControl,
- int firstIndex,
- int count)
- {
- Assert.All(target.GetRealizedContainers()!, x => Assert.IsType<TContainer>(x));
- Assert.All(target.GetRealizedContainers()!, x => Assert.Same(target, x.VisualParent));
- Assert.All(target.GetRealizedContainers()!, x => Assert.Same(itemsControl, x.Parent));
- var childIndexes = target.GetRealizedContainers()!
- .Select(x => itemsControl.IndexFromContainer(x))
- .Where(x => x >= 0)
- .OrderBy(x => x)
- .ToList();
- Assert.Equal(Enumerable.Range(firstIndex, count), childIndexes);
- }
- private static (VirtualizingStackPanel, ScrollViewer, ItemsControl) CreateTarget(
- IEnumerable<object>? items = null,
- Optional<IDataTemplate?> itemTemplate = default,
- IEnumerable<Style>? styles = null)
- {
- var (target, scroll, itemsControl) = CreateUnrootedTarget<ItemsControl>(items, itemTemplate);
- var root = CreateRoot(itemsControl, styles);
- root.LayoutManager.ExecuteInitialLayoutPass();
- return (target, scroll, itemsControl);
- }
- private static (VirtualizingStackPanel, ScrollViewer, T) CreateUnrootedTarget<T>(
- IEnumerable<object>? items = null,
- Optional<IDataTemplate?> itemTemplate = default)
- where T : ItemsControl, new()
- {
- var target = new VirtualizingStackPanel();
- items ??= new ObservableCollection<string>(Enumerable.Range(0, 100).Select(x => $"Item {x}"));
- var presenter = new ItemsPresenter
- {
- [~ItemsPresenter.ItemsPanelProperty] = new TemplateBinding(ItemsPresenter.ItemsPanelProperty),
- };
- var scroll = new ScrollViewer
- {
- Name = "PART_ScrollViewer",
- Content = presenter,
- Template = ScrollViewerTemplate(),
- };
- var itemsControl = new T
- {
- ItemsSource = items,
- Template = new FuncControlTemplate<ItemsControl>((_, ns) => scroll.RegisterInNameScope(ns)),
- ItemsPanel = new FuncTemplate<Panel?>(() => target),
- ItemTemplate = itemTemplate.GetValueOrDefault(DefaultItemTemplate()),
- };
- return (target, scroll, itemsControl);
- }
- private static TestRoot CreateRoot(Control? child, IEnumerable<Style>? styles = null)
- {
- var root = new TestRoot(true, child);
- root.ClientSize = new(100, 100);
- if (styles is not null)
- root.Styles.AddRange(styles);
- return root;
- }
- private static IDataTemplate DefaultItemTemplate()
- {
- return new FuncDataTemplate<object>((x, _) => new Canvas { Width = 100, Height = 10 });
- }
- private static void Layout(Control target)
- {
- var root = (ILayoutRoot?)target.GetVisualRoot();
- root?.LayoutManager.ExecuteLayoutPass();
- }
- private static IControlTemplate ScrollViewerTemplate()
- {
- return new FuncControlTemplate<ScrollViewer>((x, ns) =>
- new ScrollContentPresenter
- {
- Name = "PART_ContentPresenter",
- }.RegisterInNameScope(ns));
- }
- private static IDisposable App() => UnitTestApplication.Start(TestServices.RealFocus);
- private class ItemWithHeight
- {
- public ItemWithHeight(int index, double height = 10)
- {
- Caption = $"Item {index}";
- Height = height;
- }
-
- public string Caption { get; set; }
- public double Height { get; set; }
- }
- private class ResettingCollection : List<string>, INotifyCollectionChanged
- {
- public ResettingCollection(IEnumerable<string> items)
- {
- AddRange(items);
- }
- public void Reset(IEnumerable<string> items)
- {
- Clear();
- AddRange(items);
- CollectionChanged?.Invoke(
- this,
- new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
- }
- 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;
- }
- }
- }
- }
|