Browse Source

Fixed a binding bug.

StyleActivator wasn't signalling OnCompleted when it was subscribed in a
completed state, causing styles to be applied to controls that would
never activate.
pull/4/head
Steven Kirk 12 years ago
parent
commit
4fb39c6d9a
  1. 2
      Perspex.UnitTests/Perspex.UnitTests.csproj
  2. 38
      Perspex.UnitTests/Styling/StyleActivatorTests.cs
  3. 14
      Perspex/Styling/Style.cs
  4. 32
      Perspex/Styling/StyleActivator.cs
  5. 18
      Perspex/Styling/StyleBinding.cs
  6. 7
      Perspex/Styling/Styler.cs

2
Perspex.UnitTests/Perspex.UnitTests.csproj

@ -76,7 +76,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PerspexObjectTests.cs" />
<Compile Include="StyleTests.cs" />
<Compile Include="Styling\ActivatorTests.cs" />
<Compile Include="Styling\StyleActivatorTests.cs" />
<Compile Include="Styling\SelectorTests_Multiple.cs" />
<Compile Include="Styling\SelectorTests_Class.cs" />
<Compile Include="Styling\SelectorTests_Id.cs" />

38
Perspex.UnitTests/Styling/ActivatorTests.cs → Perspex.UnitTests/Styling/StyleActivatorTests.cs

@ -1,4 +1,5 @@
namespace Perspex.UnitTests.Styling
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Perspex.UnitTests.Styling
{
using System;
using System.Collections.Generic;
@ -9,16 +10,15 @@
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Perspex.Styling;
using Activator = Perspex.Styling.StyleActivator;
[TestClass]
public class ActivatorTests
public class StyleActivatorTests
{
[TestMethod]
public void Activator_And_Should_Follow_Single_Input()
{
var inputs = new[] { new TestSubject<bool>(false) };
var target = new Activator(inputs, ActivatorMode.And);
var target = new StyleActivator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>();
target.Subscribe(result);
@ -42,7 +42,7 @@
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.And);
var target = new StyleActivator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>();
target.Subscribe(result);
@ -67,7 +67,7 @@
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.And);
var target = new StyleActivator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>();
target.Subscribe(result);
@ -93,7 +93,7 @@
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.And);
var target = new StyleActivator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>();
target.Subscribe(result);
@ -110,7 +110,7 @@
public void Activator_Or_Should_Follow_Single_Input()
{
var inputs = new[] { new TestSubject<bool>(false) };
var target = new Activator(inputs, ActivatorMode.Or);
var target = new StyleActivator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>();
target.Subscribe(result);
@ -134,7 +134,7 @@
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.Or);
var target = new StyleActivator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>();
target.Subscribe(result);
@ -158,7 +158,7 @@
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.Or);
var target = new StyleActivator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>();
target.Subscribe(result);
@ -183,7 +183,7 @@
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.Or);
var target = new StyleActivator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>();
target.Subscribe(result);
@ -196,5 +196,21 @@
Assert.AreEqual(1, inputs[1].SubscriberCount);
Assert.AreEqual(1, inputs[2].SubscriberCount);
}
[TestMethod]
public void Completed_Activator_Should_Signal_OnCompleted()
{
var inputs = new[]
{
Observable.Return(false),
};
var target = new StyleActivator(inputs, ActivatorMode.Or);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
Assert.IsTrue(completed);
}
}
}

14
Perspex/Styling/Style.cs

@ -43,11 +43,21 @@ namespace Perspex.Styling
string description = "Style " + this.Selector.ToString();
StyleActivator activator = this.Selector.GetActivator(control);
if (!(activator.CurrentValue == false && activator.HasCompleted))
if (activator.CurrentValue || !activator.HasCompleted)
{
IObservable<bool> observable = activator;
// If the activator has completed, then we want its value to be true forever.
// Because of this we can't pass the activator directly as it will complete
// immediately and remove the binding.
if (activator.HasCompleted)
{
observable = Observable.Never<bool>().StartWith(true);
}
foreach (Setter setter in this.Setters)
{
StyleBinding binding = new StyleBinding(activator, setter.Value, description);
StyleBinding binding = new StyleBinding(observable, setter.Value, description);
control.Bind(setter.Property, binding, this.Selector.Priority);
}
}

