Browse Source

Started overhaul of PerspexProperty system.

Back to where we were before...
pull/4/head
grokys 12 years ago
parent
commit
072bc936ad
  1. 21
      Perspex.UnitTests/PerspexObjectTests.cs
  2. 1
      Perspex/Perspex.csproj
  3. 270
      Perspex/PerspexObject.cs
  4. 181
      Perspex/PriorityValue.cs

21
Perspex.UnitTests/PerspexObjectTests.cs

@ -87,6 +87,16 @@ namespace Perspex.UnitTests
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void SetValue_Sets_Value()
{
Class1 target = new Class1();
target.SetValue(Class1.FooProperty, "newvalue");
Assert.AreEqual("newvalue", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void SetValue_Raises_PropertyChanged()
{
@ -296,6 +306,17 @@ namespace Perspex.UnitTests
Assert.AreEqual("reset", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void Setting_UnsetValue_Reverts_To_Default_Value()
{
Class1 target = new Class1();
target.SetValue(Class1.FooProperty, "newvalue");
target.SetValue(Class1.FooProperty, PerspexProperty.UnsetValue);
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
}
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =

1
Perspex/Perspex.csproj

@ -80,6 +80,7 @@
<Compile Include="Interactive.cs" />
<Compile Include="IBindingDescription.cs" />
<Compile Include="IStyle.cs" />
<Compile Include="PriorityValue.cs" />
<Compile Include="Layout\ILayoutable.cs" />
<Compile Include="Layout\ILayoutManager.cs" />
<Compile Include="Layout\ILayoutRoot.cs" />

270
Perspex/PerspexObject.cs

@ -36,16 +36,10 @@ namespace Perspex
private PerspexObject inheritanceParent;
/// <summary>
/// The set values on this object.
/// The set values/bindings on this object.
/// </summary>
private Dictionary<PerspexProperty, object> values =
new Dictionary<PerspexProperty, object>();
/// <summary>
/// The current bindings on this object.
/// </summary>
private Dictionary<PerspexProperty, Binding> bindings =
new Dictionary<PerspexProperty, Binding>();
private Dictionary<PerspexProperty, PriorityValue> values =
new Dictionary<PerspexProperty, PriorityValue>();
/// <summary>
/// Raised when a <see cref="PerspexProperty"/> value changes on this object/
@ -163,12 +157,11 @@ namespace Perspex
public void ClearBinding(PerspexProperty property)
{
Contract.Requires<NullReferenceException>(property != null);
Binding binding;
PriorityValue value;
if (this.bindings.TryGetValue(property, out binding))
if (this.values.TryGetValue(property, out value))
{
binding.Dispose.Dispose();
this.bindings.Remove(property);
value.ClearLocalBinding();
this.Log().Debug(string.Format(
"Cleared binding on {0}.{1} (#{2:x8})",
@ -189,60 +182,6 @@ namespace Perspex
this.SetValue(property, PerspexProperty.UnsetValue);
}
/// <summary>
/// Clears a binding on a <see cref="PerspexProperty"/>, returning the bound observable and
/// leaving the last bound value in place.
/// </summary>
/// <param name="property">The property.</param>
public IObservable<object> ExtractBinding(PerspexProperty property)
{
Binding binding;
if (this.bindings.TryGetValue(property, out binding))
{
this.bindings.Remove(property);
this.Log().Debug(string.Format(
"Extracted binding on {0}.{1} (#{2:x8})",
this.GetType().Name,
property.Name,
this.GetHashCode()));
return (IObservable<object>)binding.Observable;
}
else
{
return null;
}
}
/// <summary>
/// Clears a binding on a <see cref="PerspexProperty"/>, returning the bound observable and
/// leaving the last bound value in place.
/// </summary>
/// <param name="property">The property.</param>
public IObservable<T> ExtractBinding<T>(PerspexProperty<T> property)
{
Binding binding;
if (this.bindings.TryGetValue(property, out binding))
{
this.bindings.Remove(property);
this.Log().Debug(string.Format(
"Extracted binding on {0}.{1} (#{2:x8})",
this.GetType().Name,
property.Name,
this.GetHashCode()));
return (IObservable<T>)binding.Observable;
}
else
{
return null;
}
}
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
@ -334,21 +273,25 @@ namespace Perspex
{
Contract.Requires<NullReferenceException>(property != null);
object value;
object result;
if (!this.values.TryGetValue(property, out value))
PriorityValue value;
if (this.values.TryGetValue(property, out value))
{
if (property.Inherits && this.inheritanceParent != null)
{
value = this.inheritanceParent.GetValue(property);
}
else
{
value = property.GetDefaultValue(this.GetType());
}
result = value.GetEffectiveValue();
}
else
{
result = PerspexProperty.UnsetValue;
}
return value;
if (result == PerspexProperty.UnsetValue)
{
result = this.GetDefaultValue(property);
}
return result;
}
/// <summary>
@ -397,48 +340,43 @@ namespace Perspex
{
Contract.Requires<NullReferenceException>(property != null);
TypeInfo typeInfo = value.GetType().GetTypeInfo();
Type observableType = typeInfo.ImplementedInterfaces.FirstOrDefault(x =>
x.IsConstructedGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>));
IObservable<object> binding = TryCastToObservable(value);
this.ClearBinding(property);
PriorityValue v;
if (observableType == null)
{
this.SetValueImpl(property, value);
}
else
if (!this.values.TryGetValue(property, out v))
{
IObservable<object> observable = value as IObservable<object>;
IBindingDescription bindingDescription = value as IBindingDescription;
string description = (bindingDescription != null) ? bindingDescription.Description : value.GetType().Name;
if (observable == null)
if (value == PerspexProperty.UnsetValue)
{
MethodInfo cast = typeof(PerspexObject).GetTypeInfo()
.DeclaredMethods
.FirstOrDefault(x => x.Name == "CastToObject")
.MakeGenericMethod(observableType.GenericTypeArguments[0]);
observable = (IObservable<object>)cast.Invoke(null, new[] { value });
return;
}
this.bindings.Add(property, new Binding
v = new PriorityValue();
this.values.Add(property, v);
v.Subscribe(x =>
{
Observable = value,
Dispose = observable.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.SetValueImpl(property, x);
}),
this.RaisePropertyChanged(property, oldValue, newValue);
}
});
}
this.Log().Debug(string.Format(
"Bound {0}.{1} (#{2:x8}) to {3}",
this.GetType().Name,
property.Name,
this.GetHashCode(),
description));
if (binding == null)
{
v.SetLocalValue(value);
}
else
{
v.SetLocalBinding(binding);
}
}
@ -468,7 +406,7 @@ namespace Perspex
this.SetValue((PerspexProperty)property, source);
}
private static IObservable<object> CastToObject<T>(IObservable<T> observable)
private static IObservable<object> BoxObservable<T>(IObservable<T> observable)
{
return Observable.Create<object>(observer =>
{
@ -479,6 +417,46 @@ namespace Perspex
});
}
private static IObservable<object> TryCastToObservable(object value)
{
Type observableType = value.GetType().GetTypeInfo()
.ImplementedInterfaces
.FirstOrDefault(x =>
x.IsConstructedGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>));
IObservable<object> result = null;
if (observableType != null)
{
result = value as IObservable<object>;
if (result == null)
{
MethodInfo cast = typeof(PerspexObject).GetTypeInfo()
.DeclaredMethods
.FirstOrDefault(x => x.Name == "BoxObservable")
.MakeGenericMethod(observableType.GenericTypeArguments[0]);
result = (IObservable<object>)cast.Invoke(null, new[] { value });
}
}
return result;
}
private object GetDefaultValue(PerspexProperty property)
{
if (property.Inherits && this.inheritanceParent != null)
{
return this.inheritanceParent.GetValue(property);
}
else
{
return property.GetDefaultValue(this.GetType());
}
}
/// <summary>
/// Called when a property is changed on the current <see cref="InheritanceParent"/>.
/// </summary>
@ -515,41 +493,41 @@ 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 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
{

181
Perspex/PriorityValue.cs

@ -0,0 +1,181 @@
// -----------------------------------------------------------------------
// <copyright file="PriorityValue.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reactive.Disposables;
internal class PriorityValue : IObservable<Tuple<object, object>>
{
private object localValue = PerspexProperty.UnsetValue;
private IDisposable localBinding;
private object lastValue = PerspexProperty.UnsetValue;
private List<StyleEntry> styles = new List<StyleEntry>();
private List<IObserver<Tuple<object, object>>> observers =
new List<IObserver<Tuple<object, object>>>();
public object LocalValue
{
get
{
return this.localValue;
}
set
{
if (!object.Equals(this.localValue, value))
{
this.localValue = value;
this.Push();
}
}
}
public void ClearLocalBinding()
{
if (this.localBinding != null)
{
this.localBinding.Dispose();
}
}
public void SetLocalValue(object value)
{
if (this.localBinding != null)
{
this.localBinding.Dispose();
}
this.LocalValue = value;
}
public void SetLocalBinding(IObservable<object> binding)
{
if (this.localBinding != null)
{
this.localBinding.Dispose();
}
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);
StyleEntry entry = new StyleEntry(activator, value, this.Push, e => this.styles.Remove(e));
this.styles.Add(entry);
if (this.localValue == PerspexProperty.UnsetValue)
{
this.Push();
}
}
public object GetEffectiveValue()
{
if (this.localValue != PerspexProperty.UnsetValue)
{
return this.localValue;
}
else
{
foreach (StyleEntry style in Enumerable.Reverse(this.styles))
{
if (style.Active)
{
return style.Value;
}
}
}
return PerspexProperty.UnsetValue;
}
public IDisposable Subscribe(IObserver<Tuple<object, object>> observer)
{
Contract.Requires<NullReferenceException>(observer != null);
this.observers.Add(observer);
return Disposable.Create(() => this.observers.Remove(observer));
}
private void Push()
{
object value = this.GetEffectiveValue();
if (!object.Equals(this.lastValue, value))
{
foreach (IObserver<Tuple<object, object>> observer in this.observers)
{
observer.OnNext(Tuple.Create(this.lastValue, value));
}
this.lastValue = value;
}
}
private class StyleEntry
{
private IObservable<bool> activator;
public StyleEntry(object value)
{
this.Active = true;
this.Value = value;
}
public StyleEntry(
IObservable<bool> activator,
object value,
Action activeChanged,
Action<StyleEntry> completed)
{
Contract.Requires<NullReferenceException>(activator != null);
Contract.Requires<NullReferenceException>(activeChanged != null);
this.activator = activator;
this.Value = value;
this.activator.Subscribe(x => this.Active = x, () => completed(this));
}
public bool Active
{
get;
private set;
}
public object Value
{
get;
private set;
}
}
}
}
Loading…
Cancel
Save