Browse Source

WIP: Adding virtualization to ItemsPresenter.

pull/554/head
Steven Kirk 10 years ago
parent
commit
f9b3d2ac03
  1. 3
      src/Avalonia.Controls/Avalonia.Controls.csproj
  2. 22
      src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
  3. 6
      src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs
  4. 84
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  5. 6
      src/Avalonia.Controls/Generators/ItemContainerInfo.cs
  6. 6
      src/Avalonia.Controls/Generators/TreeContainerIndex.cs
  7. 6
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  8. 21
      src/Avalonia.Controls/ItemVirtualizationMode.cs
  9. 8
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  10. 127
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  11. 2
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  12. 1
      tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
  13. 31
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs
  14. 19
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs
  15. 103
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

3
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -50,13 +50,14 @@
<Compile Include="Design.cs" />
<Compile Include="DockPanel.cs" />
<Compile Include="Expander.cs" />
<Compile Include="Generators\ItemContainer.cs" />
<Compile Include="Generators\ItemContainerInfo.cs" />
<Compile Include="Generators\TreeContainerIndex.cs" />
<Compile Include="HotkeyManager.cs" />
<Compile Include="IApplicationLifecycle.cs" />
<Compile Include="INameScope.cs" />
<Compile Include="IPseudoClasses.cs" />
<Compile Include="DropDownItem.cs" />
<Compile Include="ItemVirtualizationMode.cs" />
<Compile Include="IVirtualizingPanel.cs" />
<Compile Include="LayoutTransformControl.cs" />
<Compile Include="Mixins\ContentControlMixin.cs" />

22
src/Avalonia.Controls/Generators/IItemContainerGenerator.cs

@ -16,7 +16,7 @@ namespace Avalonia.Controls.Generators
/// <summary>
/// Gets the currently realized containers.
/// </summary>
IEnumerable<ItemContainer> Containers { get; }
IEnumerable<ItemContainerInfo> Containers { get; }
/// <summary>
/// Gets or sets the data template used to display the items in the control.
@ -34,17 +34,17 @@ namespace Avalonia.Controls.Generators
event EventHandler<ItemContainerEventArgs> Dematerialized;
/// <summary>
/// Creates container controls for a collection of items.
/// Creates a container control for an item.
/// </summary>
/// <param name="startingIndex">
/// The index of the first item of the data in the containing collection.
/// <param name="index">
/// The index of the item of the data in the containing collection.
/// </param>
/// <param name="items">The items.</param>
/// <param name="item">The item.</param>
/// <param name="selector">An optional member selector.</param>
/// <returns>The created controls.</returns>
IEnumerable<ItemContainer> Materialize(
int startingIndex,
IEnumerable items,
ItemContainerInfo Materialize(
int index,
object item,
IMemberSelector selector);
/// <summary>
@ -55,7 +55,7 @@ namespace Avalonia.Controls.Generators
/// </param>
/// <param name="count">The the number of items to remove.</param>
/// <returns>The removed containers.</returns>
IEnumerable<ItemContainer> Dematerialize(int startingIndex, int count);
IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count);
/// <summary>
/// Inserts space for newly inserted containers in the index.
@ -73,13 +73,13 @@ namespace Avalonia.Controls.Generators
/// </param>
/// <param name="count">The the number of items to remove.</param>
/// <returns>The removed containers.</returns>
IEnumerable<ItemContainer> RemoveRange(int startingIndex, int count);
IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count);
/// <summary>
/// Clears all created containers and returns the removed controls.
/// </summary>
/// <returns>The removed controls.</returns>
IEnumerable<ItemContainer> Clear();
IEnumerable<ItemContainerInfo> Clear();
/// <summary>
/// Gets the container control representing the item with the specified index.

6
src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs

@ -19,7 +19,7 @@ namespace Avalonia.Controls.Generators
/// <param name="container">The container.</param>
public ItemContainerEventArgs(
int startingIndex,
ItemContainer container)
ItemContainerInfo container)
{
StartingIndex = startingIndex;
Containers = new[] { container };
@ -32,7 +32,7 @@ namespace Avalonia.Controls.Generators
/// <param name="containers">The containers.</param>
public ItemContainerEventArgs(
int startingIndex,
IList<ItemContainer> containers)
IList<ItemContainerInfo> containers)
{
StartingIndex = startingIndex;
Containers = containers;
@ -41,7 +41,7 @@ namespace Avalonia.Controls.Generators
/// <summary>
/// Gets the containers.
/// </summary>
public IList<ItemContainer> Containers { get; }
public IList<ItemContainerInfo> Containers { get; }
/// <summary>
/// Gets the index of the first container in the source items.

84
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls.Generators
/// </summary>
public class ItemContainerGenerator : IItemContainerGenerator
{
private List<ItemContainer> _containers = new List<ItemContainer>();
private List<ItemContainerInfo> _containers = new List<ItemContainerInfo>();
/// <summary>
/// Initializes a new instance of the <see cref="ItemContainerGenerator"/> class.
@ -29,7 +29,7 @@ namespace Avalonia.Controls.Generators
}
/// <inheritdoc/>
public IEnumerable<ItemContainer> Containers => _containers.Where(x => x != null);
public IEnumerable<ItemContainerInfo> Containers => _containers.Where(x => x != null);
/// <inheritdoc/>
public event EventHandler<ItemContainerEventArgs> Materialized;
@ -48,33 +48,24 @@ namespace Avalonia.Controls.Generators
public IControl Owner { get; }
/// <inheritdoc/>
public IEnumerable<ItemContainer> Materialize(
int startingIndex,
IEnumerable items,
public ItemContainerInfo Materialize(
int index,
object item,
IMemberSelector selector)
{
Contract.Requires<ArgumentNullException>(items != null);
var i = selector != null ? selector.Select(item) : item;
var container = new ItemContainerInfo(CreateContainer(i), item, index);
int index = startingIndex;
var result = new List<ItemContainer>();
AddContainer(container);
Materialized?.Invoke(this, new ItemContainerEventArgs(index, container));
foreach (var item in items)
{
var i = selector != null ? selector.Select(item) : item;
var container = new ItemContainer(CreateContainer(i), item, index++);
result.Add(container);
}
AddContainers(result);
Materialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
return result.Where(x => x != null).ToList();
return container;
}
/// <inheritdoc/>
public virtual IEnumerable<ItemContainer> Dematerialize(int startingIndex, int count)
public virtual IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count)
{
var result = new List<ItemContainer>();
var result = new List<ItemContainerInfo>();
for (int i = startingIndex; i < startingIndex + count; ++i)
{
@ -93,13 +84,13 @@ namespace Avalonia.Controls.Generators
/// <inheritdoc/>
public virtual void InsertSpace(int index, int count)
{
_containers.InsertRange(index, Enumerable.Repeat<ItemContainer>(null, count));
_containers.InsertRange(index, Enumerable.Repeat<ItemContainerInfo>(null, count));
}
/// <inheritdoc/>
public virtual IEnumerable<ItemContainer> RemoveRange(int startingIndex, int count)
public virtual IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count)
{
List<ItemContainer> result = new List<ItemContainer>();
List<ItemContainerInfo> result = new List<ItemContainerInfo>();
if (startingIndex < _containers.Count)
{
@ -112,10 +103,10 @@ namespace Avalonia.Controls.Generators
}
/// <inheritdoc/>
public virtual IEnumerable<ItemContainer> Clear()
public virtual IEnumerable<ItemContainerInfo> Clear()
{
var result = _containers.Where(x => x != null).ToList();
_containers = new List<ItemContainer>();
_containers = new List<ItemContainerInfo>();
if (result.Count > 0)
{
@ -172,32 +163,29 @@ namespace Avalonia.Controls.Generators
}
/// <summary>
/// Adds a collection of containers to the index.
/// Adds a container to the index.
/// </summary>
/// <param name="containers">The containers.</param>
protected void AddContainers(IList<ItemContainer> containers)
/// <param name="container">The container.</param>
protected void AddContainer(ItemContainerInfo container)
{
Contract.Requires<ArgumentNullException>(containers != null);
Contract.Requires<ArgumentNullException>(container != null);
foreach (var c in containers)
while (_containers.Count < container.Index)
{
while (_containers.Count < c.Index)
{
_containers.Add(null);
}
_containers.Add(null);
}
if (_containers.Count == c.Index)
{
_containers.Add(c);
}
else if (_containers[c.Index] == null)
{
_containers[c.Index] = c;
}
else
{
throw new InvalidOperationException("Container already created.");
}
if (_containers.Count == container.Index)
{
_containers.Add(container);
}
else if (_containers[container.Index] == null)
{
_containers[container.Index] = container;
}
else
{
throw new InvalidOperationException("Container already created.");
}
}
@ -207,7 +195,7 @@ namespace Avalonia.Controls.Generators
/// <param name="index">The first index.</param>
/// <param name="count">The number of elements in the range.</param>
/// <returns>The containers.</returns>
protected IEnumerable<ItemContainer> GetContainerRange(int index, int count)
protected IEnumerable<ItemContainerInfo> GetContainerRange(int index, int count)
{
return _containers.GetRange(index, count);
}

6
src/Avalonia.Controls/Generators/ItemContainer.cs → src/Avalonia.Controls/Generators/ItemContainerInfo.cs

@ -7,17 +7,17 @@ namespace Avalonia.Controls.Generators
/// Holds information about an item container generated by an
/// <see cref="IItemContainerGenerator"/>.
/// </summary>
public class ItemContainer
public class ItemContainerInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemContainer"/> class.
/// Initializes a new instance of the <see cref="ItemContainerInfo"/> class.
/// </summary>
/// <param name="container">The container control.</param>
/// <param name="item">The item that the container represents.</param>
/// <param name="index">
/// The index of the item in the <see cref="ItemsControl.Items"/> collection.
/// </param>
public ItemContainer(IControl container, object item, int index)
public ItemContainerInfo(IControl container, object item, int index)
{
ContainerControl = container;
Item = item;

6
src/Avalonia.Controls/Generators/TreeContainerIndex.cs

@ -47,7 +47,7 @@ namespace Avalonia.Controls.Generators
Materialized?.Invoke(
this,
new ItemContainerEventArgs(0, new ItemContainer(container, item, 0)));
new ItemContainerEventArgs(0, new ItemContainerInfo(container, item, 0)));
}
/// <summary>
@ -62,14 +62,14 @@ namespace Avalonia.Controls.Generators
Dematerialized?.Invoke(
this,
new ItemContainerEventArgs(0, new ItemContainer(container, item, 0)));
new ItemContainerEventArgs(0, new ItemContainerInfo(container, item, 0)));
}
/// <summary>
/// Removes a set of containers from the index.
/// </summary>
/// <param name="containers">The item containers.</param>
public void Remove(IEnumerable<ItemContainer> containers)
public void Remove(IEnumerable<ItemContainerInfo> containers)
{
foreach (var container in containers)
{

6
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -99,20 +99,20 @@ namespace Avalonia.Controls.Generators
}
}
public override IEnumerable<ItemContainer> Clear()
public override IEnumerable<ItemContainerInfo> Clear()
{
var items = base.Clear();
Index.Remove(items);
return items;
}
public override IEnumerable<ItemContainer> Dematerialize(int startingIndex, int count)
public override IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count)
{
Index.Remove(GetContainerRange(startingIndex, count));
return base.Dematerialize(startingIndex, count);
}
public override IEnumerable<ItemContainer> RemoveRange(int startingIndex, int count)
public override IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count)
{
Index.Remove(GetContainerRange(startingIndex, count));
return base.RemoveRange(startingIndex, count);

21
src/Avalonia.Controls/ItemVirtualizationMode.cs

@ -0,0 +1,21 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Controls
{
/// <summary>
/// Describes the item virtualization method to use for a list.
/// </summary>
public enum ItemVirtualizationMode
{
/// <summary>
/// Do not virtualize items.
/// </summary>
None,
/// <summary>
/// Virtualize items without smooth scrolling.
/// </summary>
Simple,
}
}

8
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@ -175,12 +175,8 @@ namespace Avalonia.Controls.Presenters
if (container == null)
{
var item = Items.Cast<object>().ElementAt(index);
var materialized = ItemContainerGenerator.Materialize(
index,
new[] { item },
MemberSelector);
container = materialized.First().ContainerControl;
Panel.Children.Add(container);
var materialized = ItemContainerGenerator.Materialize(index, item, MemberSelector);
Panel.Children.Add(materialized.ContainerControl);
}
return container;

127
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -1,9 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
@ -12,8 +15,18 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Displays items inside an <see cref="ItemsControl"/>.
/// </summary>
public class ItemsPresenter : ItemsPresenterBase
public class ItemsPresenter : ItemsPresenterBase, IScrollable
{
/// <summary>
/// Defines the <see cref="VirtualizationMode"/> property.
/// </summary>
public static readonly StyledProperty<ItemVirtualizationMode> VirtualizationModeProperty =
AvaloniaProperty.Register<ItemsPresenter, ItemVirtualizationMode>(
nameof(VirtualizationMode),
defaultValue: ItemVirtualizationMode.Simple);
private VirtualizationInfo _virt;
/// <summary>
/// Initializes static members of the <see cref="ItemsPresenter"/> class.
/// </summary>
@ -24,11 +37,72 @@ namespace Avalonia.Controls.Presenters
KeyboardNavigationMode.Once);
}
/// <summary>
/// Gets or sets the virtualization mode for the items.
/// </summary>
public ItemVirtualizationMode VirtualizationMode
{
get { return GetValue(VirtualizationModeProperty); }
set { SetValue(VirtualizationModeProperty, value); }
}
/// <inheritdoc/>
bool IScrollable.IsLogicalScrollEnabled
{
get { return _virt != null && VirtualizationMode != ItemVirtualizationMode.None; }
}
/// <inheritdoc/>
Action IScrollable.InvalidateScroll { get; set; }
Size IScrollable.Extent
{
get
{
switch (VirtualizationMode)
{
case ItemVirtualizationMode.Simple:
return new Size(0, Items?.Count() ?? 0);
default:
return default(Size);
}
}
}
Vector IScrollable.Offset { get; set; }
Size IScrollable.Viewport
{
get
{
throw new NotImplementedException();
}
}
Size IScrollable.ScrollSize
{
get
{
throw new NotImplementedException();
}
}
Size IScrollable.PageScrollSize
{
get
{
throw new NotImplementedException();
}
}
/// <inheritdoc/>
protected override void CreatePanel()
{
base.CreatePanel();
var virtualizingPanel = Panel as IVirtualizingPanel;
_virt = virtualizingPanel != null ? new VirtualizationInfo(virtualizingPanel) : null;
if (!Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty))
{
KeyboardNavigation.SetDirectionalNavigation(
@ -55,7 +129,7 @@ namespace Avalonia.Controls.Presenters
generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count);
}
AddContainers(generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector));
AddContainers(e.NewStartingIndex, e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
@ -64,8 +138,7 @@ namespace Avalonia.Controls.Presenters
case NotifyCollectionChangedAction.Replace:
RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count));
var containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector);
AddContainers(containers);
var containers = AddContainers(e.NewStartingIndex, e.NewItems);
var i = e.NewStartingIndex;
@ -83,7 +156,7 @@ namespace Avalonia.Controls.Presenters
if (Items != null)
{
AddContainers(generator.Materialize(0, Items, MemberSelector));
AddContainers(0, Items);
}
break;
@ -92,17 +165,20 @@ namespace Avalonia.Controls.Presenters
InvalidateMeasure();
}
private void AddContainersToPanel(IEnumerable<ItemContainer> items)
private IList<ItemContainerInfo> AddContainers(int index, IEnumerable items)
{
foreach (var i in items)
var generator = ItemContainerGenerator;
var result = new List<ItemContainerInfo>();
foreach (var item in items)
{
var i = generator.Materialize(index++, item, MemberSelector);
if (i.ContainerControl != null)
{
if (i.Index < this.Panel.Children.Count)
{
// HACK: This will insert at the wrong place when there are null items,
// but all of this will need to be rewritten when we implement
// virtualization so hope no-one notices until then :)
// TODO: This will insert at the wrong place when there are null items.
this.Panel.Children.Insert(i.Index, i.ContainerControl);
}
else
@ -110,39 +186,34 @@ namespace Avalonia.Controls.Presenters
this.Panel.Children.Add(i.ContainerControl);
}
}
result.Add(i);
}
return result;
}
private void AddContainers(IEnumerable<ItemContainer> items)
private void RemoveContainers(IEnumerable<ItemContainerInfo> items)
{
foreach (var i in items)
{
if (i.ContainerControl != null)
{
if (i.Index < this.Panel.Children.Count)
{
// HACK: This will insert at the wrong place when there are null items,
// but all of this will need to be rewritten when we implement
// virtualization so hope no-one notices until then :)
this.Panel.Children.Insert(i.Index, i.ContainerControl);
}
else
{
this.Panel.Children.Add(i.ContainerControl);
}
this.Panel.Children.Remove(i.ContainerControl);
}
}
}
private void RemoveContainers(IEnumerable<ItemContainer> items)
private class VirtualizationInfo
{
foreach (var i in items)
public VirtualizationInfo(IVirtualizingPanel panel)
{
if (i.ContainerControl != null)
{
this.Panel.Children.Remove(i.ContainerControl);
}
Panel = panel;
}
public IVirtualizingPanel Panel { get; }
public int FirstIndex { get; set; }
public int LastIndex { get; set; }
}
}
}

