1
0
Эх сурвалжийг харах

Don't add ItemsControl items to its panel logical children

Julien Lebosquain 2 жил өмнө
parent
commit
3791fa8a51

+ 39 - 11
src/Avalonia.Controls/Panel.cs

@@ -1,13 +1,12 @@
 using System;
-using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.Linq;
+using Avalonia.Controls.Presenters;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Metadata;
 using Avalonia.Reactive;
-using Avalonia.Styling;
 
 namespace Avalonia.Controls
 {
@@ -26,6 +25,16 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<IBrush?> BackgroundProperty =
             Border.BackgroundProperty.AddOwner<Panel>();
 
+        /// <summary>
+        /// Defines the <see cref="IsItemsHost"/> property.
+        /// </summary>
+        public static readonly DirectProperty<Panel, bool> IsItemsHostProperty =
+            AvaloniaProperty.RegisterDirect<Panel, bool>(
+                nameof(IsItemsHost),
+                o => o.IsItemsHost,
+                (o, v) => o.IsItemsHost = v,
+                unsetValue: false);
+
         /// <summary>
         /// Initializes static members of the <see cref="Panel"/> class.
         /// </summary>
@@ -34,6 +43,7 @@ namespace Avalonia.Controls
             AffectsRender<Panel>(BackgroundProperty);
         }
 
+        private bool _isItemsHost;
         private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
 
         /// <summary>
@@ -59,6 +69,15 @@ namespace Avalonia.Controls
             set { SetValue(BackgroundProperty, value); }
         }
 
+        /// <summary>
+        /// Gets whether the <see cref="Panel"/> hosts the items created by an <see cref="ItemsPresenter"/>.
+        /// </summary>
+        public bool IsItemsHost
+        {
+            get => _isItemsHost;
+            set => SetAndRaise(IsItemsHostProperty, ref _isItemsHost, value);
+        }
+
         event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
         {
             add
@@ -129,24 +148,29 @@ namespace Avalonia.Controls
         /// <param name="e">The event args.</param>
         protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
         {
-            List<Control> controls;
-
             switch (e.Action)
             {
                 case NotifyCollectionChangedAction.Add:
-                    controls = e.NewItems!.OfType<Control>().ToList();
-                    LogicalChildren.InsertRange(e.NewStartingIndex, controls);
+                    if (!IsItemsHost)
+                    {
+                        LogicalChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Control>().ToList());
+                    }
                     VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Visual>());
                     break;
 
                 case NotifyCollectionChangedAction.Move:
-                    LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
-                    VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+                    if (!IsItemsHost)
+                    {
+                        LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
+                    }
+                    VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
                     break;
 
                 case NotifyCollectionChangedAction.Remove:
-                    controls = e.OldItems!.OfType<Control>().ToList();
-                    LogicalChildren.RemoveAll(controls);
+                    if (!IsItemsHost)
+                    {
+                        LogicalChildren.RemoveAll(e.OldItems!.OfType<Control>().ToList());
+                    }
                     VisualChildren.RemoveAll(e.OldItems!.OfType<Visual>());
                     break;
 
@@ -155,7 +179,10 @@ namespace Avalonia.Controls
                     {
                         var index = i + e.OldStartingIndex;
                         var child = (Control)e.NewItems![i]!;
-                        LogicalChildren[index] = child;
+                        if (!IsItemsHost)
+                        {
+                            LogicalChildren[index] = child;
+                        }
                         VisualChildren[index] = child;
                     }
                     break;
@@ -200,6 +227,7 @@ namespace Avalonia.Controls
             return child is Control control ? Children.IndexOf(control) : -1;
         }
 
+        /// <inheritdoc />
         public bool TryGetTotalCount(out int count)
         {
             count = Children.Count;

+ 1 - 0
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@@ -167,6 +167,7 @@ namespace Avalonia.Controls.Presenters
 
                 Panel = ItemsPanel.Build();
                 Panel.SetValue(TemplatedParentProperty, TemplatedParent);
+                Panel.IsItemsHost = true;
                 _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
                 LogicalChildren.Add(Panel);
                 VisualChildren.Add(Panel);

+ 14 - 1
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@@ -73,6 +73,19 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(target, target.Presenter.Panel.TemplatedParent);
         }
 
+        [Fact]
+        public void Panel_Should_Have_ItemsHost_Set_To_True()
+        {
+            var target = new ItemsControl();
+
+            target.Template = GetTemplate();
+            target.Items = new[] { "Foo" };
+            target.ApplyTemplate();
+            target.Presenter!.ApplyTemplate();
+
+            Assert.True(target.Presenter.Panel!.IsItemsHost);
+        }
+
         [Fact]
         public void Container_Should_Have_TemplatedParent_Set_To_Null()
         {
@@ -634,7 +647,7 @@ namespace Avalonia.Controls.UnitTests
             target.ApplyTemplate();
             target.Presenter.ApplyTemplate();
 
-            var item = target.Presenter.Panel.LogicalChildren[0];
+            var item = target.LogicalChildren[0];
             Assert.Null(NameScope.GetNameScope((TextBlock)item));
         }
 

+ 15 - 0
tests/Avalonia.Controls.UnitTests/PanelTests.cs

@@ -141,5 +141,20 @@ namespace Avalonia.Controls.UnitTests
             var panel = new Panel();
             Assert.Throws<ArgumentNullException>(() => panel.Children.Add(null!));
         }
+
+        [Fact]
+        public void Adding_Control_To_Items_Host_Panel_Should_Not_Affect_Logical_Children()
+        {
+            var child = new Control();
+            var realParent = new ContentControl { Content = child };
+            var panel = new Panel { IsItemsHost = true };
+
+            panel.Children.Add(child);
+
+            Assert.Empty(panel.LogicalChildren);
+            Assert.Same(child.Parent, realParent);
+            Assert.Same(child.GetLogicalParent(), realParent);
+            Assert.Same(child.GetVisualParent(), panel);
+        }
     }
 }

+ 1 - 1
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@@ -327,7 +327,7 @@ namespace Avalonia.Controls.UnitTests
 
             ApplyTemplate(target);
 
-            var logicalChildren = target.ItemsPresenterPart.Panel.GetLogicalChildren();
+            var logicalChildren = target.GetLogicalChildren();
 
             var result = logicalChildren
                 .OfType<TabItem>()

+ 1 - 1
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@@ -1178,7 +1178,7 @@ namespace Avalonia.Controls.UnitTests
             target.ApplyTemplate();
             target.Presenter.ApplyTemplate();
 
-            var item = target.Presenter.Panel.LogicalChildren[0];
+            var item = target.LogicalChildren[0];
             Assert.Null(NameScope.GetNameScope((TreeViewItem)item));
         }