diff --git a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
index 2e02bdc647..b5ac8aef6e 100644
--- a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
@@ -37,7 +37,7 @@ namespace Avalonia.Controls.Generators
/// Creates a container control for an item.
///
///
- /// The index of the item of the data in the containing collection.
+ /// The index of the item of data in the control's items.
///
/// The item.
/// An optional member selector.
@@ -51,7 +51,7 @@ namespace Avalonia.Controls.Generators
/// Removes a set of created containers.
///
///
- /// The index of the first item of the data in the containing collection.
+ /// The index of the first item in the control's items.
///
/// The the number of items to remove.
/// The removed containers.
@@ -69,12 +69,18 @@ namespace Avalonia.Controls.Generators
/// the gap.
///
///
- /// The index of the first item of the data in the containing collection.
+ /// The index of the first item in the control's items.
///
/// The the number of items to remove.
/// The removed containers.
IEnumerable RemoveRange(int startingIndex, int count);
+ bool TryRecycle(
+ int oldIndex,
+ int newIndex,
+ object item,
+ IMemberSelector selector);
+
///
/// Clears all created containers and returns the removed controls.
///
diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
index 3bf69d910a..801f237804 100644
--- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
+++ b/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;
}
+ ///
+ public virtual bool TryRecycle(
+ int oldIndex,
+ int newIndex,
+ object item,
+ IMemberSelector selector)
+ {
+ return false;
+ }
+
///
public virtual IEnumerable Clear()
{
@@ -189,6 +200,21 @@ namespace Avalonia.Controls.Generators
}
}
+ ///
+ /// Moves a container.
+ ///
+ /// The old index.
+ /// The new index.
+ /// The new item.
+ /// The container info.
+ 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);
+ }
+
///
/// Gets all containers with an index that fall within a range.
///
diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
index c4bc730e15..76922bfc55 100644
--- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
+++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
@@ -75,5 +75,27 @@ namespace Avalonia.Controls.Generators
return result;
}
}
+
+ ///
+ 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;
+ }
}
}
diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
index 68e410ae19..e26a4fb0d6 100644
--- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
+++ b/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;
+ }
+
///
/// Gets the data template for the specified item.
///
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index fa2b987a07..d2572e4088 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/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);
+ }
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
index 5fbed4d553..abebe85080 100644
--- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
+++ b/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
}
}
+ ///
+ /// Creates the for the control.
+ ///
+ ///
+ /// An or null.
+ ///
+ 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;
+ }
+
///
protected override Size MeasureOverride(Size availableSize)
{
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
index fe56cd95ac..3072770127 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
+++ b/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(this, TestContainer.ContentProperty, null) :
+ new ItemContainerGenerator(this);
+ }
+ }
+
+ private class TestContainer : ContentControl
+ {
+ public TestContainer()
+ {
+ Width = 10;
+ Height = 10;
+ }
+ }
}
}