Browse Source

Working better than before now...

Can have more than one style attached to a property, YAY
pull/4/head
grokys 12 years ago
parent
commit
dae8dcc8bd
  1. 87
      Perspex.UnitTests/PerspexObjectTests.cs
  2. 13
      Perspex/IBindingDescription.cs
  3. 22
      Perspex/Match.cs
  4. 1
      Perspex/Perspex.csproj
  5. 153
      Perspex/PerspexObject.cs
  6. 25
      Perspex/PriorityValue.cs
  7. 1
      Perspex/Selectors.cs
  8. 48
      Perspex/Setter.cs
  9. 38
      Perspex/Style.cs

87
Perspex.UnitTests/PerspexObjectTests.cs

@ -9,6 +9,7 @@ namespace Perspex.UnitTests
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
@ -317,6 +318,92 @@ namespace Perspex.UnitTests
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void StyleBinding_Overrides_Default_Value()
{
Class1 target = new Class1();
target.SetValue(Class1.FooProperty, "stylevalue", Observable.Return(true));
Assert.AreEqual("stylevalue", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void StyleBinding_Doesnt_Override_Local_Value()
{
Class1 target = new Class1();
target.SetValue(Class1.FooProperty, "newvalue");
target.SetValue(Class1.FooProperty, "stylevalue", Observable.Return(true));
Assert.AreEqual("newvalue", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void StyleBinding_Deactivated_Doesnt_Override_Default_Value()
{
Class1 target = new Class1();
target.SetValue(Class1.FooProperty, "stylevalue", Observable.Return(false));
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void StyleBinding_Toggles_On_Activation()
{
Class1 target = new Class1();
Subject<bool> source = new Subject<bool>();
target.SetValue(Class1.FooProperty, "stylevalue", source);
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
source.OnNext(true);
Assert.AreEqual("stylevalue", target.GetValue(Class1.FooProperty));
source.OnNext(false);
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void StyleBinding_Detaches_OnCompleted()
{
Class1 target = new Class1();
Subject<bool> source = new Subject<bool>();
target.SetValue(Class1.FooProperty, "stylevalue", source);
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
source.OnNext(true);
Assert.AreEqual("stylevalue", target.GetValue(Class1.FooProperty));
source.OnCompleted();
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void Later_StyleBindings_Have_Precedence()
{
Class1 target = new Class1();
Subject<bool> source1 = new Subject<bool>();
Subject<bool> source2 = new Subject<bool>();
target.SetValue(Class1.FooProperty, "style1", source1);
target.SetValue(Class1.FooProperty, "style2", source2);
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
source1.OnNext(true);
Assert.AreEqual("style1", target.GetValue(Class1.FooProperty));
source2.OnNext(true);
Assert.AreEqual("style2", target.GetValue(Class1.FooProperty));
source1.OnNext(false);
Assert.AreEqual("style2", target.GetValue(Class1.FooProperty));
source2.OnNext(false);
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
source2.OnNext(true);
Assert.AreEqual("style2", target.GetValue(Class1.FooProperty));
source1.OnNext(true);
Assert.AreEqual("style2", target.GetValue(Class1.FooProperty));
}
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =

13
Perspex/IBindingDescription.cs

@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Perspex
{
public interface IBindingDescription
{
string Description { get; }
}
}

22
Perspex/Match.cs

@ -7,6 +7,9 @@
namespace Perspex
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Controls;
public class Match
@ -35,6 +38,25 @@ namespace Perspex
set;
}
public IObservable<bool> GetActivator()
{
List<IObservable<bool>> observables = new List<IObservable<bool>>();
Match match = this;
do
{
if (match.Observable != null)
{
observables.Add(match.Observable);
}
match = match.Previous;
}
while (match != null);
return System.Reactive.Linq.Observable.CombineLatest(observables).Select(x => x.All(b => b));
}
public override string ToString()
{
string result = (this.Previous != null) ? this.Previous.ToString() : string.Empty;

1
Perspex/Perspex.csproj

@ -78,7 +78,6 @@
<Compile Include="Controls\TextBlock.cs" />
<Compile Include="Input\MouseEventArgs.cs" />
<Compile Include="Interactive.cs" />
<Compile Include="IBindingDescription.cs" />
<Compile Include="IStyle.cs" />
<Compile Include="PriorityValue.cs" />
<Compile Include="Layout\ILayoutable.cs" />

153
Perspex/PerspexObject.cs

@ -331,6 +331,32 @@ namespace Perspex
return this.values.ContainsKey(property);
}
/// <summary>
/// Sets a <see cref="PerspexProperty"/> value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
public void SetValue<T>(PerspexProperty<T> property, T value)
{
Contract.Requires<NullReferenceException>(property != null);
this.SetValue((PerspexProperty)property, value);
}
/// <summary>
/// Binds a <see cref="PerspexProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
public void SetValue<T>(PerspexProperty<T> property, IObservable<T> source)
{
Contract.Requires<NullReferenceException>(property != null);
this.SetValue((PerspexProperty)property, source);
}
/// <summary>
/// Sets a <see cref="PerspexProperty"/> value.
/// </summary>
@ -351,23 +377,8 @@ namespace Perspex
return;
}
v = new PriorityValue();
v = this.CreatePriorityValue(property);
this.values.Add(property, v);
v.Subscribe(x =>
{
object oldValue = (x.Item1 == PerspexProperty.UnsetValue) ?
this.GetDefaultValue(property) :
x.Item1;
object newValue = (x.Item2 == PerspexProperty.UnsetValue) ?
this.GetDefaultValue(property) :
x.Item2;
if (!object.Equals(oldValue, newValue))
{
this.RaisePropertyChanged(property, oldValue, newValue);
}
});
}
if (binding == null)
@ -377,33 +388,46 @@ namespace Perspex
else
{
v.SetLocalBinding(binding);
this.Log().Debug(string.Format(
"Bound value of {0}.{1} (#{2:x8})",
this.GetType().Name,
property.Name,
this.GetHashCode()));
}
}
/// <summary>
/// Sets a <see cref="PerspexProperty"/> value.
/// Binds a <see cref="PerspexProperty"/> to an style.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
public void SetValue<T>(PerspexProperty<T> property, T value)
/// <param name="value">The activated value.</param>
/// <param name="activator">An observable which activates the value.</param>
/// <remarks>
/// Style bindings have a lower precedence than local value bindings. They are toggled
/// on or off by <paramref name="activator"/> and can be unbound by the activator
/// completing.
/// </remarks>
public void SetValue(PerspexProperty property, object value, IObservable<bool> activator)
{
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<NullReferenceException>(activator != null);
this.SetValue((PerspexProperty)property, value);
}
PriorityValue v;
/// <summary>
/// Binds a <see cref="PerspexProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
public void SetValue<T>(PerspexProperty<T> property, IObservable<T> source)
{
Contract.Requires<NullReferenceException>(property != null);
if (!this.values.TryGetValue(property, out v))
{
v = this.CreatePriorityValue(property);
this.values.Add(property, v);
}
this.SetValue((PerspexProperty)property, source);
v.AddStyle(activator, value);
this.Log().Debug(string.Format(
"Bound value of {0}.{1} (#{2:x8}) to style",
this.GetType().Name,
property.Name,
this.GetHashCode()));
}
private static IObservable<object> BoxObservable<T>(IObservable<T> observable)
@ -445,6 +469,35 @@ namespace Perspex
return result;
}
private PriorityValue CreatePriorityValue(PerspexProperty property)
{
PriorityValue result = new PriorityValue();
result.Subscribe(x =>
{
object oldValue = (x.Item1 == PerspexProperty.UnsetValue) ?
this.GetDefaultValue(property) :
x.Item1;
object newValue = (x.Item2 == PerspexProperty.UnsetValue) ?
this.GetDefaultValue(property) :
x.Item2;
if (!object.Equals(oldValue, newValue))
{
this.RaisePropertyChanged(property, oldValue, newValue);
this.Log().Debug(string.Format(
"Set value of {0}.{1} (#{2:x8}) to {3}",
this.GetType().Name,
property.Name,
this.GetHashCode(),
newValue));
}
});
return result;
}
private object GetDefaultValue(PerspexProperty property)
{
if (property.Inherits && this.inheritanceParent != null)
@ -493,42 +546,6 @@ namespace Perspex
}
}
//private void SetValueImpl(PerspexProperty property, object value)
//{
// Contract.Requires<NullReferenceException>(property != null);
// if (!property.IsValidValue(value))
// {
// throw new InvalidOperationException("Invalid value for " + property.Name);
// }
// object oldValue = this.GetValue(property);
// if (!object.Equals(oldValue, value))
// {
// string valueString = value.ToString();
// if (value == PerspexProperty.UnsetValue)
// {
// valueString = "[Unset]";
// this.values.Remove(property);
// }
// else
// {
// this.values[property] = value;
// }
// this.RaisePropertyChanged(property, oldValue, value);
// this.Log().Debug(string.Format(
// "Set value of {0}.{1} (#{2:x8}) to '{3}'",
// this.GetType().Name,
// property.Name,
// this.GetHashCode(),
// valueString));
// }
//}
private class Binding
{
public object Observable { get; set; }

25
Perspex/PriorityValue.cs

@ -70,18 +70,6 @@ namespace Perspex
this.localBinding = binding.Subscribe(value => this.LocalValue = value);
}
public void AddStyle(object value)
{
StyleEntry entry = new StyleEntry(value);
this.styles.Add(entry);
if (this.localValue == PerspexProperty.UnsetValue)
{
this.Push();
}
}
public void AddStyle(IObservable<bool> activator, object value)
{
Contract.Requires<NullReferenceException>(activator != null);
@ -144,12 +132,6 @@ namespace Perspex
{
private IObservable<bool> activator;
public StyleEntry(object value)
{
this.Active = true;
this.Value = value;
}
public StyleEntry(
IObservable<bool> activator,
object value,
@ -162,7 +144,12 @@ namespace Perspex
this.activator = activator;
this.Value = value;
this.activator.Subscribe(x => this.Active = x, () => completed(this));
this.activator.Subscribe(x =>
{
this.Active = x;
activeChanged();
},
() => completed(this));
}
public bool Active

1
Perspex/Selectors.cs

@ -27,6 +27,7 @@ namespace Perspex
return new Match
{
Control = control,
Observable = Observable.Return(true),
Token = typeof(T).Name,
};
}

48
Perspex/Setter.cs

@ -14,8 +14,6 @@ namespace Perspex
public class Setter
{
private object oldValue;
public Setter()
{
}
@ -37,51 +35,5 @@ namespace Perspex
get;
set;
}
internal Subject CreateSubject(Control control, string description)
{
object oldValue = control.GetValue(this.Property);
return new Subject(control, this.Value, oldValue, description);
}
internal class Subject : IObservable<object>, IBindingDescription
{
private Control control;
private object onValue;
private object offValue;
private List<IObserver<object>> observers;
public Subject(Control control, object onValue, object offValue, string description)
{
this.control = control;
this.onValue = onValue;
this.offValue = offValue;
this.observers = new List<IObserver<object>>();
this.Description = description;
}
public string Description
{
get;
private set;
}
public IDisposable Subscribe(IObserver<object> observer)
{
observers.Add(observer);
return Disposable.Create(() => this.observers.Remove(observer));
}
public void Push(bool on)
{
foreach (IObserver<object> o in this.observers)
{
o.OnNext(on ? this.onValue : this.offValue);
}
}
}
}
}

38
Perspex/Style.cs

@ -45,45 +45,11 @@ namespace Perspex
if (match != null)
{
string description = "Style " + match.ToString();
List<IObservable<bool>> o = new List<IObservable<bool>>();
while (match != null)
{
if (match.Observable != null)
{
o.Add(match.Observable);
}
match = match.Previous;
}
List<Setter.Subject> subjects = new List<Setter.Subject>();
IObservable<bool> activator = match.GetActivator();
foreach (Setter setter in this.Setters)
{
Setter.Subject subject = setter.CreateSubject(control, description);
subjects.Add(subject);
control.SetValue(setter.Property, subject);
}
if (o.Count == 0)
{
foreach (Setter.Subject subject in subjects)
{
subject.Push(true);
}
}
else
{
Observable.CombineLatest(o).Subscribe(x =>
{
bool on = x.All(y => y);
foreach (Setter.Subject subject in subjects)
{
subject.Push(on);
}
});
control.SetValue(setter.Property, setter.Value, activator);
}
}
}

Loading…
Cancel
Save