From 3791fa8a51ed75a8b170d776d459b061e9352909 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Thu, 23 Feb 2023 23:44:33 +0100 Subject: [PATCH] Don't add ItemsControl items to its panel logical children --- src/Avalonia.Controls/Panel.cs | 50 +++++++++++++++---- .../Presenters/ItemsPresenter.cs | 1 + .../ItemsControlTests.cs | 15 +++++- .../Avalonia.Controls.UnitTests/PanelTests.cs | 15 ++++++ .../TabControlTests.cs | 2 +- .../TreeViewTests.cs | 2 +- 6 files changed, 71 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index fa18ee468c..035e775af7 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/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 BackgroundProperty = Border.BackgroundProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly DirectProperty IsItemsHostProperty = + AvaloniaProperty.RegisterDirect( + nameof(IsItemsHost), + o => o.IsItemsHost, + (o, v) => o.IsItemsHost = v, + unsetValue: false); + /// /// Initializes static members of the class. /// @@ -34,6 +43,7 @@ namespace Avalonia.Controls AffectsRender(BackgroundProperty); } + private bool _isItemsHost; private EventHandler? _childIndexChanged; /// @@ -59,6 +69,15 @@ namespace Avalonia.Controls set { SetValue(BackgroundProperty, value); } } + /// + /// Gets whether the hosts the items created by an . + /// + public bool IsItemsHost + { + get => _isItemsHost; + set => SetAndRaise(IsItemsHostProperty, ref _isItemsHost, value); + } + event EventHandler? IChildIndexProvider.ChildIndexChanged { add @@ -129,24 +148,29 @@ namespace Avalonia.Controls /// The event args. protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e) { - List controls; - switch (e.Action) { case NotifyCollectionChangedAction.Add: - controls = e.NewItems!.OfType().ToList(); - LogicalChildren.InsertRange(e.NewStartingIndex, controls); + if (!IsItemsHost) + { + LogicalChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType().ToList()); + } VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType()); 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().ToList(); - LogicalChildren.RemoveAll(controls); + if (!IsItemsHost) + { + LogicalChildren.RemoveAll(e.OldItems!.OfType().ToList()); + } VisualChildren.RemoveAll(e.OldItems!.OfType()); 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; } + /// public bool TryGetTotalCount(out int count) { count = Children.Count; diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index e8eaac7d17..0a762c438d 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/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); diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index f074972cef..3aaf62f0bf 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/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)); } diff --git a/tests/Avalonia.Controls.UnitTests/PanelTests.cs b/tests/Avalonia.Controls.UnitTests/PanelTests.cs index a31f0dd4c2..82f133d533 100644 --- a/tests/Avalonia.Controls.UnitTests/PanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/PanelTests.cs @@ -141,5 +141,20 @@ namespace Avalonia.Controls.UnitTests var panel = new Panel(); Assert.Throws(() => 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); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 4c5397db60..0f72b2101a 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/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() diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 1a9fb33b7e..2ca716fa8f 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/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)); }