diff --git a/samples/VirtualizationTest/MainWindow.xaml b/samples/VirtualizationTest/MainWindow.xaml
index b8fffe6688..a6fd6918ef 100644
--- a/samples/VirtualizationTest/MainWindow.xaml
+++ b/samples/VirtualizationTest/MainWindow.xaml
@@ -17,6 +17,10 @@
+
+
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index 3c972e922f..1ff85c50ac 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
@@ -47,7 +47,7 @@ namespace Avalonia.Controls.Presenters
if (delta != 0)
{
- RecycleContainers(delta);
+ RecycleMoveContainers(delta);
FirstIndex += delta;
NextIndex += delta;
}
@@ -56,7 +56,7 @@ namespace Avalonia.Controls.Presenters
{
// We're moving to a partially obscured item at the end of the list.
var firstIndex = ItemCount - panel.Children.Count;
- RecycleContainers(firstIndex - FirstIndex);
+ RecycleMoveContainers(firstIndex - FirstIndex);
NextIndex = ItemCount;
FirstIndex = NextIndex - panel.Children.Count;
panel.PixelOffset = VirtualizingPanel.PixelOverflow;
@@ -85,14 +85,26 @@ namespace Avalonia.Controls.Presenters
{
base.ItemsChanged(items, e);
- if (e.Action == NotifyCollectionChangedAction.Reset)
+ switch (e.Action)
{
- // We could recycle items here if this proves to be inefficient, but
- // Reset indicates a large change and should (?) be quite rare.
- VirtualizingPanel.Children.Clear();
- Owner.ItemContainerGenerator.Clear();
- FirstIndex = NextIndex = 0;
- CreateRemoveContainers();
+ case NotifyCollectionChangedAction.Add:
+ if (e.NewStartingIndex >= FirstIndex &&
+ e.NewStartingIndex + e.NewItems.Count < NextIndex)
+ {
+ CreateRemoveContainers();
+ RecycleContainers();
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ // We could recycle items here if this proves to be inefficient, but
+ // Reset indicates a large change and should (?) be quite rare.
+ VirtualizingPanel.Children.Clear();
+ Owner.ItemContainerGenerator.Clear();
+ FirstIndex = NextIndex = 0;
+ CreateRemoveContainers();
+ break;
}
((ILogicalScrollable)Owner).InvalidateScroll();
@@ -161,7 +173,31 @@ namespace Avalonia.Controls.Presenters
}
}
- private void RecycleContainers(int delta)
+ private void RecycleContainers()
+ {
+ var panel = VirtualizingPanel;
+ var generator = Owner.ItemContainerGenerator;
+ var selector = Owner.MemberSelector;
+ var containers = generator.Containers.ToList();
+ var itemIndex = FirstIndex;
+
+ foreach (var container in containers)
+ {
+ var item = Items.ElementAt(itemIndex);
+
+ if (!object.Equals(container.Item, item))
+ {
+ if (!generator.TryRecycle(itemIndex, itemIndex, item, selector))
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ ++itemIndex;
+ }
+ }
+
+ private void RecycleMoveContainers(int delta)
{
var panel = VirtualizingPanel;
var generator = Owner.ItemContainerGenerator;
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
index 013487f8fc..036ecdce6f 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
@@ -154,6 +155,32 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(5, ((IVirtualizingPanel)target.Panel).PixelOffset);
}
+ [Fact]
+ public void Inserting_Items_Should_Update_Containers()
+ {
+ var target = CreateTarget(itemCount: 20);
+
+ target.ApplyTemplate();
+ target.Measure(new Size(100, 95));
+ target.Arrange(new Rect(0, 0, 100, 95));
+
+ ((ILogicalScrollable)target).Offset = new Vector(0, 5);
+
+ var expected = Enumerable.Range(5, 10).Select(x => $"Item {x}").ToList();
+ var items = (ObservableCollection)target.Items;
+
+ Assert.Equal(
+ expected,
+ target.Panel.Children.Select(x => x.DataContext));
+
+ items.Insert(6, "Inserted");
+ expected.Insert(1, "Inserted");
+ expected.RemoveAt(expected.Count - 1);
+
+ var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
+ Assert.Equal(expected, actual);
+ }
+
public class WithContainers
{
[Fact]
@@ -237,7 +264,9 @@ namespace Avalonia.Controls.UnitTests.Presenters
int itemCount = 20)
{
ItemsPresenter result;
- var items = Enumerable.Range(0, itemCount).Select(x => $"Item {x}").ToList();
+
+ var items = new ObservableCollection(
+ Enumerable.Range(0, itemCount).Select(x => $"Item {x}"));
var scroller = new ScrollContentPresenter
{