Browse Source

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.
Steven Kirk 4 năm trước cách đây
mục cha
commit
1837548b3b

+ 52 - 3
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<Dock>(
+                tabItem,
+                TabControl.TabStripPlacementProperty));
 
             if (tabItem.HeaderTemplate == null)
             {
-                tabItem[~HeaderedContentControl.HeaderTemplateProperty] = Owner[~ItemsControl.ItemTemplateProperty];
+                tabItem.Bind(TabItem.HeaderTemplateProperty, new OwnerBinding<IDataTemplate>(
+                    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<IDataTemplate>(
+                    tabItem,
+                    TabControl.ContentTemplateProperty));
             }
 
             return tabItem;
         }
+
+        private class OwnerBinding<T> : SingleSubscriberObservableBase<T>
+        {
+            private readonly TabItem _item;
+            private readonly StyledProperty<T> _ownerProperty;
+            private IDisposable _ownerSubscription;
+            private IDisposable _propertySubscription;
+
+            public OwnerBinding(TabItem item, StyledProperty<T> 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));
+                }
+            }
+        }
     }
 }

+ 1 - 0
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();