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

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

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
namespace Avalonia.Controls.Generators namespace Avalonia.Controls.Generators
{ {
@ -102,6 +103,16 @@ namespace Avalonia.Controls.Generators
return result; return result;
} }
/// <inheritdoc/>
public virtual bool TryRecycle(
int oldIndex,
int newIndex,
object item,
IMemberSelector selector)
{
return false;
}
/// <inheritdoc/> /// <inheritdoc/>
public virtual IEnumerable<ItemContainerInfo> Clear() 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> /// <summary>
/// Gets all containers with an index that fall within a range. /// Gets all containers with an index that fall within a range.
/// </summary> /// </summary>

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

@ -75,5 +75,27 @@ namespace Avalonia.Controls.Generators
return result; 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); return base.RemoveRange(startingIndex, count);
} }
public override bool TryRecycle(int oldIndex, int newIndex, object item, IMemberSelector selector)
{
return false;
}
/// <summary> /// <summary>
/// Gets the data template for the specified item. /// Gets the data template for the specified item.
/// </summary> /// </summary>

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

@ -53,50 +53,10 @@ namespace Avalonia.Controls.Presenters
var scroll = (VirtualizingPanel.ScrollDirection == Orientation.Vertical) ? var scroll = (VirtualizingPanel.ScrollDirection == Orientation.Vertical) ?
value.Y : value.X; value.Y : value.X;
var delta = (int)(scroll - FirstIndex); var delta = (int)(scroll - FirstIndex);
var panel = VirtualizingPanel;
if (delta != 0) if (delta != 0)
{ {
if (delta >= panel.Children.Count) RecycleContainers(delta);
{
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);
}
FirstIndex += delta; FirstIndex += delta;
LastIndex += delta; LastIndex += delta;
} }
@ -167,5 +127,42 @@ namespace Avalonia.Controls.Presenters
LastIndex -= count; 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) if (_generator == null)
{ {
var i = TemplatedParent as ItemsControl; _generator = CreateItemContainerGenerator();
_generator = i?.ItemContainerGenerator;
if (_generator == null)
{
_generator = new ItemContainerGenerator(this);
_generator.ItemTemplate = ItemTemplate;
}
} }
return _generator; 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/> /// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) 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.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
@ -205,7 +206,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(new Vector(0, 5), ((IScrollable)target).Offset); Assert.Equal(new Vector(0, 5), ((IScrollable)target).Offset);
Assert.Equal(scrolledContainers, target.Panel.Children); Assert.Equal(scrolledContainers, target.Panel.Children);
for (var i = 0; i < target.Panel.Children.Count; ++i) for (var i = 0; i < target.Panel.Children.Count; ++i)
{ {
Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext); 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(new Vector(0, 0), ((IScrollable)target).Offset);
Assert.Equal(containers, target.Panel.Children); 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) for (var i = 0; i < target.Panel.Children.Count; ++i)
{ {
Assert.Equal(items[i], target.Panel.Children[i].DataContext); Assert.Equal(items[i], target.Panel.Children[i].DataContext);
@ -249,6 +252,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
private static ItemsPresenter CreateTarget( private static ItemsPresenter CreateTarget(
ItemVirtualizationMode mode = ItemVirtualizationMode.Simple, ItemVirtualizationMode mode = ItemVirtualizationMode.Simple,
Orientation orientation = Orientation.Vertical, Orientation orientation = Orientation.Vertical,
bool useContainers = true,
int itemCount = 20) int itemCount = 20)
{ {
ItemsPresenter result; ItemsPresenter result;
@ -256,7 +260,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = new ScrollContentPresenter var scroller = new ScrollContentPresenter
{ {
Content = result = new ItemsPresenter Content = result = new TestItemsPresenter(useContainers)
{ {
Items = items, Items = items,
ItemsPanel = VirtualizingPanelTemplate(orientation), ItemsPanel = VirtualizingPanelTemplate(orientation),
@ -287,5 +291,31 @@ namespace Avalonia.Controls.UnitTests.Presenters
Orientation = orientation, 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