32
Perspex/Styling/StyleActivator.cs

@ -21,26 +21,25 @@ namespace Perspex.Styling
{
private ActivatorMode mode;
private List<bool> values = new List<bool>();
private bool[] values;
private List<IDisposable> subscriptions = new List<IDisposable>();
private List<IObserver<bool>> observers = new List<IObserver<bool>>();
public StyleActivator(
IEnumerable<IObservable<bool>> inputs,
IList<IObservable<bool>> inputs,
ActivatorMode mode = ActivatorMode.And)
{
int i = 0;
this.mode = mode;
this.values = new bool[inputs.Count];
foreach (IObservable<bool> input in inputs)
{
int capturedIndex = i;
this.values.Add(false);
IDisposable subscription = input.Subscribe(
x => this.Update(capturedIndex, x),
x => this.Finish(capturedIndex),
@ -79,9 +78,18 @@ namespace Perspex.Styling
{
Contract.Requires<ArgumentNullException>(observer != null);
this.observers.Add(observer);
observer.OnNext(this.CurrentValue);
return Disposable.Create(() => this.observers.Remove(observer));
if (this.HasCompleted)
{
observer.OnCompleted();
return Disposable.Empty;
}
else
{
this.observers.Add(observer);
return Disposable.Create(() => this.observers.Remove(observer));
}
}
private void Update(int index, bool value)
@ -111,10 +119,14 @@ namespace Perspex.Styling
private void Finish(int i)
{
// If the observable has finished on 'false' and we're in And mode then it will never
// go back to true so we can unsubscribe from all the other subscriptions now.
// Similarly in Or mode; if the completed value is true then we're done.
bool unsubscribe = this.mode == ActivatorMode.And ? !this.values[i] : this.values[i];
// We can unsubscribe from everything if the completed observable:
// - Is the only subscription.
// - Has finished on 'false' and we're in And mode
// - Has finished on 'true' and we're in Or mode
var value = this.values[i];
var unsubscribe =
(this.values.Length == 1) ||
(this.mode == ActivatorMode.And ? !value : value);
if (unsubscribe)
{

18
Perspex/Styling/StyleBinding.cs

@ -15,15 +15,14 @@ namespace Perspex.Styling
/// <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="Value"/>.
/// When the activator produces false (and before the activator returns a value) it will
/// produce <see cref="PerspexProperty.UnsetValue"/>.
/// When the activator produces false it will produce <see cref="PerspexProperty.UnsetValue"/>.
/// </remarks>
internal class StyleBinding : IObservable<object>, IObservableDescription
{
/// <summary>
/// The subject that provides the observable implementation.
/// The activator.
/// </summary>
private BehaviorSubject<object> subject = new BehaviorSubject<object>(PerspexProperty.UnsetValue);
private IObservable<bool> activator;
/// <summary>
/// Initializes a new instance of the <see cref="StyleBinding"/> class.
@ -36,13 +35,9 @@ namespace Perspex.Styling
object activatedValue,
string description)
{
this.activator = activator;
this.ActivatedValue = activatedValue;
this.Description = description;
activator.Subscribe(
active => this.subject.OnNext(active ? this.ActivatedValue : PerspexProperty.UnsetValue),
error => this.subject.OnError(error),
() => this.subject.OnCompleted());
}
/// <summary>
@ -71,7 +66,10 @@ namespace Perspex.Styling
public IDisposable Subscribe(IObserver<object> observer)
{
Contract.Requires<NullReferenceException>(observer != null);
return this.subject.Subscribe(observer);
return this.activator.Subscribe(
active => observer.OnNext(active ? this.ActivatedValue : PerspexProperty.UnsetValue),
observer.OnError,
observer.OnCompleted);
}
}
}

7
Perspex/Styling/Styler.cs

@ -18,7 +18,12 @@ namespace Perspex.Styling
{
IVisual visual = control as IVisual;
IStyled styleContainer = visual.GetVisualAncestorOrSelf<IStyled>();
Application.Current.Styles.Attach(control);
if (Application.Current != null)
{
Application.Current.Styles.Attach(control);
}
this.ApplyStyles(control, styleContainer);
}

Loading…
Cancel
Save