From 1ae6b929b3506b3162637f9deb03575956508fce Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 5 Mar 2016 14:06:07 +0100 Subject: [PATCH] Think I've fixed the leak... But I think it's showing up bugs elsewhere that need to be fixed. --- src/Perspex.Controls/Control.cs | 6 +++ .../Primitives/TemplatedControl.cs | 12 +++++ src/Perspex.Styling/LogicalTree/ILogical.cs | 10 ++++ .../ControlTests.cs | 18 ++++++++ .../Primitives/TemplatedControlTests.cs | 46 +++++++++++++++++++ .../SelectorTests_Child.cs | 5 ++ .../SelectorTests_Descendent.cs | 5 ++ .../SelectorTests_Template.cs | 26 +++++++++++ 8 files changed, 128 insertions(+) diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index 4c67051c7d..8ab69f671a 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -312,6 +312,12 @@ namespace Perspex.Controls /// IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent; + /// + void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + this.OnDetachedFromLogicalTree(e); + } + /// void ISupportInitialize.BeginInit() { diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index 10e312bb2b..336ba09b49 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/src/Perspex.Controls/Primitives/TemplatedControl.cs @@ -7,6 +7,7 @@ using System.Reactive.Linq; using Perspex.Controls.Templates; using Perspex.Data; using Perspex.Interactivity; +using Perspex.LogicalTree; using Perspex.Media; using Perspex.Styling; using Perspex.VisualTree; @@ -311,6 +312,17 @@ namespace Perspex.Controls.Primitives return this; } + /// + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + if (VisualChildren.Count > 0) + { + ((ILogical)VisualChildren[0]).NotifyDetachedFromLogicalTree(e); + } + + base.OnDetachedFromLogicalTree(e); + } + /// /// Called when the control's template is applied. /// diff --git a/src/Perspex.Styling/LogicalTree/ILogical.cs b/src/Perspex.Styling/LogicalTree/ILogical.cs index 5eb4b42abf..e10702a148 100644 --- a/src/Perspex.Styling/LogicalTree/ILogical.cs +++ b/src/Perspex.Styling/LogicalTree/ILogical.cs @@ -35,5 +35,15 @@ namespace Perspex.LogicalTree /// Gets the logical children. /// IPerspexReadOnlyList LogicalChildren { get; } + + /// + /// Notifies the control that it is being detached from a rooted logical tree. + /// + /// The event args. + /// + /// This method will be called automatically by the framework, you should not need to call + /// this method yourself. + /// + void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e); } } diff --git a/tests/Perspex.Controls.UnitTests/ControlTests.cs b/tests/Perspex.Controls.UnitTests/ControlTests.cs index 5016527123..2d09da00d5 100644 --- a/tests/Perspex.Controls.UnitTests/ControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/ControlTests.cs @@ -212,6 +212,24 @@ namespace Perspex.Controls.UnitTests } } + [Fact] + public void StyleDetach_Is_Triggered_When_Control_Removed_From_Logical_Tree() + { + using (PerspexLocator.EnterScope()) + { + var root = new TestRoot(); + var child = new Border(); + + root.Child = child; + + bool styleDetachTriggered = false; + ((IStyleable)child).StyleDetach.Subscribe(_ => styleDetachTriggered = true); + root.Child = null; + + Assert.True(styleDetachTriggered); + } + } + private class TestControl : Control { public new PerspexObject InheritanceParent => base.InheritanceParent; diff --git a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs index a93c6b3026..ac02ce9306 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -344,6 +344,52 @@ namespace Perspex.Controls.UnitTests.Primitives Assert.Null(border.TemplatedParent); } + [Fact] + public void TemplateChild_AttachedToLogicalTree_Should_Be_Raised() + { + Border templateChild = new Border(); + var root = new TestRoot + { + Child = new TestTemplatedControl + { + Template = new FuncControlTemplate(_ => new Decorator + { + Child = templateChild, + }) + } + }; + + var raised = false; + templateChild.AttachedToLogicalTree += (s, e) => raised = true; + + root.Child.ApplyTemplate(); + Assert.True(raised); + } + + [Fact] + public void TemplateChild_DetachedFromLogicalTree_Should_Be_Raised() + { + Border templateChild = new Border(); + var root = new TestRoot + { + Child = new TestTemplatedControl + { + Template = new FuncControlTemplate(_ => new Decorator + { + Child = templateChild, + }) + } + }; + + root.Child.ApplyTemplate(); + + var raised = false; + templateChild.DetachedFromLogicalTree += (s, e) => raised = true; + + root.Child = null; + Assert.True(raised); + } + private static IControl ScrollingContentControlTemplate(ContentControl control) { return new Border diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs index 1e11fcfeb9..0f054aea96 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs @@ -135,6 +135,11 @@ namespace Perspex.Styling.UnitTests { throw new NotImplementedException(); } + + public void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + throw new NotImplementedException(); + } } public class TestLogical1 : TestLogical diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs index c6749fd58e..1fb5a39167 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs @@ -166,6 +166,11 @@ namespace Perspex.Styling.UnitTests { throw new NotImplementedException(); } + + public void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + throw new NotImplementedException(); + } } public class TestLogical1 : TestLogical diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Template.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Template.cs index 26a0921f71..f4a26a8e90 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Template.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Template.cs @@ -1,6 +1,7 @@ // Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using System.Linq; using System.Reactive.Linq; using Moq; @@ -11,6 +12,9 @@ using System.Threading.Tasks; namespace Perspex.Styling.UnitTests { + using System.Reactive; + using System.Reactive.Subjects; + using Collections; using Controls = Controls.Controls; public class SelectorTests_Template @@ -108,6 +112,28 @@ namespace Perspex.Styling.UnitTests Assert.False(await activator.Take(1)); } + [Fact] + public void Nested_Selector_Is_Unsubscribed() + { + var target = new Mock(); + var templatedControl = target.As(); + var styleable = target.As(); + BuildVisualTree(target); + + styleable.Setup(x => x.Classes).Returns(new Classes("foo")); + var border = (Border)target.Object.VisualChildren.Single(); + var selector = new Selector().OfType(templatedControl.Object.GetType()).Class("foo").Template().OfType(); + var activator = selector.Match(border).ObservableResult; + var inccDebug = (InccDebug)styleable.Object.Classes; + + using (activator.Subscribe(_ => { })) + { + Assert.Equal(1, inccDebug.GetCollectionChangedSubscribers().Length); + } + + Assert.Null(inccDebug.GetCollectionChangedSubscribers()); + } + private void BuildVisualTree(Mock templatedControl) where T : class, IVisual { templatedControl.Setup(x => x.VisualChildren).Returns(new Controls