19 changed files with 1225 additions and 324 deletions
@ -0,0 +1,201 @@ |
|||||
|
// -----------------------------------------------------------------------
|
||||
|
// <copyright file="PriorityValueTests.cs" company="Steven Kirk">
|
||||
|
// Copyright 2013 MIT Licence. See licence.md for more information.
|
||||
|
// </copyright>
|
||||
|
// -----------------------------------------------------------------------
|
||||
|
|
||||
|
namespace Perspex.UnitTests |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Reactive.Disposables; |
||||
|
using System.Reactive.Linq; |
||||
|
using System.Reactive.Subjects; |
||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting; |
||||
|
|
||||
|
[TestClass] |
||||
|
public class PriorityValueTests |
||||
|
{ |
||||
|
[TestMethod] |
||||
|
public void Initial_Value_Should_Be_UnsetValue() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
|
||||
|
Assert.AreSame(PerspexProperty.UnsetValue, target.Value); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void First_Binding_Sets_Value() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
|
||||
|
target.Add(this.Single("foo"), 0); |
||||
|
|
||||
|
Assert.AreEqual("foo", target.Value); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void Changing_Binding_Should_Set_Value() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
var subject = new BehaviorSubject<string>("foo"); |
||||
|
|
||||
|
target.Add(subject, 0); |
||||
|
Assert.AreEqual("foo", target.Value); |
||||
|
subject.OnNext("bar"); |
||||
|
Assert.AreEqual("bar", target.Value); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void Binding_With_Lower_Priority_Has_Precedence() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
|
||||
|
target.Add(this.Single("foo"), 1); |
||||
|
target.Add(this.Single("bar"), 0); |
||||
|
target.Add(this.Single("baz"), 1); |
||||
|
|
||||
|
Assert.AreEqual("bar", target.Value); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void Later_Binding_With_Same_Priority_Should_Take_Precedence() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
|
||||
|
target.Add(this.Single("foo"), 1); |
||||
|
target.Add(this.Single("bar"), 0); |
||||
|
target.Add(this.Single("baz"), 0); |
||||
|
target.Add(this.Single("qux"), 1); |
||||
|
|
||||
|
Assert.AreEqual("baz", target.Value); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void Changing_Binding_With_Lower_Priority_Should_Set_Not_Value() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
var subject = new BehaviorSubject<string>("bar"); |
||||
|
|
||||
|
target.Add(this.Single("foo"), 0); |
||||
|
target.Add(subject, 1); |
||||
|
Assert.AreEqual("foo", target.Value); |
||||
|
subject.OnNext("baz"); |
||||
|
Assert.AreEqual("foo", target.Value); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void UnsetValue_Should_Fall_Back_To_Next_Binding() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
var subject = new BehaviorSubject<object>("bar"); |
||||
|
|
||||
|
target.Add(subject, 0); |
||||
|
target.Add(this.Single("foo"), 1); |
||||
|
|
||||
|
Assert.AreEqual("bar", target.Value); |
||||
|
|
||||
|
subject.OnNext(PerspexProperty.UnsetValue); |
||||
|
|
||||
|
Assert.AreEqual("foo", target.Value); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void Adding_Value_Should_Call_OnNext() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
bool called = false; |
||||
|
|
||||
|
target.Subscribe(value => called = (value.Item1 == PerspexProperty.UnsetValue && (string)value.Item2 == "foo")); |
||||
|
target.Add(this.Single("foo"), 0); |
||||
|
|
||||
|
Assert.IsTrue(called); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void Changing_Value_Should_Call_OnNext() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
var subject = new BehaviorSubject<object>("foo"); |
||||
|
bool called = false; |
||||
|
|
||||
|
target.Add(subject, 0); |
||||
|
target.Subscribe(value => called = ((string)value.Item1 == "foo" && (string)value.Item2 == "bar")); |
||||
|
subject.OnNext("bar"); |
||||
|
|
||||
|
Assert.IsTrue(called); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void Disposing_A_Binding_Should_Revert_To_Next_Value() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
|
||||
|
target.Add(this.Single("foo"), 0); |
||||
|
var disposable = target.Add(this.Single("bar"), 0); |
||||
|
|
||||
|
Assert.AreEqual("bar", target.Value); |
||||
|
disposable.Dispose(); |
||||
|
Assert.AreEqual("foo", target.Value); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void Disposing_A_Binding_Should_Remove_BindingEntry() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
|
||||
|
target.Add(this.Single("foo"), 0); |
||||
|
var disposable = target.Add(this.Single("bar"), 0); |
||||
|
|
||||
|
Assert.AreEqual(2, target.GetBindings().Count()); |
||||
|
disposable.Dispose(); |
||||
|
Assert.AreEqual(1, target.GetBindings().Count()); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void Completing_A_Binding_Should_Revert_To_Next_Value() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
var subject = new BehaviorSubject<object>("bar"); |
||||
|
|
||||
|
target.Add(this.Single("foo"), 0); |
||||
|
target.Add(subject, 0); |
||||
|
|
||||
|
Assert.AreEqual("bar", target.Value); |
||||
|
subject.OnCompleted(); |
||||
|
Assert.AreEqual("foo", target.Value); |
||||
|
} |
||||
|
|
||||
|
[TestMethod] |
||||
|
public void Completing_A_Binding_Should_Remove_BindingEntry() |
||||
|
{ |
||||
|
var target = new PriorityValue(); |
||||
|
var subject = new BehaviorSubject<object>("bar"); |
||||
|
|
||||
|
target.Add(this.Single("foo"), 0); |
||||
|
target.Add(subject, 0); |
||||
|
|
||||
|
Assert.AreEqual(2, target.GetBindings().Count()); |
||||
|
subject.OnCompleted(); |
||||
|
Assert.AreEqual(1, target.GetBindings().Count()); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an observable that returns a single value but does not complete.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The type of the observable.</typeparam>
|
||||
|
/// <param name="value">The value.</param>
|
||||
|
/// <returns>The observable.</returns>
|
||||
|
/// <remarks>
|
||||
|
/// Seems like there should be something that does this in Rx but I couldn't find it.
|
||||
|
/// </remarks>
|
||||
|
private IObservable<T> Single<T>(T value) |
||||
|
{ |
||||
|
return Observable.Create<T>(observer => |
||||
|
{ |
||||
|
observer.OnNext(value); |
||||
|
return Disposable.Create(() => { }); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
// -----------------------------------------------------------------------
|
||||
|
// <copyright file="ActivatedBinding.cs" company="Tricycle">
|
||||
|
// Copyright 2013 Tricycle. All rights reserved.
|
||||
|
// </copyright>
|
||||
|
// -----------------------------------------------------------------------
|
||||
|
|
||||
|
namespace Perspex.Styling |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Reactive.Disposables; |
||||
|
using System.Reactive.Subjects; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
internal class StyleBinding : IObservable<object>, IObservableDescription |
||||
|
{ |
||||
|
private List<IObserver<object>> observers = new List<IObserver<object>>(); |
||||
|
|
||||
|
public StyleBinding( |
||||
|
StyleActivator activator, |
||||
|
object activatedValue, |
||||
|
string description) |
||||
|
{ |
||||
|
this.Activator = activator; |
||||
|
this.ActivatedValue = activatedValue; |
||||
|
this.Description = description; |
||||
|
|
||||
|
this.Activator.Subscribe( |
||||
|
active => this.OnNext(active ? this.ActivatedValue : PerspexProperty.UnsetValue), |
||||
|
error => this.OnError(error), |
||||
|
() => this.OnCompleted()); |
||||
|
} |
||||
|
|
||||
|
public StyleActivator Activator |
||||
|
{ |
||||
|
get; |
||||
|
private set; |
||||
|
} |
||||
|
|
||||
|
public string Description |
||||
|
{ |
||||
|
get; |
||||
|
private set; |
||||
|
} |
||||
|
|
||||
|
public object ActivatedValue |
||||
|
{ |
||||
|
get; |
||||
|
private set; |
||||
|
} |
||||
|
|
||||
|
public IDisposable Subscribe(IObserver<object> observer) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(observer != null); |
||||
|
this.observers.Add(observer); |
||||
|
return Disposable.Create(() => this.observers.Remove(observer)); |
||||
|
} |
||||
|
|
||||
|
private void OnCompleted() |
||||
|
{ |
||||
|
foreach (var observer in this.observers) |
||||
|
{ |
||||
|
observer.OnCompleted(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnError(Exception error) |
||||
|
{ |
||||
|
foreach (var observer in this.observers) |
||||
|
{ |
||||
|
observer.OnError(error); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnNext(object value) |
||||
|
{ |
||||
|
foreach (var observer in this.observers) |
||||
|
{ |
||||
|
observer.OnNext(value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,555 @@ |
|||||
|
// ----------------------------------------------------------------------- |
||||
|
// <copyright file="PerspexObject.cs" company="Tricycle"> |
||||
|
// Copyright 2013 Tricycle. All rights reserved. |
||||
|
// </copyright> |
||||
|
// ----------------------------------------------------------------------- |
||||
|
|
||||
|
namespace Perspex |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Expressions; |
||||
|
using System.Reactive; |
||||
|
using System.Reactive.Disposables; |
||||
|
using System.Reactive.Linq; |
||||
|
using System.Reflection; |
||||
|
using Splat; |
||||
|
|
||||
|
/// <summary> |
||||
|
/// The priority of a binding. |
||||
|
/// </summary> |
||||
|
public enum BindingPriority |
||||
|
{ |
||||
|
/// <summary> |
||||
|
/// A local value. |
||||
|
/// </summary> |
||||
|
LocalValue, |
||||
|
|
||||
|
/// <summary> |
||||
|
/// A style binding. |
||||
|
/// </summary> |
||||
|
Style, |
||||
|
|
||||
|
/// <summary> |
||||
|
/// A template binding. |
||||
|
/// </summary> |
||||
|
Template, |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// An object with <see cref="PerspexProperty"/> support. |
||||
|
/// </summary> |
||||
|
/// <remarks> |
||||
|
/// This class is analogous to DependencyObject in WPF. |
||||
|
/// </remarks> |
||||
|
public class PerspexObject : IEnableLogger |
||||
|
{ |
||||
|
/// <summary> |
||||
|
/// The registered properties by type. |
||||
|
/// </summary> |
||||
|
private static Dictionary<Type, List<PerspexProperty>> registered = |
||||
|
new Dictionary<Type, List<PerspexProperty>>(); |
||||
|
|
||||
|
/// <summary> |
||||
|
/// The parent object that inherited values are inherited from. |
||||
|
/// </summary> |
||||
|
private PerspexObject inheritanceParent; |
||||
|
|
||||
|
/// <summary> |
||||
|
/// The set values/bindings on this object. |
||||
|
/// </summary> |
||||
|
private Dictionary<PerspexProperty, PriorityValue> values = |
||||
|
new Dictionary<PerspexProperty, PriorityValue>(); |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Raised when a <see cref="PerspexProperty"/> value changes on this object/ |
||||
|
/// </summary> |
||||
|
public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged; |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Gets or sets the parent object that inherited <see cref="PerspexProperty"/> values |
||||
|
/// are inherited from. |
||||
|
/// </summary> |
||||
|
protected PerspexObject InheritanceParent |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return this.inheritanceParent; |
||||
|
} |
||||
|
|
||||
|
set |
||||
|
{ |
||||
|
if (this.inheritanceParent != value) |
||||
|
{ |
||||
|
if (this.inheritanceParent != null) |
||||
|
{ |
||||
|
this.inheritanceParent.PropertyChanged -= this.ParentPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
var inherited = (from property in GetProperties(this.GetType()) |
||||
|
where property.Inherits |
||||
|
select new |
||||
|
{ |
||||
|
Property = property, |
||||
|
Value = this.GetValue(property), |
||||
|
}).ToList(); |
||||
|
|
||||
|
this.inheritanceParent = value; |
||||
|
|
||||
|
foreach (var i in inherited) |
||||
|
{ |
||||
|
object newValue = this.GetValue(i.Property); |
||||
|
|
||||
|
if (!object.Equals(i.Value, newValue)) |
||||
|
{ |
||||
|
this.RaisePropertyChanged(i.Property, i.Value, newValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (this.inheritanceParent != null) |
||||
|
{ |
||||
|
this.inheritanceParent.PropertyChanged += this.ParentPropertyChanged; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Defers property change notifications until <see cref="EndDeferChanges"/> is called. |
||||
|
/// </summary> |
||||
|
public void BeginDeferChanges() |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
//foreach (PriorityValue v in this.values.Values) |
||||
|
//{ |
||||
|
// v.BeginDeferChanges(); |
||||
|
//} |
||||
|
|
||||
|
//this.Log().Debug(string.Format( |
||||
|
// "Defer style changes on {0} (#{1:x8})", |
||||
|
// this.GetType().Name, |
||||
|
// this.GetHashCode())); |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Ends the defer of property change notifications initiated by a previous call |
||||
|
/// to <see cref="BeginDeferChanges"/>. |
||||
|
/// </summary> |
||||
|
public void EndDeferChanges() |
||||
|
{ |
||||
|
//foreach (PriorityValue v in this.values.Values) |
||||
|
//{ |
||||
|
// v.EndDeferChanges(); |
||||
|
//} |
||||
|
|
||||
|
//this.Log().Debug(string.Format( |
||||
|
// "End defer style changes on {0} (#{1:x8})", |
||||
|
// this.GetType().Name, |
||||
|
// this.GetHashCode())); |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Gets all <see cref="PerspexProperty"/>s registered on a type. |
||||
|
/// </summary> |
||||
|
/// <param name="type">The type.</param> |
||||
|
/// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns> |
||||
|
public static IEnumerable<PerspexProperty> GetProperties(Type type) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(type != null); |
||||
|
|
||||
|
TypeInfo i = type.GetTypeInfo(); |
||||
|
|
||||
|
while (type != null) |
||||
|
{ |
||||
|
List<PerspexProperty> list; |
||||
|
|
||||
|
if (registered.TryGetValue(type, out list)) |
||||
|
{ |
||||
|
foreach (PerspexProperty p in list) |
||||
|
{ |
||||
|
yield return p; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
type = type.GetTypeInfo().BaseType; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Registers a <see cref="PerspexProperty"/> on a type. |
||||
|
/// </summary> |
||||
|
/// <param name="type">The type.</param> |
||||
|
/// <param name="property">The property.</param> |
||||
|
/// <remarks> |
||||
|
/// You won't usually want to call this method directly, instead use the |
||||
|
/// <see cref="PerspexProperty.Register"/> method. |
||||
|
/// </remarks> |
||||
|
public static void Register(Type type, PerspexProperty property) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(type != null); |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
List<PerspexProperty> list; |
||||
|
|
||||
|
if (!registered.TryGetValue(type, out list)) |
||||
|
{ |
||||
|
list = new List<PerspexProperty>(); |
||||
|
registered.Add(type, list); |
||||
|
} |
||||
|
|
||||
|
if (!list.Contains(property)) |
||||
|
{ |
||||
|
list.Add(property); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Clears a binding on a <see cref="PerspexProperty"/>, leaving the last bound value in |
||||
|
/// place. |
||||
|
/// </summary> |
||||
|
/// <param name="property">The property.</param> |
||||
|
public void ClearBinding(PerspexProperty property) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
//Contract.Requires<NullReferenceException>(property != null); |
||||
|
//PriorityValue value; |
||||
|
|
||||
|
//if (this.values.TryGetValue(property, out value)) |
||||
|
//{ |
||||
|
// value.ClearLocalBinding(); |
||||
|
|
||||
|
// this.Log().Debug(string.Format( |
||||
|
// "Cleared binding on {0}.{1} (#{2:x8})", |
||||
|
// this.GetType().Name, |
||||
|
// property.Name, |
||||
|
// this.GetHashCode())); |
||||
|
//} |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Clears a <see cref="PerspexProperty"/> value, including its binding. |
||||
|
/// </summary> |
||||
|
/// <param name="property">The property.</param> |
||||
|
public void ClearValue(PerspexProperty property) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
this.SetValue(property, PerspexProperty.UnsetValue); |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Gets an observable for a <see cref="PerspexProperty"/>. |
||||
|
/// </summary> |
||||
|
/// <param name="property"></param> |
||||
|
/// <returns></returns> |
||||
|
public IObservable<object> GetObservable(PerspexProperty property) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
return Observable.Create<object>(observer => |
||||
|
{ |
||||
|
EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) => |
||||
|
{ |
||||
|
if (e.Property == property) |
||||
|
{ |
||||
|
observer.OnNext(e.NewValue); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
this.PropertyChanged += handler; |
||||
|
observer.OnNext(this.GetValue(property)); |
||||
|
|
||||
|
return () => |
||||
|
{ |
||||
|
this.PropertyChanged -= handler; |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Gets an observable for a <see cref="PerspexProperty"/>. |
||||
|
/// </summary> |
||||
|
/// <typeparam name="T"></typeparam> |
||||
|
/// <param name="property"></param> |
||||
|
/// <returns></returns> |
||||
|
public IObservable<T> GetObservable<T>(PerspexProperty<T> property) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
return this.GetObservable((PerspexProperty)property).Cast<T>(); |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Gets an observable for a <see cref="ReadOnlyPerspexProperty"/>. |
||||
|
/// </summary> |
||||
|
/// <typeparam name="T"></typeparam> |
||||
|
/// <param name="property"></param> |
||||
|
/// <returns></returns> |
||||
|
public IObservable<T> GetObservable<T>(ReadOnlyPerspexProperty<T> property) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
return this.GetObservable((PerspexProperty<T>)property.Property); |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Gets an observable for a <see cref="PerspexProperty"/>. |
||||
|
/// </summary> |
||||
|
/// <typeparam name="T"></typeparam> |
||||
|
/// <param name="property"></param> |
||||
|
/// <returns></returns> |
||||
|
public IObservable<Tuple<T, T>> GetObservableWithHistory<T>(PerspexProperty<T> property) |
||||
|
{ |
||||
|
return Observable.Create<Tuple<T, T>>(observer => |
||||
|
{ |
||||
|
EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) => |
||||
|
{ |
||||
|
if (e.Property == property) |
||||
|
{ |
||||
|
observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue)); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
this.PropertyChanged += handler; |
||||
|
|
||||
|
return () => |
||||
|
{ |
||||
|
this.PropertyChanged -= handler; |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Gets a <see cref="PerspexProperty"/> value. |
||||
|
/// </summary> |
||||
|
/// <param name="property">The property.</param> |
||||
|
/// <returns>The value.</returns> |
||||
|
public object GetValue(PerspexProperty property) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
object result; |
||||
|
|
||||
|
PriorityValue value; |
||||
|
|
||||
|
if (this.values.TryGetValue(property, out value)) |
||||
|
{ |
||||
|
result = value.Value; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
result = PerspexProperty.UnsetValue; |
||||
|
} |
||||
|
|
||||
|
if (result == PerspexProperty.UnsetValue) |
||||
|
{ |
||||
|
result = this.GetDefaultValue(property); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Gets a <see cref="PerspexProperty"/> value. |
||||
|
/// </summary> |
||||
|
/// <param name="property">The property.</param> |
||||
|
/// <returns>The value.</returns> |
||||
|
public T GetValue<T>(PerspexProperty<T> property) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
return (T)this.GetValue((PerspexProperty)property); |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Gets a <see cref="ReadOnlyPerspexProperty"/> value. |
||||
|
/// </summary> |
||||
|
/// <typeparam name="T">The type of the property.</typeparam> |
||||
|
/// <param name="property">The property.</param> |
||||
|
/// <returns>The value.</returns> |
||||
|
public T GetValue<T>(ReadOnlyPerspexProperty<T> property) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
return (T)this.GetValue(property.Property); |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Checks whether a <see cref="PerspexProperty"/> is set on this object. |
||||
|
/// </summary> |
||||
|
/// <param name="property"></param> |
||||
|
/// <returns></returns> |
||||
|
public bool IsSet(PerspexProperty property) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
return this.values.ContainsKey(property); |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Sets a <see cref="PerspexProperty"/> value. |
||||
|
/// </summary> |
||||
|
/// <param name="property">The property.</param> |
||||
|
/// <param name="value">The value.</param> |
||||
|
public void SetValue(PerspexProperty property, object value) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
PriorityValue v; |
||||
|
|
||||
|
if (!this.values.TryGetValue(property, out v)) |
||||
|
{ |
||||
|
if (value == PerspexProperty.UnsetValue) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
v = this.CreatePriorityValue(property); |
||||
|
this.values.Add(property, v); |
||||
|
} |
||||
|
|
||||
|
throw new NotImplementedException(); |
||||
|
//v.SetLocalValue(value); |
||||
|
} |
||||
|
|
||||
|
/// <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> |
||||
|
/// <param name="priority">The priority of the binding.</param> |
||||
|
public void Bind( |
||||
|
PerspexProperty property, |
||||
|
IObservable<object> source, |
||||
|
BindingPriority priority = BindingPriority.LocalValue) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
|
||||
|
this.Log().Debug(string.Format( |
||||
|
"Bound value of {0}.{1} (#{2:x8})", |
||||
|
this.GetType().Name, |
||||
|
property.Name, |
||||
|
this.GetHashCode())); |
||||
|
} |
||||
|
|
||||
|
/// <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> |
||||
|
/// <param name="priority">The priority of the binding.</param> |
||||
|
public void Bind<T>( |
||||
|
PerspexProperty<T> property, |
||||
|
IObservable<T> source, |
||||
|
BindingPriority priority = BindingPriority.LocalValue) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
this.Bind((PerspexProperty)property, (IObservable<object>)source, priority); |
||||
|
} |
||||
|
|
||||
|
private static IObservable<object> BoxObservable<T>(IObservable<T> observable) |
||||
|
{ |
||||
|
return Observable.Create<object>(observer => |
||||
|
{ |
||||
|
return observable.Subscribe(value => |
||||
|
{ |
||||
|
observer.OnNext(value); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
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> |
||||
|
/// <param name="sender">The event sender.</param> |
||||
|
/// <param name="e">The event args.</param> |
||||
|
/// <remarks> |
||||
|
/// Checks for changes in an inherited property value. |
||||
|
/// </remarks> |
||||
|
private void ParentPropertyChanged(object sender, PerspexPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
Contract.Requires<ArgumentNullException>(e != null); |
||||
|
|
||||
|
if (e.Property.Inherits && !this.IsSet(e.Property)) |
||||
|
{ |
||||
|
this.RaisePropertyChanged(e.Property, e.OldValue, e.NewValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary> |
||||
|
/// Raises the <see cref="PropertyChanged"/> event. |
||||
|
/// </summary> |
||||
|
/// <param name="property">The property that has changed.</param> |
||||
|
/// <param name="oldValue">The old property value.</param> |
||||
|
/// <param name="newValue">The new property value.</param> |
||||
|
private void RaisePropertyChanged(PerspexProperty property, object oldValue, object newValue) |
||||
|
{ |
||||
|
Contract.Requires<NullReferenceException>(property != null); |
||||
|
|
||||
|
if (this.PropertyChanged != null) |
||||
|
{ |
||||
|
this.PropertyChanged( |
||||
|
this, |
||||
|
new PerspexPropertyChangedEventArgs(property, oldValue, newValue)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue