From 1adcac74a1f27abf5d8c129fbaf27d5173b4000f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 7 May 2025 22:38:05 +0100 Subject: [PATCH 1/4] add failing unit test. --- .../TreeDataGridRowsPresenterTests.cs | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tests/Avalonia.Controls.TreeDataGrid.Tests/Primitives/TreeDataGridRowsPresenterTests.cs b/tests/Avalonia.Controls.TreeDataGrid.Tests/Primitives/TreeDataGridRowsPresenterTests.cs index d3822bd7..dbf18057 100644 --- a/tests/Avalonia.Controls.TreeDataGrid.Tests/Primitives/TreeDataGridRowsPresenterTests.cs +++ b/tests/Avalonia.Controls.TreeDataGrid.Tests/Primitives/TreeDataGridRowsPresenterTests.cs @@ -3,7 +3,10 @@ using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Models.TreeDataGrid; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Headless.XUnit; using Avalonia.LogicalTree; using Avalonia.Media; @@ -423,6 +426,53 @@ public void Handles_Moving_Focused_Row_While_Outside_Viewport() // The correct element should be shown. Assert.Same(items[0], target.RealizedElements.ElementAt(0)!.DataContext); } + + [AvaloniaFact(Timeout = 10000)] + public void Handles_Adding_Rows_While_Detached_From_VisualTree() + { + var (target, scroll, items) = CreateTarget(itemCount: 5); + var testWindow = scroll.Parent as TestWindow; + + if (testWindow != null) + { + testWindow.Content = null; + testWindow.UpdateLayout(); + } + + var tabItem = new TabItem { Content = scroll }; + var tabControl = new TabControl { Items = { tabItem, new TabItem() }, Template = TabControlTemplate() }; + + ApplyTemplate(tabControl); + + if (testWindow != null) + { + testWindow.Content = tabControl; + tabControl.ApplyTemplate(); + testWindow.UpdateLayout(); + } + + Dispatcher.UIThread.RunJobs(); + + tabControl.SelectedIndex = 1; + + Layout(target); + + Enumerable.Range(5, 5).ToList().ForEach(x => items.Insert(1, new Model { Id = x, Title = "Item " + x })); + + tabControl.SelectedIndex = 0; + Layout(target); + + var indexes = GetRealizedRowIndexes(target); + var models = target!.RealizedElements + .Cast().Select(x => x?.Model) + .Cast().ToList(); + + var distinctModelCount = models.DistinctBy(x => x.Id).Count(); + + Assert.Equal(10, indexes.Count); + Assert.Equal(10, models.Count); + Assert.Equal(10, distinctModelCount); + } [AvaloniaFact(Timeout = 10000)] public void Updates_Star_Column_ActualWidth() @@ -615,6 +665,55 @@ private static void Layout(TreeDataGridRowsPresenter target) { target.UpdateLayout(); } + + private static void ApplyTemplate(TabControl target) + { + target.ApplyTemplate(); + + target.Presenter?.ApplyTemplate(); + + foreach (var tabItem in target.GetLogicalChildren().OfType()) + { + tabItem.Template = TabItemTemplate(); + + tabItem.ApplyTemplate(); + + tabItem.Presenter?.UpdateChild(); + } + } + + private static IControlTemplate TabItemTemplate() + { + return new FuncControlTemplate((parent, scope) => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [~ContentPresenter.ContentProperty] = new TemplateBinding(TabItem.HeaderProperty), + [~ContentPresenter.ContentTemplateProperty] = new TemplateBinding(TabItem.HeaderTemplateProperty), + RecognizesAccessKey = true, + }.RegisterInNameScope(scope)); + } + + private static IControlTemplate TabControlTemplate() + { + return new FuncControlTemplate((parent, scope) => + new StackPanel + { + Children = + { + new ItemsPresenter + { + Name = "PART_ItemsPresenter", + }.RegisterInNameScope(scope), + new ContentPresenter + { + Name = "PART_SelectedContentHost", + [~ContentPresenter.ContentProperty] = new TemplateBinding(TabControl.SelectedContentProperty), + [~ContentPresenter.ContentTemplateProperty] = new TemplateBinding(TabControl.SelectedContentTemplateProperty), + }.RegisterInNameScope(scope) + } + }); + } private class Model { From 9bc4d94898faaf92e4db30ab6ef51b400d9ec439 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 7 May 2025 22:44:46 +0100 Subject: [PATCH 2/4] ensure elements are recycled when TdgPresenters get detached from the visual tree. --- .../Primitives/TreeDataGridPresenterBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs index b95dd152..28aaf1a1 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs @@ -429,6 +429,7 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e EffectiveViewportChanged -= OnEffectiveViewportChanged; UnsubscribeFromItemChanges(); + RecycleAllElements(); } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) From 247aaca2aba9d6dd023ff2f5755e116b941bf464 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Aug 2025 20:50:14 +0200 Subject: [PATCH 3/4] Try to fix nuke build. Message from nuke was: ``` [WRN] : Solution /home/vsts/work/1/s/Avalonia.Controls.TreeDataGrid.sln has active build configurations for the build project. Either enable SuppressBuildProjectCheck on Build.Solution or remove the following entries from the solution file: - {D45C7B46-A12C-4412-8397-B51B75A09999}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D45C7B46-A12C-4412-8397-B51B75A09999}.Release|Any CPU.Build.0 = Release|Any CPU ``` Not sure why this has started happening now, but doing what it says. --- Avalonia.Controls.TreeDataGrid.sln | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Avalonia.Controls.TreeDataGrid.sln b/Avalonia.Controls.TreeDataGrid.sln index 24d5d5c6..fd8002c3 100644 --- a/Avalonia.Controls.TreeDataGrid.sln +++ b/Avalonia.Controls.TreeDataGrid.sln @@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AE911386 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_build", "nukebuild\_build.csproj", "{D45C7B46-A12C-4412-8397-B51B75A09999}" EndProject +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{0F5B5686-C914-416E-ABA3-ED41A653626C}" ProjectSection(SolutionItems) = preProject build\SharedVersion.props = build\SharedVersion.props @@ -45,9 +46,7 @@ Global {9A36E37E-2C03-4B5A-B7EE-A91DC95C3E4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {9A36E37E-2C03-4B5A-B7EE-A91DC95C3E4A}.Release|Any CPU.Build.0 = Release|Any CPU {D45C7B46-A12C-4412-8397-B51B75A09999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D45C7B46-A12C-4412-8397-B51B75A09999}.Debug|Any CPU.Build.0 = Debug|Any CPU {D45C7B46-A12C-4412-8397-B51B75A09999}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D45C7B46-A12C-4412-8397-B51B75A09999}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From e997fde2210ba25bd2f6a70a71d6f2c1411d580f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Aug 2025 21:20:04 +0200 Subject: [PATCH 4/4] Remove duplicate EndProject. --- Avalonia.Controls.TreeDataGrid.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/Avalonia.Controls.TreeDataGrid.sln b/Avalonia.Controls.TreeDataGrid.sln index fd8002c3..92277cf0 100644 --- a/Avalonia.Controls.TreeDataGrid.sln +++ b/Avalonia.Controls.TreeDataGrid.sln @@ -20,7 +20,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AE911386 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_build", "nukebuild\_build.csproj", "{D45C7B46-A12C-4412-8397-B51B75A09999}" EndProject -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{0F5B5686-C914-416E-ABA3-ED41A653626C}" ProjectSection(SolutionItems) = preProject build\SharedVersion.props = build\SharedVersion.props