From d7b6a8e97a6d838f20d418b25e6eff429383d3d0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Feb 2023 18:08:32 +0100 Subject: [PATCH 1/2] Added failing test for #10398. --- .../TreeViewTests.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 1a9fb33b7e..2ca3495d30 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -100,6 +100,31 @@ namespace Avalonia.Controls.UnitTests Assert.All(items, x => Assert.Same(theme, x.ItemContainerTheme)); } + [Fact] + public void Finds_Correct_DataTemplate_When_Application_DataTemplate_Is_Present() + { + // #10398 + using var app = UnitTestApplication.Start(); + + Avalonia.Application.Current.DataTemplates.Add(new FuncDataTemplate((x, _) => new Canvas())); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(Avalonia.Application.Current); + + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = CreateTestTreeData(), + }; + + var root = new TestRoot(target); + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); + Assert.Equal(new[] { "Child1", "Child2", "Child3" }, ExtractItemHeader(target, 1)); + Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2)); + } + [Fact] public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers() { From 5281bbcca097bbe4a8ce7d58264d02efc4df3292 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 28 Feb 2023 16:39:41 +0100 Subject: [PATCH 2/2] Search for data template on tree attachment. When a `HeaderedItemsControl` is used in an `ItemsControl` it needs to search for an `ITreeDataTemplate` in order to populate the `Items` property. This can't be done properly until it's attached to the logical tree. Fixes #10398 --- src/Avalonia.Controls/ItemsControl.cs | 9 +--- .../Primitives/HeaderedItemsControl.cs | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 9483f98881..bebf4a38f6 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -383,14 +383,7 @@ namespace Avalonia.Controls { hic.Header = item; hic.HeaderTemplate = itemTemplate; - - itemTemplate ??= hic.FindDataTemplate(item) ?? this.FindDataTemplate(item); - - if (itemTemplate is ITreeDataTemplate treeTemplate) - { - if (item is not null && treeTemplate.ItemsSelector(item) is { } itemsBinding) - BindingOperations.Apply(hic, ItemsProperty, itemsBinding, null); - } + hic.PrepareItemContainer(); } } diff --git a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs index 71ae7a5bf6..55d2ec7506 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs @@ -1,6 +1,8 @@ +using System; using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.LogicalTree; namespace Avalonia.Controls.Primitives @@ -10,6 +12,9 @@ namespace Avalonia.Controls.Primitives /// public class HeaderedItemsControl : ItemsControl, IContentPresenterHost { + private IDisposable? _itemsBinding; + private bool _prepareItemContainerOnAttach; + /// /// Defines the property. /// @@ -60,6 +65,17 @@ namespace Avalonia.Controls.Primitives /// IAvaloniaList IContentPresenterHost.LogicalChildren => LogicalChildren; + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + base.OnAttachedToLogicalTree(e); + + if (_prepareItemContainerOnAttach) + { + PrepareItemContainer(); + _prepareItemContainerOnAttach = false; + } + } + /// bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter) { @@ -81,6 +97,37 @@ namespace Avalonia.Controls.Primitives return false; } + internal void PrepareItemContainer() + { + _itemsBinding?.Dispose(); + _itemsBinding = null; + + var item = Header; + + if (item is null) + { + _prepareItemContainerOnAttach = false; + return; + } + + var headerTemplate = HeaderTemplate; + + if (headerTemplate is null) + { + if (((ILogical)this).IsAttachedToLogicalTree) + headerTemplate = this.FindDataTemplate(item); + else + _prepareItemContainerOnAttach = true; + } + + if (headerTemplate is ITreeDataTemplate treeTemplate && + treeTemplate.Match(item) && + treeTemplate.ItemsSelector(item) is { } itemsBinding) + { + _itemsBinding = BindingOperations.Apply(this, ItemsProperty, itemsBinding, null); + } + } + private void HeaderChanged(AvaloniaPropertyChangedEventArgs e) { if (e.OldValue is ILogical oldChild)