2
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -206,7 +206,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Called when the items for the presenter change, either because <see cref="Items"/>
/// has been set, or the items collection has been modified.
/// has been set, the items collection has been modified, or the panel has been created.
/// </summary>
/// <param name="e">A description of the change.</param>
protected abstract void ItemsChanged(NotifyCollectionChangedEventArgs e);

1
tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj

@ -91,6 +91,7 @@
<ItemGroup>
<Compile Include="ClassesTests.cs" />
<Compile Include="LayoutTransformControlTests.cs" />
<Compile Include="Presenters\ItemsPresenterTests_Virtualization.cs" />
<Compile Include="TextBoxTests_ValidationState.cs" />
<Compile Include="UserControlTests.cs" />
<Compile Include="DockPanelTests.cs" />

31
tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs

@ -1,8 +1,11 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Templates;
using Xunit;
namespace Avalonia.Controls.UnitTests.Generators
@ -15,7 +18,7 @@ namespace Avalonia.Controls.UnitTests.Generators
var items = new[] { "foo", "bar", "baz" };
var owner = new Decorator();
var target = new ItemContainerGenerator(owner);
var containers = target.Materialize(0, items, null);
var containers = Materialize(target, 0, items);
var result = containers
.Select(x => x.ContainerControl)
.OfType<TextBlock>()
@ -31,20 +34,36 @@ namespace Avalonia.Controls.UnitTests.Generators
var items = new[] { "foo", "bar", "baz" };
var owner = new Decorator();
var target = new ItemContainerGenerator(owner);
var containers = target.Materialize(0, items, null).ToList();
var containers = Materialize(target, 0, items);
Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0));
Assert.Equal(containers[1].ContainerControl, target.ContainerFromIndex(1));
Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2));
}
private IList<ItemContainerInfo> Materialize(
IItemContainerGenerator generator,
int index,
string[] items)
{
var result = new List<ItemContainerInfo>();
foreach (var item in items)
{
var container = generator.Materialize(index++, item, null);
result.Add(container);
}
return result;
}
[Fact]
public void IndexFromContainer_Should_Return_Index()
{
var items = new[] { "foo", "bar", "baz" };
var owner = new Decorator();
var target = new ItemContainerGenerator(owner);
var containers = target.Materialize(0, items, null).ToList();
var containers = Materialize(target, 0, items);
Assert.Equal(0, target.IndexFromContainer(containers[0].ContainerControl));
Assert.Equal(1, target.IndexFromContainer(containers[1].ContainerControl));
@ -57,7 +76,7 @@ namespace Avalonia.Controls.UnitTests.Generators
var items = new[] { "foo", "bar", "baz" };
var owner = new Decorator();
var target = new ItemContainerGenerator(owner);
var containers = target.Materialize(0, items, null).ToList();
var containers = Materialize(target, 0, items);
target.Dematerialize(1, 1);
@ -72,7 +91,7 @@ namespace Avalonia.Controls.UnitTests.Generators
var items = new[] { "foo", "bar", "baz" };
var owner = new Decorator();
var target = new ItemContainerGenerator(owner);
var containers = target.Materialize(0, items, null);
var containers = Materialize(target, 0, items);
var expected = target.Containers.Take(2).ToList();
var result = target.Dematerialize(0, 2);
@ -85,7 +104,7 @@ namespace Avalonia.Controls.UnitTests.Generators
var items = new[] { "foo", "bar", "baz" };
var owner = new Decorator();
var target = new ItemContainerGenerator(owner);
var containers = target.Materialize(0, items, null).ToList();
var containers = Materialize(target, 0, items);
var removed = target.RemoveRange(1, 1).Single();

19
tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Generators;
using Xunit;
@ -15,7 +16,7 @@ namespace Avalonia.Controls.UnitTests.Generators
var items = new[] { "foo", "bar", "baz" };
var owner = new Decorator();
var target = new ItemContainerGenerator<ListBoxItem>(owner, ListBoxItem.ContentProperty, null);
var containers = target.Materialize(0, items, null);
var containers = Materialize(target, 0, items);
var result = containers
.Select(x => x.ContainerControl)
.OfType<ListBoxItem>()
@ -24,5 +25,21 @@ namespace Avalonia.Controls.UnitTests.Generators
Assert.Equal(items, result);
}
private IList<ItemContainerInfo> Materialize(
IItemContainerGenerator generator,
int index,
string[] items)
{
var result = new List<ItemContainerInfo>();
foreach (var item in items)
{
var container = generator.Materialize(index++, item, null);
result.Add(container);
}
return result;
}
}
}

