Browse Source

ItemsControl+ItemVirtualizerSimple did not recreated item containers when Items or ItemTemplate were replaced

adospace 5 years ago
parent
commit
13cd835bc0

+ 1 - 1
global.json

@@ -1,6 +1,6 @@
 {
 	"sdk": {
-		"version": "3.1.401"
+		"version": "3.1.301"
 	},
     "msbuild-sdks": {
         "Microsoft.Build.Traversal": "1.0.43",

+ 5 - 1
src/Avalonia.Controls/ItemsControl.cs

@@ -449,7 +449,11 @@ namespace Avalonia.Controls
             if (_itemContainerGenerator != null)
             {
                 _itemContainerGenerator.ItemTemplate = (IDataTemplate)e.NewValue;
-                // TODO: Rebuild the item containers.
+
+                if (e.OldValue != null && Presenter != null)
+                {
+                    Presenter.ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+                }
             }
         }
 

+ 4 - 0
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@@ -200,6 +200,10 @@ namespace Avalonia.Controls.Presenters
                         break;
 
                     case NotifyCollectionChangedAction.Reset:
+                        Owner.ItemContainerGenerator.Clear();
+                        VirtualizingPanel.Children.Clear();
+                        FirstIndex = NextIndex = 0;
+
                         RecycleContainersOnRemove();
                         CreateAndRemoveContainers();
                         panel.ForceInvalidateMeasure();

+ 3 - 1
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@@ -57,6 +57,8 @@ namespace Avalonia.Controls.Presenters
 
             set
             {
+                var itemsReplaced = (_items != value);
+
                 _itemsSubscription?.Dispose();
                 _itemsSubscription = null;
 
@@ -67,7 +69,7 @@ namespace Avalonia.Controls.Presenters
 
                 SetAndRaise(ItemsProperty, ref _items, value);
 
-                if (_createdPanel)
+                if (_createdPanel && itemsReplaced)
                 {
                     ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                 }

+ 92 - 0
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@@ -596,6 +596,98 @@ namespace Avalonia.Controls.UnitTests
             root.Child = target;
         }
 
+        [Fact]
+        public void Presenter_Items_Should_Be_In_Sync_When_Replacing_Items()
+        {
+            var target = new ItemsControl
+            {
+                Template = GetTemplate(),
+                Items = new[]
+                {
+                    new Item("Item1")
+                }
+            };
+
+            var root = new TestRoot { Child = target };
+            var otherPanel = new StackPanel();
+
+            target.ApplyTemplate();
+            target.Presenter.ApplyTemplate();
+
+            int dematerializedEventCallCount = 0;
+            target.ItemContainerGenerator.Dematerialized += (s, e) =>
+            {
+                Assert.IsType<Item>(e.Containers[0].Item);
+                Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
+                dematerializedEventCallCount++;
+            };
+
+            int materializedEventCallCount = 0;
+            target.ItemContainerGenerator.Materialized += (s, e) =>
+            {
+                Assert.IsType<Item>(e.Containers[0].Item);
+                Assert.Equal("Item2", ((Item)e.Containers[0].Item).Value);
+                materializedEventCallCount++;
+            };
+
+            target.Items = new[]
+            {
+                new Item("Item2")
+            };
+
+            //Ensure that events are called one time only
+            Assert.Equal(1, dematerializedEventCallCount);
+            Assert.Equal(1, materializedEventCallCount);
+        }
+
+        [Fact]
+        public void Presenter_Items_Should_Be_In_Sync_When_Replacing_ItemTemplate()
+        {
+            var target = new ItemsControl
+            {
+                Template = GetTemplate(),
+                Items = new[]
+                {
+                    new Item("Item1")
+                },
+                ItemTemplate = new FuncDataTemplate<Item>((x, ns) => new TextBlock())
+            };
+
+            var root = new TestRoot { Child = target };
+            var otherPanel = new StackPanel();
+
+            target.ApplyTemplate();
+            target.Presenter.ApplyTemplate();
+
+            int dematerializedEventCallCount = 0;
+            target.ItemContainerGenerator.Dematerialized += (s, e) =>
+            {
+                Assert.IsType<Item>(e.Containers[0].Item);
+                Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
+                var contentPresenter = ((ContentPresenter)e.Containers[0].ContainerControl);
+                contentPresenter.UpdateChild();
+                Assert.IsType<TextBlock>(contentPresenter.Child);
+                dematerializedEventCallCount++;
+            };
+
+            int materializedEventCallCount = 0;
+            target.ItemContainerGenerator.Materialized += (s, e) =>
+            {
+                Assert.IsType<Item>(e.Containers[0].Item);
+                Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
+                var contentPresenter = ((ContentPresenter)e.Containers[0].ContainerControl);
+                contentPresenter.UpdateChild();
+                Assert.IsType<Canvas>(contentPresenter.Child);
+                materializedEventCallCount++;
+            };
+
+            target.ItemTemplate =
+                new FuncDataTemplate<Item>((x, ns) => new Canvas());
+
+            Assert.Equal(1, dematerializedEventCallCount);
+            Assert.Equal(1, materializedEventCallCount);
+        }
+
         private class Item
         {
             public Item(string value)

+ 112 - 0
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@@ -454,6 +454,118 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
+
+        [Fact]
+        public void ListBox_Presenter_Items_Should_Be_In_Sync_When_Replacing_Items()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
+
+                var target = new ListBox()
+                {
+                    VerticalAlignment = Layout.VerticalAlignment.Top,
+                    AutoScrollToSelectedItem = true,
+                    Width = 50,
+                    VirtualizationMode = ItemVirtualizationMode.Simple,
+                    Items = new[]
+                    {
+                        new Item("Item1")
+                    },
+                };
+                wnd.Content = target;
+
+                var lm = wnd.LayoutManager;
+
+                lm.ExecuteInitialLayoutPass();
+
+                int dematerializedEventCallCount = 0;
+                target.ItemContainerGenerator.Dematerialized += (s, e) =>
+                {
+                    Assert.IsType<Item>(e.Containers[0].Item);
+                    Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
+                    dematerializedEventCallCount++;
+                };
+
+                int materializedEventCallCount = 0;
+                target.ItemContainerGenerator.Materialized += (s, e) =>
+                {
+                    Assert.IsType<Item>(e.Containers[0].Item);
+                    Assert.Equal("Item2", ((Item)e.Containers[0].Item).Value);
+                    materializedEventCallCount++;
+                };
+
+                target.Items = new[]
+                {
+                    new Item("Item2")
+                };
+
+                //assert that materialize/dematerialize events are called exactly one time
+                Assert.Equal(1, dematerializedEventCallCount);
+                Assert.Equal(1, materializedEventCallCount);
+            }
+        }
+
+        [Fact]
+        public void ListBox_Items_Should_Be_In_Sync_When_Replacing_ItemTemplate()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
+
+                var target = new ListBox()
+                {
+                    VerticalAlignment = Layout.VerticalAlignment.Top,
+                    AutoScrollToSelectedItem = true,
+                    Width = 50,
+                    VirtualizationMode = ItemVirtualizationMode.Simple,
+                    Items = new[]
+                    {
+                        new Item("Item1")
+                    },
+                    ItemTemplate =
+                        new FuncDataTemplate<Item>((x, ns) => new Canvas())
+                };
+
+                wnd.Content = target;
+
+                var lm = wnd.LayoutManager;
+
+                lm.ExecuteInitialLayoutPass();
+
+                int dematerializedEventCallCount = 0;
+                target.ItemContainerGenerator.Dematerialized += (s, e) =>
+                {
+                    Assert.IsType<Item>(e.Containers[0].Item);
+                    Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
+                    Assert.IsType<Canvas>(((ListBoxItem)e.Containers[0].ContainerControl).Presenter.Child);
+                    dematerializedEventCallCount++;
+                };
+
+                int materializedEventCallCount = 0;
+                ListBoxItem materializedListBoxItem = null;
+                target.ItemContainerGenerator.Materialized += (s, e) =>
+                {
+                    Assert.IsType<Item>(e.Containers[0].Item);
+                    Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
+                    materializedListBoxItem = ((ListBoxItem)e.Containers[0].ContainerControl);
+                    materializedEventCallCount++;
+                };
+
+                target.ItemTemplate =
+                    new FuncDataTemplate<Item>((x, ns) => new TextBlock());
+
+                //ensure events are called only one time
+                Assert.Equal(1, dematerializedEventCallCount);
+                Assert.Equal(1, materializedEventCallCount);
+
+                wnd.LayoutManager.ExecuteLayoutPass();
+
+                //ensure that new template has been applied
+                Assert.IsType<TextBlock>(materializedListBoxItem.Presenter.Child);
+            }
+        }
+
         private FuncControlTemplate ListBoxTemplate()
         {
             return new FuncControlTemplate<ListBox>((parent, scope) =>