From 1837548b3b595207c3ae814a7a50daa92089e2fd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 Feb 2021 17:08:13 +0100 Subject: [PATCH] Fix TabItems leaking. When a `TabItem` was created a binding was being set up to the owner `TabControl` but that binding was never being freed. Ideally we'd be setting these properties in XAML rather than hardcoding them in the generator but that would be a breaking change for everyone who re-templated `TabControl`. As a second-best option what we'd do is set up a `$parent` binding in the generator but this isn't available in Avalonia.Controls so had to implement a quick observable which watches for a parent `TabControl` and subscribes to it. --- .../Generators/TabItemContainerGenerator.cs | 55 ++++++++++++++++++- .../TabControlTests.cs | 1 + 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs index d2e05ee136..840a1f66f1 100644 --- a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs @@ -1,4 +1,10 @@ +using System; +using System.Collections.Generic; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.LogicalTree; +using Avalonia.Reactive; +using Avalonia.VisualTree; namespace Avalonia.Controls.Generators { @@ -16,11 +22,15 @@ namespace Avalonia.Controls.Generators { var tabItem = (TabItem)base.CreateContainer(item); - tabItem[~TabControl.TabStripPlacementProperty] = Owner[~TabControl.TabStripPlacementProperty]; + tabItem.Bind(TabItem.TabStripPlacementProperty, new OwnerBinding( + tabItem, + TabControl.TabStripPlacementProperty)); if (tabItem.HeaderTemplate == null) { - tabItem[~HeaderedContentControl.HeaderTemplateProperty] = Owner[~ItemsControl.ItemTemplateProperty]; + tabItem.Bind(TabItem.HeaderTemplateProperty, new OwnerBinding( + tabItem, + TabControl.ItemTemplateProperty)); } if (tabItem.Header == null) @@ -40,10 +50,49 @@ namespace Avalonia.Controls.Generators if (!(tabItem.Content is IControl)) { - tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty]; + tabItem.Bind(TabItem.ContentTemplateProperty, new OwnerBinding( + tabItem, + TabControl.ContentTemplateProperty)); } return tabItem; } + + private class OwnerBinding : SingleSubscriberObservableBase + { + private readonly TabItem _item; + private readonly StyledProperty _ownerProperty; + private IDisposable _ownerSubscription; + private IDisposable _propertySubscription; + + public OwnerBinding(TabItem item, StyledProperty ownerProperty) + { + _item = item; + _ownerProperty = ownerProperty; + } + + protected override void Subscribed() + { + _ownerSubscription = ControlLocator.Track(_item, 0, typeof(TabControl)).Subscribe(OwnerChanged); + } + + protected override void Unsubscribed() + { + _ownerSubscription?.Dispose(); + _ownerSubscription = null; + } + + private void OwnerChanged(ILogical c) + { + _propertySubscription?.Dispose(); + _propertySubscription = null; + + if (c is TabControl tabControl) + { + _propertySubscription = tabControl.GetObservable(_ownerProperty) + .Subscribe(x => PublishNext(x)); + } + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index e6f7ac601f..c54d7efe61 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -374,6 +374,7 @@ namespace Avalonia.Controls.UnitTests new TextBlock { Tag = "bar", Text = x }), Items = new[] { "Foo" }, }; + var root = new TestRoot(target); ApplyTemplate(target); ((ContentPresenter)target.ContentPart).UpdateChild();