Browse Source

Allow setters to use observables for their value.

pull/83/head
Steven Kirk 11 years ago
parent
commit
ac24ccaebd
  1. 72
      Tests/Perspex.Styling.UnitTests/StyleTests.cs
  2. 52
      src/Perspex.Styling/Styling/Setter.cs
  3. 31
      src/Perspex.Styling/Styling/Style.cs
  4. 59
      src/Perspex.Styling/Styling/StyleBinding.cs

72
Tests/Perspex.Styling.UnitTests/StyleTests.cs

@ -8,6 +8,7 @@ namespace Perspex.Styling.UnitTests
{
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
using Perspex.Controls;
using Perspex.Styling;
using Xunit;
@ -107,6 +108,77 @@ namespace Perspex.Styling.UnitTests
Assert.Equal(new[] { "foodefault", "Foo", "Bar", "foodefault" }, values);
}
[Fact]
public void Style_With_Value_And_Source_Should_Throw_Exception()
{
var source = new BehaviorSubject<string>("Foo");
Style style = new Style(x => x.OfType<Class1>())
{
Setters = new[]
{
new Setter
{
Property = Class1.FooProperty,
Source = source,
Value = "Foo",
},
},
};
var target = new Class1();
Assert.Throws<InvalidOperationException>(() => style.Attach(target));
}
[Fact]
public void Style_With_Source_Should_Update_Value()
{
var source = new BehaviorSubject<string>("Foo");
Style style = new Style(x => x.OfType<Class1>())
{
Setters = new[]
{
new Setter(Class1.FooProperty, source),
},
};
var target = new Class1();
style.Attach(target);
Assert.Equal("Foo", target.Foo);
source.OnNext("Bar");
Assert.Equal("Bar", target.Foo);
}
[Fact]
public void Style_With_Source_Should_Update_And_Restore_Value()
{
var source = new BehaviorSubject<string>("Foo");
Style style = new Style(x => x.OfType<Class1>().Class("foo"))
{
Setters = new[]
{
new Setter(Class1.FooProperty, source),
},
};
var target = new Class1();
style.Attach(target);
Assert.Equal("foodefault", target.Foo);
target.Classes.Add("foo");
Assert.Equal("Foo", target.Foo);
source.OnNext("Bar");
Assert.Equal("Bar", target.Foo);
target.Classes.Remove("foo");
Assert.Equal("foodefault", target.Foo);
}
private class Class1 : Control
{
public static readonly PerspexProperty<string> FooProperty =

52
src/Perspex.Styling/Styling/Setter.cs

@ -1,29 +1,79 @@
// -----------------------------------------------------------------------
// <copyright file="Setter.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Styling
{
using System;
/// <summary>
/// A setter for a <see cref="Style"/>.
/// </summary>
/// <remarks>
/// A <see cref="Setter"/> is used to set a <see cref="PerspexProperty"/> value on a
/// <see cref="PerspexObject"/> depending on a condition.
/// </remarks>
public class Setter
{
/// <summary>
/// Initializes a new instance of the <see cref="Setter"/> class.
/// </summary>
public Setter()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Setter"/> class.
/// </summary>
/// <param name="property">The property to set.</param>
/// <param name="value">The property value.</param>
public Setter(PerspexProperty property, object value)
{
this.Property = property;
this.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="Setter"/> class.
/// </summary>
/// <param name="property">The property to set.</param>
/// <param name="source">An observable which produces the value for the property.</param>
public Setter(PerspexProperty property, IObservable<object> source)
{
this.Property = property;
this.Source = source;
}
/// <summary>
/// Gets or sets the property to set.
/// </summary>
public PerspexProperty Property
{
get;
set;
}
/// <summary>
/// Gets or sets an observable which produces the value for the property.
/// </summary>
/// <remarks>
/// Only one of <see cref="Source"/> and <see cref="Value"/> should be set.
/// </remarks>
public IObservable<object> Source
{
get;
set;
}
/// <summary>
/// Gets or sets the property value.
/// </summary>
/// <remarks>
/// Only one of <see cref="Source"/> and <see cref="Value"/> should be set.
/// </remarks>
public object Value
{
get;

31
src/Perspex.Styling/Styling/Style.cs

@ -46,7 +46,19 @@ namespace Perspex.Styling
{
foreach (Setter setter in this.Setters)
{
control.SetValue(setter.Property, setter.Value, BindingPriority.Style);
if (setter.Source != null && setter.Value != null)
{
throw new InvalidOperationException("Cannot set both Source and Value on a Setter.");
}
if (setter.Source == null)
{
control.SetValue(setter.Property, setter.Value, BindingPriority.Style);
}
else
{
control.Bind(setter.Property, setter.Source, BindingPriority.Style);
}
}
}
}
@ -54,7 +66,22 @@ namespace Perspex.Styling
{
foreach (Setter setter in this.Setters)
{
var binding = new StyleBinding(match.ObservableResult, setter.Value, description);
if (setter.Source != null && setter.Value != null)
{
throw new InvalidOperationException("Cannot set both Source and Value on a Setter.");
}
StyleBinding binding;
if (setter.Source == null)
{
binding = new StyleBinding(match.ObservableResult, setter.Value, description);
}
else
{
binding = new StyleBinding(match.ObservableResult, setter.Source, description);
}
control.Bind(setter.Property, binding, BindingPriority.StyleTrigger);
}
}

59
src/Perspex.Styling/Styling/StyleBinding.cs

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------
// <copyright file="StyleBinding.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
@ -8,14 +8,16 @@ namespace Perspex.Styling
{
using System;
using System.Reactive;
using System.Reactive.Linq;
/// <summary>
/// Provides an observable for a style.
/// </summary>
/// <remarks>
/// This class takes an activator and a value. The activator is an observable which produces
/// a bool. When the activator produces true, this observable will produce
/// <see cref="ActivatedValue"/>. When the activator produces false it will produce
/// A <see cref="StyleBinding"/> has two inputs: an activator observable and either an
/// <see cref="ActivatedValue"/> or a <see cref="Source"/> observable which produces the
/// activated value. When the activator produces true, the <see cref="StyleBinding"/> will
/// produce the current activated value. When the activator produces false it will produce
/// <see cref="PerspexProperty.UnsetValue"/>.
/// </remarks>
internal class StyleBinding : ObservableBase<object>, IDescription
@ -42,12 +44,19 @@ namespace Perspex.Styling
}
/// <summary>
/// Gets a description of the binding.
/// Initializes a new instance of the <see cref="StyleBinding"/> class.
/// </summary>
public string Description
/// <param name="activator">The activator.</param>
/// <param name="source">An observable that produces the activated value.</param>
/// <param name="description">The binding description.</param>
public StyleBinding(
IObservable<bool> activator,
IObservable<object> source,
string description)
{
get;
private set;
this.activator = activator;
this.Description = description;
this.Source = source;
}
/// <summary>
@ -59,6 +68,22 @@ namespace Perspex.Styling
private set;
}
/// <summary>
/// Gets a description of the binding.
/// </summary>
public string Description
{
get;
}
/// <summary>
/// Gets an observable which produces the <see cref="ActivatedValue"/>.
/// </summary>
public IObservable<object> Source
{
get;
}
/// <summary>
/// Notifies the provider that an observer is to receive notifications.
/// </summary>
@ -67,10 +92,20 @@ namespace Perspex.Styling
protected override IDisposable SubscribeCore(IObserver<object> observer)
{
Contract.Requires<NullReferenceException>(observer != null);
return this.activator.Subscribe(
active => observer.OnNext(active ? this.ActivatedValue : PerspexProperty.UnsetValue),
observer.OnError,
observer.OnCompleted);
if (this.Source == null)
{
return this.activator.Subscribe(
active => observer.OnNext(active ? this.ActivatedValue : PerspexProperty.UnsetValue),
observer.OnError,
observer.OnCompleted);
}
else
{
return Observable
.CombineLatest(this.activator, this.Source, (x, y) => new { Active = x, Value = y })
.Subscribe(x => observer.OnNext(x.Active ? x.Value : PerspexProperty.UnsetValue));
}
}
}
}

Loading…
Cancel
Save