Browse Source

Detach styles when control removed from visual tree.

pull/366/head
Steven Kirk 11 years ago
parent
commit
4fa3c98ca3
  1. 7
      src/Perspex.Controls/ItemsControl.cs
  2. 17
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  3. 4
      src/Perspex.Layout/LayoutManager.cs
  4. 4
      src/Perspex.Styling/Properties/AssemblyInfo.cs
  5. 14
      src/Perspex.Styling/Styling/Style.cs
  6. 13
      src/Perspex.Styling/Styling/StyleBinding.cs
  7. 2
      tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj
  8. 79
      tests/Perspex.Styling.UnitTests/StyleBindingTests.cs
  9. 27
      tests/Perspex.Styling.UnitTests/StyleTests.cs
  10. 34
      tests/Perspex.Styling.UnitTests/TestRoot.cs

7
src/Perspex.Controls/ItemsControl.cs

@ -152,6 +152,13 @@ namespace Perspex.Controls
Presenter = nameScope.Find<IItemsPresenter>("PART_ItemsPresenter");
}
/// <inheritdoc/>
protected override void OnTemplateChanged(PerspexPropertyChangedEventArgs e)
{
base.OnTemplateChanged(e);
ItemContainerGenerator.Clear();
}
/// <summary>
/// Caled when the <see cref="Items"/> property changes.
/// </summary>

17
src/Perspex.Controls/Primitives/TemplatedControl.cs

@ -81,12 +81,7 @@ namespace Perspex.Controls.Primitives
/// </summary>
static TemplatedControl()
{
TemplateProperty.Changed.Subscribe(e =>
{
var templatedControl = (TemplatedControl)e.Sender;
templatedControl._templateApplied = false;
templatedControl.InvalidateMeasure();
});
TemplateProperty.Changed.AddClassHandler<TemplatedControl>(x => x.OnTemplateChanged);
}
/// <summary>
@ -224,6 +219,16 @@ namespace Perspex.Controls.Primitives
{
}
/// <summary>
/// Called when the <see cref="Template"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnTemplateChanged(PerspexPropertyChangedEventArgs e)
{
_templateApplied = false;
InvalidateMeasure();
}
/// <summary>
/// Sets the TemplatedParent property for a control created from the control template and
/// applies the templates of nested templated controls. Also adds each control to its name

4
src/Perspex.Layout/LayoutManager.cs

@ -226,12 +226,12 @@ namespace Perspex.Layout
{
var parent = item.Control.GetVisualParent<ILayoutable>();
while (parent.PreviousMeasure == null)
while (parent != null && parent.PreviousMeasure == null)
{
parent = parent.GetVisualParent<ILayoutable>();
}
if (parent.GetVisualRoot() == Root)
if (parent != null && parent.GetVisualRoot() == Root)
{
parent.Measure(parent.PreviousMeasure.Value, true);
}

4
src/Perspex.Styling/Properties/AssemblyInfo.cs

@ -2,7 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reflection;
using System.Runtime.CompilerServices;
using Perspex.Metadata;
[assembly: AssemblyTitle("Perspex.Styling")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Styling")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Styling")]
[assembly: InternalsVisibleTo("Perspex.Styling.UnitTests")]

14
src/Perspex.Styling/Styling/Style.cs

@ -56,9 +56,21 @@ namespace Perspex.Styling
if (match.ImmediateResult != false)
{
var visual = control as IVisual;
var activator = match.ObservableResult ??
Observable.Never<bool>().StartWith(true);
if (visual != null)
{
var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => visual.DetachedFromVisualTree += x,
x => visual.DetachedFromVisualTree -= x);
activator = activator.TakeUntil(detached);
}
foreach (var setter in Setters)
{
setter.Apply(this, control, match.ObservableResult);
setter.Apply(this, control, activator);
}
}
}

13
src/Perspex.Styling/Styling/StyleBinding.cs

@ -61,7 +61,8 @@ namespace Perspex.Styling
/// </summary>
public object ActivatedValue
{
get; }
get;
}
/// <summary>
/// Gets a description of the binding.
@ -90,16 +91,16 @@ namespace Perspex.Styling
if (Source == null)
{
return _activator.Subscribe(
active => observer.OnNext(active ? ActivatedValue : PerspexProperty.UnsetValue),
observer.OnError,
observer.OnCompleted);
return _activator
.Select(active => active ? ActivatedValue : PerspexProperty.UnsetValue)
.Subscribe(observer);
}
else
{
return _activator
.CombineLatest(Source, (x, y) => new { Active = x, Value = y })
.Subscribe(x => observer.OnNext(x.Active ? x.Value : PerspexProperty.UnsetValue));
.Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue)
.Subscribe(observer);
}
}
}

