Browse Source

More WIP on virtualization.

Kinda nearly working in the test app.
pull/554/head
Steven Kirk 10 years ago
parent
commit
1d83126d20
  1. 12
      src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
  2. 26
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  3. 22
      src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
  4. 5
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  5. 79
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  6. 29
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  7. 34
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

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

@ -37,7 +37,7 @@ namespace Avalonia.Controls.Generators
/// Creates a container control for an item.
/// </summary>
/// <param name="index">
/// The index of the item of the data in the containing collection.
/// The index of the item of data in the control's items.
/// </param>
/// <param name="item">The item.</param>
/// <param name="selector">An optional member selector.</param>
@ -51,7 +51,7 @@ namespace Avalonia.Controls.Generators
/// Removes a set of created containers.
/// </summary>
/// <param name="startingIndex">
/// The index of the first item of the data in the containing collection.
/// The index of the first item in the control's items.
/// </param>
/// <param name="count">The the number of items to remove.</param>
/// <returns>The removed containers.</returns>
@ -69,12 +69,18 @@ namespace Avalonia.Controls.Generators
/// the gap.
/// </summary>
/// <param name="startingIndex">
/// The index of the first item of the data in the containing collection.
/// The index of the first item in the control's items.
/// </param>
/// <param name="count">The the number of items to remove.</param>
/// <returns>The removed containers.</returns>
IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count);
bool TryRecycle(
int oldIndex,
int newIndex,
object item,
IMemberSelector selector);
/// <summary>
/// Clears all created containers and returns the removed controls.
/// </summary>

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

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
namespace Avalonia.Controls.Generators
{
@ -102,6 +103,16 @@ namespace Avalonia.Controls.Generators
return result;
}
/// <inheritdoc/>
public virtual bool TryRecycle(
int oldIndex,
int newIndex,
object item,
IMemberSelector selector)
{
return false;
}
/// <inheritdoc/>
public virtual IEnumerable<ItemContainerInfo> Clear()
{
@ -189,6 +200,21 @@ namespace Avalonia.Controls.Generators
}
}
/// <summary>
/// Moves a container.
/// </summary>
/// <param name="oldIndex">The old index.</param>
/// <param name="newIndex">The new index.</param>
/// <param name="item">The new item.</param>
/// <returns>The container info.</returns>
protected void MoveContainer(int oldIndex, int newIndex, object item)
{
var container = _containers[oldIndex];
var newContainer = new ItemContainerInfo(container.ContainerControl, item, newIndex);
_containers[oldIndex] = null;
AddContainer(newContainer);
}
/// <summary>
/// Gets all containers with an index that fall within a range.
/// </summary>

22
src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs

@ -75,5 +75,27 @@ namespace Avalonia.Controls.Generators
return result;
}
}
/// <inheritdoc/>
public override bool TryRecycle(
int oldIndex,
int newIndex,
object item,
IMemberSelector selector)
{
var container = ContainerFromIndex(oldIndex);
var i = selector != null ? selector.Select(item) : item;
container.SetValue(ContentProperty, i);
if (!(item is IControl))
{
container.DataContext = i;
}
MoveContainer(oldIndex, newIndex, i);
return true;
}
}
}

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

@ -118,6 +118,11 @@ namespace Avalonia.Controls.Generators
return base.RemoveRange(startingIndex, count);
}
public override bool TryRecycle(int oldIndex, int newIndex, object item, IMemberSelector selector)
{
return false;
}
/// <summary>
/// Gets the data template for the specified item.
/// </summary>