103
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

@ -0,0 +1,103 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests.Presenters
{
public class ItemsPresenterTests_Virtualization
{
[Fact]
public void Should_Return_IsLogicalScrollEnabled_False_When_Has_No_Virtualizing_Panel()
{
var target = new ItemsPresenter
{
};
target.ApplyTemplate();
Assert.False(((IScrollable)target).IsLogicalScrollEnabled);
}
[Fact]
public void Should_Return_IsLogicalScrollEnabled_False_When_VirtualizationMode_None()
{
var target = new ItemsPresenter
{
ItemsPanel = VirtualizingPanelTemplate(),
VirtualizationMode = ItemVirtualizationMode.None,
};
target.ApplyTemplate();
Assert.False(((IScrollable)target).IsLogicalScrollEnabled);
}
[Fact]
public void Should_Return_IsLogicalScrollEnabled_True_When_Has_Virtualizing_Panel()
{
var target = new ItemsPresenter
{
ItemsPanel = VirtualizingPanelTemplate(),
};
target.ApplyTemplate();
Assert.True(((IScrollable)target).IsLogicalScrollEnabled);
}
public class Simple
{
[Fact]
public void Should_Return_Items_Count_For_Extent()
{
var target = new ItemsPresenter
{
Items = new string[10],
ItemsPanel = VirtualizingPanelTemplate(),
VirtualizationMode = ItemVirtualizationMode.Simple,
};
target.ApplyTemplate();
Assert.Equal(new Size(0, 10), ((IScrollable)target).Extent);
}
[Fact]
public void Should_Have_Number_Of_Visible_Items_As_Viewport()
{
var target = new ItemsPresenter
{
Items = new string[20],
ItemsPanel = VirtualizingPanelTemplate(),
ItemTemplate = ItemTemplate(),
VirtualizationMode = ItemVirtualizationMode.Simple,
};
target.ApplyTemplate();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(10, ((IScrollable)target).Viewport.Height);
}
}
private static IDataTemplate ItemTemplate()
{
return new FuncDataTemplate<string>(x => new TextBlock
{
Text = x,
Height = 10,
});
}
private static ITemplate<IPanel> VirtualizingPanelTemplate()
{
return new FuncTemplate<IPanel>(() => new VirtualizingStackPanel());
}
}
}
Loading…
Cancel
Save