2
tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj

@ -85,10 +85,12 @@
<Compile Include="SelectorTests_OfType.cs" />
<Compile Include="SelectorTests_Template.cs" />
<Compile Include="StyleActivatorTests.cs" />
<Compile Include="StyleBindingTests.cs" />
<Compile Include="StyleTests.cs" />
<Compile Include="TestControlBase.cs" />
<Compile Include="TestObservable.cs" />
<Compile Include="TestObserver.cs" />
<Compile Include="TestRoot.cs" />
<Compile Include="TestSelectors.cs" />
<Compile Include="TestSubject.cs" />
<Compile Include="TestTemplatedControl.cs" />

79
tests/Perspex.Styling.UnitTests/StyleBindingTests.cs

@ -0,0 +1,79 @@
// 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.Collections.Generic;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Xunit;
namespace Perspex.Styling.UnitTests
{
public class StyleBindingTests
{
[Fact]
public async void Should_Produce_UnsetValue_On_Activator_False()
{
var activator = new BehaviorSubject<bool>(false);
var target = new StyleBinding(activator, 1, string.Empty);
var result = await target.Take(1);
Assert.Equal(PerspexProperty.UnsetValue, result);
}
[Fact]
public async void Should_Produce_Value_On_Activator_True()
{
var activator = new BehaviorSubject<bool>(true);
var target = new StyleBinding(activator, 1, string.Empty);
var result = await target.Take(1);
Assert.Equal(1, result);
}
[Fact]
public void Should_Change_Value_On_Activator_Change()
{
var activator = new BehaviorSubject<bool>(false);
var target = new StyleBinding(activator, 1, string.Empty);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
activator.OnNext(true);
activator.OnNext(false);
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result);
}
[Fact]
public void Should_Change_Value_With_Source_Observable()
{
var activator = new BehaviorSubject<bool>(false);
var source = new BehaviorSubject<object>(1);
var target = new StyleBinding(activator, source, string.Empty);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
activator.OnNext(true);
source.OnNext(2);
activator.OnNext(false);
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, 2, PerspexProperty.UnsetValue }, result);
}
[Fact]
public void Should_Complete_When_Activator_Completes()
{
var activator = new BehaviorSubject<bool>(false);
var target = new StyleBinding(activator, 1, string.Empty);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
activator.OnCompleted();
Assert.True(completed);
}
}
}

27
tests/Perspex.Styling.UnitTests/StyleTests.cs

@ -166,7 +166,7 @@ namespace Perspex.Styling.UnitTests
{
var source = new BehaviorSubject<string>("Foo");
Style style = new Style(x => x.OfType<Class1>().Class("foo"))
var style = new Style(x => x.OfType<Class1>().Class("foo"))
{
Setters = new[]
{
@ -187,6 +187,31 @@ namespace Perspex.Styling.UnitTests
Assert.Equal("foodefault", target.Foo);
}
[Fact]
public void Style_Should_Detach_When_Removed_From_Visual_Tree()
{
Border border;
var style = new Style(x => x.OfType<Border>())
{
Setters = new[]
{
new Setter(Border.BorderThicknessProperty, 4),
}
};
var root = new TestRoot
{
Child = border = new Border(),
};
style.Attach(border, null);
Assert.Equal(4, border.BorderThickness);
root.Child = null;
Assert.Equal(0, border.BorderThickness);
}
private class Class1 : Control
{
public static readonly PerspexProperty<string> FooProperty =

34
tests/Perspex.Styling.UnitTests/TestRoot.cs

@ -0,0 +1,34 @@
// 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 Moq;
using Perspex.Controls;
using Perspex.Layout;
using Perspex.Platform;
using Perspex.Rendering;
namespace Perspex.Styling.UnitTests
{
internal class TestRoot : Decorator, ILayoutRoot, IRenderRoot
{
public Size ClientSize => new Size(100, 100);
public ILayoutManager LayoutManager => new Mock<ILayoutManager>().Object;
public IRenderTarget RenderTarget
{
get { throw new NotImplementedException(); }
}
public IRenderQueueManager RenderQueueManager
{
get { throw new NotImplementedException(); }
}
public Point TranslatePointToScreen(Point p)
{
return new Point();
}
}
}
Loading…
Cancel
Save