79
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -53,50 +53,10 @@ namespace Avalonia.Controls.Presenters
var scroll = (VirtualizingPanel.ScrollDirection == Orientation.Vertical) ?
value.Y : value.X;
var delta = (int)(scroll - FirstIndex);
var panel = VirtualizingPanel;
if (delta != 0)
{
if (delta >= panel.Children.Count)
{
var index = FirstIndex + delta;
foreach (var container in panel.Children)
{
container.DataContext = Items.ElementAt(index++);
}
}
else if (delta > 0)
{
var containers = panel.Children.GetRange(0, delta).ToList();
panel.Children.RemoveRange(0, delta);
var index = LastIndex + 1;
foreach (var container in containers)
{
container.DataContext = Items.ElementAt(index++);
}
panel.Children.AddRange(containers);
}
else
{
var first = panel.Children.Count + delta;
var count = -delta;
var containers = panel.Children.GetRange(first, count).ToList();
panel.Children.RemoveRange(first, count);
var index = FirstIndex + delta;
foreach (var container in containers)
{
container.DataContext = Items.ElementAt(index++);
}
panel.Children.InsertRange(0, containers);
}
RecycleContainers(delta);
FirstIndex += delta;
LastIndex += delta;
}
@ -167,5 +127,42 @@ namespace Avalonia.Controls.Presenters
LastIndex -= count;
}
}
private void RecycleContainers(int delta)
{
var panel = VirtualizingPanel;
var generator = Owner.ItemContainerGenerator;
var selector = Owner.MemberSelector;
var sign = delta < 0 ? -1 : 1;
var first = delta < 0 ? panel.Children.Count + delta : 0;
var count = Math.Abs(delta);
var containers = panel.Children.GetRange(first, count).ToList();
for (var i = 0; i < containers.Count; ++i)
{
var oldItemIndex = FirstIndex + first + i;
var newItemIndex = oldItemIndex + delta + ((panel.Children.Count - count) * sign);
var item = Items.ElementAt(newItemIndex);
if (!generator.TryRecycle(oldItemIndex, newItemIndex, item, selector))
{
throw new NotImplementedException();
}
}
if (delta < panel.Children.Count)
{
panel.Children.RemoveRange(first, count);
if (delta > 0)
{
panel.Children.AddRange(containers);
}
else
{
panel.Children.InsertRange(0, containers);
}
}
}
}
}

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

@ -101,14 +101,7 @@ namespace Avalonia.Controls.Presenters
{
if (_generator == null)
{
var i = TemplatedParent as ItemsControl;
_generator = i?.ItemContainerGenerator;
if (_generator == null)
{
_generator = new ItemContainerGenerator(this);
_generator.ItemTemplate = ItemTemplate;
}
_generator = CreateItemContainerGenerator();
}
return _generator;
@ -170,6 +163,26 @@ namespace Avalonia.Controls.Presenters
}
}
/// <summary>
/// Creates the <see cref="ItemContainerGenerator"/> for the control.
/// </summary>
/// <returns>
/// An <see cref="IItemContainerGenerator"/> or null.
/// </returns>
protected virtual IItemContainerGenerator CreateItemContainerGenerator()
{
var i = TemplatedParent as ItemsControl;
var result = i?.ItemContainerGenerator;
if (result == null)
{
result = new ItemContainerGenerator(this);
result.ItemTemplate = ItemTemplate;
}
return result;
}
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{

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

@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -205,7 +206,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(new Vector(0, 5), ((IScrollable)target).Offset);
Assert.Equal(scrolledContainers, target.Panel.Children);
for (var i = 0; i < target.Panel.Children.Count; ++i)
{
Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext);
@ -215,6 +216,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(new Vector(0, 0), ((IScrollable)target).Offset);
Assert.Equal(containers, target.Panel.Children);
var dcs = target.Panel.Children.Select(x => x.DataContext).ToList();
for (var i = 0; i < target.Panel.Children.Count; ++i)
{
Assert.Equal(items[i], target.Panel.Children[i].DataContext);
@ -249,6 +252,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
private static ItemsPresenter CreateTarget(
ItemVirtualizationMode mode = ItemVirtualizationMode.Simple,
Orientation orientation = Orientation.Vertical,
bool useContainers = true,
int itemCount = 20)
{
ItemsPresenter result;
@ -256,7 +260,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = new ScrollContentPresenter
{
Content = result = new ItemsPresenter
Content = result = new TestItemsPresenter(useContainers)
{
Items = items,
ItemsPanel = VirtualizingPanelTemplate(orientation),
@ -287,5 +291,31 @@ namespace Avalonia.Controls.UnitTests.Presenters
Orientation = orientation,
});
}
private class TestItemsPresenter : ItemsPresenter
{
private bool _useContainers;
public TestItemsPresenter(bool useContainers)
{
_useContainers = useContainers;
}
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return _useContainers ?
new ItemContainerGenerator<TestContainer>(this, TestContainer.ContentProperty, null) :
new ItemContainerGenerator(this);
}
}
private class TestContainer : ContentControl
{
public TestContainer()
{
Width = 10;
Height = 10;
}
}
}
}

Loading…
Cancel
Save