Browse Source

WIP: INCC virtualization support.

Steven Kirk 9 years ago
parent
commit
3e8a8c6d7c

+ 4 - 0
samples/VirtualizationTest/MainWindow.xaml

@@ -17,6 +17,10 @@
             <TextBox Watermark="Viewport"
                      UseFloatingWatermark="True"
                      Text="{Binding #listBox.Scroll.Viewport, Mode=OneWay}"/>
+            <TextBox Watermark="Item to Create"
+                     UseFloatingWatermark="True"
+                     Text="{Binding NewItemString}"/>
+            <Button Command="{Binding NewItemCommand}">Insert Item</Button>
             <Button Command="{Binding RecreateCommand}">Recreate</Button>
         </StackPanel>
 

+ 46 - 10
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;

+ 30 - 1
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<string>)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<string>(
+                Enumerable.Range(0, itemCount).Select(x => $"Item {x}"));
 
             var scroller = new ScrollContentPresenter
             {