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