Browse Source

Started revamping binding system.

To allow prioritized bindings.
pull/4/head
Steven Kirk 13 years ago
parent
commit
d1491d9ceb
  1. 5
      Perspex.UnitTests/Controls/ContentControlTests.cs
  2. 1
      Perspex.UnitTests/Perspex.UnitTests.csproj
  3. 106
      Perspex.UnitTests/PerspexObjectTests.cs
  4. 201
      Perspex.UnitTests/PriorityValueTEsts.cs
  5. 16
      Perspex.UnitTests/Styling/ActivatorTests.cs
  6. 3
      Perspex.UnitTests/Styling/TestControlBase.cs
  7. 5
      Perspex.UnitTests/Styling/TestTemplatedControl.cs
  8. 5
      Perspex.Windows/Window.cs
  9. 1
      Perspex/Perspex.csproj
  10. 222
      Perspex/PerspexObject.cs
  11. 306
      Perspex/PriorityValue.cs
  12. 17
      Perspex/Styling/IStyleable.cs
  13. 2
      Perspex/Styling/Selector.cs
  14. 1
      Perspex/Styling/Selectors.cs
  15. 3
      Perspex/Styling/Style.cs
  16. 10
      Perspex/Styling/StyleActivator.cs
  17. 85
      Perspex/Styling/StyleBinding.cs
  18. 5
      Perspex/Themes/Default/ButtonStyle.cs
  19. 555
      Perspex/tkfbyosx.jgd

5
Perspex.UnitTests/Controls/ContentControlTests.cs

@ -71,7 +71,10 @@ namespace Perspex.UnitTests.Controls
Border border = new Border();
border.Background = new Perspex.Media.SolidColorBrush(0xffffffff);
ContentPresenter contentPresenter = new ContentPresenter();
contentPresenter.SetValue(ContentPresenter.ContentProperty, parent.GetObservable(ContentControl.ContentProperty));
contentPresenter.Bind(
ContentPresenter.ContentProperty,
parent.GetObservable(ContentControl.ContentProperty),
BindingPriority.Template);
border.Content = contentPresenter;
return border;
});

1
Perspex.UnitTests/Perspex.UnitTests.csproj

@ -71,6 +71,7 @@
<Compile Include="Controls\ContentControlTests.cs" />
<Compile Include="Controls\ControlTests.cs" />
<Compile Include="Controls\TemplatedControlTests.cs" />
<Compile Include="PriorityValueTests.cs" />
<Compile Include="PerspexPropertyTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PerspexObjectTests.cs" />

106
Perspex.UnitTests/PerspexObjectTests.cs

@ -249,7 +249,7 @@ namespace Perspex.UnitTests
Class1 source = new Class1();
source.SetValue(Class1.FooProperty, "initial");
target.SetValue(Class1.FooProperty, source.GetObservable(Class1.FooProperty));
target.Bind(Class1.FooProperty, source.GetObservable(Class1.FooProperty));
Assert.AreEqual("initial", target.GetValue(Class1.FooProperty));
}
@ -273,7 +273,7 @@ namespace Perspex.UnitTests
Class1 source = new Class1();
source.SetValue(Class1.FooProperty, "initial");
target.SetValue(Class1.FooProperty, source.GetObservable(Class1.FooProperty));
target.Bind(Class1.FooProperty, source.GetObservable(Class1.FooProperty));
source.SetValue(Class1.FooProperty, "subsequent");
Assert.AreEqual("subsequent", target.GetValue(Class1.FooProperty));
@ -286,7 +286,7 @@ namespace Perspex.UnitTests
Class1 source = new Class1();
source.SetValue(Class1.FooProperty, "initial");
target.SetValue(Class1.FooProperty, source.GetObservable(Class1.FooProperty));
target.Bind(Class1.FooProperty, source.GetObservable(Class1.FooProperty));
target.ClearValue(Class1.FooProperty);
source.SetValue(Class1.FooProperty, "newvalue");
@ -300,7 +300,7 @@ namespace Perspex.UnitTests
Class1 source = new Class1();
source.SetValue(Class1.FooProperty, "initial");
target.SetValue(Class1.FooProperty, source.GetObservable(Class1.FooProperty));
target.Bind(Class1.FooProperty, source.GetObservable(Class1.FooProperty));
target.SetValue(Class1.FooProperty, "reset");
source.SetValue(Class1.FooProperty, "newvalue");
@ -321,87 +321,93 @@ namespace Perspex.UnitTests
[TestMethod]
public void StyleBinding_Overrides_Default_Value()
{
Class1 target = new Class1();
throw new NotImplementedException();
//Class1 target = new Class1();
target.SetValue(Class1.FooProperty, "stylevalue", Observable.Return(true));
//target.Bind(Class1.FooProperty, "stylevalue", Observable.Return(true));
Assert.AreEqual("stylevalue", target.GetValue(Class1.FooProperty));
//Assert.AreEqual("stylevalue", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void StyleBinding_Doesnt_Override_Local_Value()
{
Class1 target = new Class1();
throw new NotImplementedException();
//Class1 target = new Class1();
target.SetValue(Class1.FooProperty, "newvalue");
target.SetValue(Class1.FooProperty, "stylevalue", Observable.Return(true));
//target.SetValue(Class1.FooProperty, "newvalue");
//target.SetValue(Class1.FooProperty, "stylevalue", Observable.Return(true));
Assert.AreEqual("newvalue", target.GetValue(Class1.FooProperty));
//Assert.AreEqual("newvalue", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void StyleBinding_Deactivated_Doesnt_Override_Default_Value()
{
Class1 target = new Class1();
throw new NotImplementedException();
//Class1 target = new Class1();
target.SetValue(Class1.FooProperty, "stylevalue", Observable.Return(false));
//target.SetValue(Class1.FooProperty, "stylevalue", Observable.Return(false));
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
//Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
}
[TestMethod]
public void StyleBinding_Toggles_On_Activation()
{
Class1 target = new Class1();
throw new NotImplementedException();
//Class1 target = new Class1();
Subject<bool> source = new Subject<bool>();
target.SetValue(Class1.FooProperty, "stylevalue", source);
//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));
//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();
throw new NotImplementedException();
//Class1 target = new Class1();
Subject<bool> source = new Subject<bool>();
target.SetValue(Class1.FooProperty, "stylevalue", source);
//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));
//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));
throw new NotImplementedException();
//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

201
Perspex.UnitTests/PriorityValueTEsts.cs

@ -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(() => { });
});
}
}
}

16
Perspex.UnitTests/Styling/ActivatorTests.cs

@ -18,7 +18,7 @@
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 Activator(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 Activator(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 Activator(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 Activator(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 Activator(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 Activator(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 Activator(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 Activator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>();
target.Subscribe(result);

3
Perspex.UnitTests/Styling/TestControlBase.cs

@ -30,8 +30,9 @@ namespace Perspex.UnitTests.Styling
set;
}
public virtual void SetValue(PerspexProperty property, object value, IObservable<bool> activator)
public void Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
}
}

5
Perspex.UnitTests/Styling/TestTemplatedControl.cs

@ -38,6 +38,9 @@ namespace Perspex.UnitTests.Styling
throw new NotImplementedException();
}
public abstract void SetValue(PerspexProperty property, object value, System.IObservable<bool> activator);
public void Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
}
}

5
Perspex.Windows/Window.cs

@ -85,7 +85,10 @@ namespace Perspex.Windows
Border border = new Border();
border.Background = new Perspex.Media.SolidColorBrush(0xffffffff);
ContentPresenter contentPresenter = new ContentPresenter();
contentPresenter.SetValue(ContentPresenter.ContentProperty, this.GetObservable(Window.ContentProperty));
contentPresenter.Bind(
ContentPresenter.ContentProperty,
this.GetObservable(Window.ContentProperty),
BindingPriority.Style);
border.Content = contentPresenter;
return border;
}

1
Perspex/Perspex.csproj

@ -69,6 +69,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Application.cs" />
<Compile Include="Styling\StyleBinding.cs" />
<Compile Include="Classes.cs" />
<Compile Include="Contract.cs" />
<Compile Include="Controls\Border.cs" />

222
Perspex/PerspexObject.cs

@ -16,6 +16,27 @@ namespace Perspex
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>
@ -99,15 +120,16 @@ namespace Perspex
/// </summary>
public void BeginDeferChanges()
{
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()));
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>
@ -116,15 +138,16 @@ namespace Perspex
/// </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()));
throw new NotImplementedException();
//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>
@ -189,19 +212,20 @@ namespace Perspex
/// <param name="property">The property.</param>
public void ClearBinding(PerspexProperty property)
{
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()));
}
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>
@ -312,7 +336,7 @@ namespace Perspex
if (this.values.TryGetValue(property, out value))
{
result = value.GetEffectiveValue();
result = value.Value;
}
else
{
@ -364,32 +388,6 @@ 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>
@ -399,8 +397,6 @@ namespace Perspex
{
Contract.Requires<NullReferenceException>(property != null);
IObservable<object> binding = TryCastToObservable(value);
PriorityValue v;
if (!this.values.TryGetValue(property, out v))
@ -414,57 +410,59 @@ namespace Perspex
this.values.Add(property, v);
}
if (binding == null)
{
v.SetLocalValue(value);
}
else
{
v.SetLocalBinding(binding);
this.Log().Debug(string.Format(
"Bound value of {0}.{1} (#{2:x8})",
this.GetType().Name,
property.Name,
this.GetHashCode()));
}
throw new NotImplementedException();
//v.SetLocalValue(value);
}
/// <summary>
/// Binds a <see cref="PerspexProperty"/> to a style.
/// 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 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)
/// <param name="value">The value.</param>
public void SetValue<T>(PerspexProperty<T> property, T value)
{
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<NullReferenceException>(activator != null);
PriorityValue v;
if (!this.values.TryGetValue(property, out v))
{
v = this.CreatePriorityValue(property);
this.values.Add(property, v);
}
v.AddStyle(activator, value);
this.SetValue((PerspexProperty)property, value);
}
IObservableDescription description = activator as IObservableDescription;
string bindingDescription = description != null ? description.Description : "[unnamed]";
/// <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}) to style '{3}'",
"Bound value of {0}.{1} (#{2:x8})",
this.GetType().Name,
property.Name,
this.GetHashCode(),
bindingDescription));
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)
@ -478,34 +476,6 @@ 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 PriorityValue CreatePriorityValue(PerspexProperty property)
{
PriorityValue result = new PriorityValue();

306
Perspex/PriorityValue.cs

@ -11,184 +11,262 @@ namespace Perspex
using System.Linq;
using System.Reactive.Disposables;
internal class PriorityValue : IObservable<Tuple<object, object>>
/// <summary>
/// Maintains a list of prioritised bindings together with a current value.
/// </summary>
public 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>();
/// <summary>
/// The currently registered binding entries.
/// </summary>
private LinkedList<BindingEntry> bindings = new LinkedList<BindingEntry>();
/// <summary>
/// The current observers.
/// </summary>
private List<IObserver<Tuple<object, object>>> observers =
new List<IObserver<Tuple<object, object>>>();
private int defer;
/// <summary>
/// The current value.
/// </summary>
private object value;
private bool dirty;
/// <summary>
/// The priority of the binding that is currently active.
/// </summary>
private int valuePriority = int.MaxValue;
public object LocalValue
/// <summary>
/// Initializes a new instance of the <see cref="PriorityValue"/> class.
/// </summary>
public PriorityValue()
{
get
{
return this.localValue;
}
set
{
if (!object.Equals(this.localValue, value))
{
this.localValue = value;
this.Push();
}
}
this.value = PerspexProperty.UnsetValue;
}
public void ClearLocalBinding()
/// <summary>
/// Gets the currently active bindings on this object.
/// </summary>
/// <returns>An enumerable collection of bindings.</returns>
public IEnumerable<BindingEntry> GetBindings()
{
if (this.localBinding != null)
{
this.localBinding.Dispose();
}
return this.bindings;
}
public void SetLocalValue(object value)
/// <summary>
/// Gets the current value.
/// </summary>
public object Value
{
if (this.localBinding != null)
{
this.localBinding.Dispose();
}
this.LocalValue = value;
get { return this.value; }
}
public void SetLocalBinding(IObservable<object> binding)
/// <summary>
/// Adds a new binding.
/// </summary>
/// <param name="binding">The binding.</param>
/// <param name="priority">The binding priority.</param>
/// <returns>
/// A disposable that will remove the binding.
/// </returns>
public IDisposable Add(IObservable<object> binding, int priority)
{
if (this.localBinding != null)
{
this.localBinding.Dispose();
}
BindingEntry entry = new BindingEntry();
LinkedListNode<BindingEntry> insert = this.bindings.First;
this.localBinding = binding.Subscribe(value => this.LocalValue = value);
}
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)
while (insert != null && insert.Value.Priority < priority)
{
this.Push();
insert = insert.Next;
}
}
public object GetEffectiveValue()
{
if (this.localValue != PerspexProperty.UnsetValue)
if (insert == null)
{
return this.localValue;
this.bindings.AddLast(entry);
}
else
{
foreach (StyleEntry style in Enumerable.Reverse(this.styles))
{
if (style.Active)
{
return style.Value;
}
}
this.bindings.AddBefore(insert, entry);
}
return PerspexProperty.UnsetValue;
entry.Start(binding, priority, this.EntryChanged, this.EntryCompleted);
return Disposable.Create(() =>
{
entry.Dispose();
this.bindings.Remove(entry);
this.UpdateValue();
});
}
/// <summary>
/// Notifies the provider that an observer is to receive notifications.
/// </summary>
/// <param name="observer">The object that is to receive notifications.</param>
/// <returns>
/// A reference to an interface that allows observers to stop receiving notifications
/// before the provider has finished sending them.
/// </returns>
public IDisposable Subscribe(IObserver<Tuple<object, object>> observer)
{
Contract.Requires<NullReferenceException>(observer != null);
this.observers.Add(observer);
return Disposable.Create(() => this.observers.Remove(observer));
}
public void BeginDeferChanges()
/// <summary>
/// Called when an binding's value changes.
/// </summary>
/// <param name="changed">The changed entry.</param>
private void EntryChanged(BindingEntry changed)
{
if (this.defer++ == 0)
if (changed.Priority <= this.valuePriority)
{
this.dirty = false;
this.UpdateValue();
}
}
public void EndDeferChanges()
/// <summary>
/// Called when an binding completes.
/// </summary>
/// <param name="changed">The completed entry.</param>
private void EntryCompleted(BindingEntry entry)
{
if (this.defer > 0 && --this.defer == 0 && dirty)
{
this.Push();
}
entry.Dispose();
this.bindings.Remove(entry);
this.UpdateValue();
}
private void Push()
/// <summary>
/// Notifies all observers of a change in value.
/// </summary>
/// <param name="value">The old and new values.</param>
private void OnNext(Tuple<object, object> value)
{
if (defer == 0)
foreach (var observer in this.observers)
{
object value = this.GetEffectiveValue();
observer.OnNext(value);
}
}
if (!object.Equals(this.lastValue, value))
{
foreach (IObserver<Tuple<object, object>> observer in this.observers)
{
observer.OnNext(Tuple.Create(this.lastValue, value));
}
/// <summary>
/// Sets the current value and notifies all observers.
/// </summary>
/// <param name="value">The new value.</param>
/// <param name="priority">The priority of the binding which produced the value.</param>
private void SetValue(object value, int priority)
{
object old = this.value;
this.lastValue = value;
}
this.valuePriority = priority;
if (!EqualityComparer<object>.Default.Equals(old, value))
{
this.value = value;
this.OnNext(Tuple.Create(old, value));
}
else
}
/// <summary>
/// Updates the current value.
/// </summary>
private void UpdateValue()
{
foreach (BindingEntry entry in this.bindings)
{
dirty = true;
if (entry.Value != PerspexProperty.UnsetValue)
{
this.SetValue(entry.Value, entry.Priority);
return;
}
}
}
private class StyleEntry
/// <summary>
/// A registered binding.
/// </summary>
public class BindingEntry : IDisposable
{
private IObservable<bool> activator;
/// <summary>
/// The binding subscription.
/// </summary>
private IDisposable subscription;
/// <summary>
/// The priority of the binding.
/// </summary>
public int Priority
{
get;
private set;
}
public StyleEntry(
IObservable<bool> activator,
object value,
Action activeChanged,
Action<StyleEntry> completed)
/// <summary>
/// The current value of the binding.
/// </summary>
public object Value
{
Contract.Requires<NullReferenceException>(activator != null);
Contract.Requires<NullReferenceException>(activeChanged != null);
get;
private set;
}
this.activator = activator;
this.Value = value;
/// <summary>
/// Starts listening to the specified binding.
/// </summary>
/// <param name="binding">The binding.</param>
/// <param name="priority">The binding priority.</param>
/// <param name="changed">Called when the binding changes.</param>
/// <param name="completed">Called when the binding completes.</param>
public void Start(
IObservable<object> binding,
int priority,
Action<BindingEntry> changed,
Action<BindingEntry> completed)
{
Contract.Requires<ArgumentNullException>(binding != null);
Contract.Requires<ArgumentNullException>(changed != null);
Contract.Requires<ArgumentNullException>(completed != null);
this.activator.Subscribe(x =>
if (this.subscription != null)
{
this.Active = x;
activeChanged();
},
() => completed(this));
}
throw new Exception("PriorityValue.Entry.Start() called more than once.");
}
this.Priority = priority;
this.Value = PerspexProperty.UnsetValue;
public bool Active
{
get;
private set;
if (binding is IObservableDescription)
{
this.Description = ((IObservableDescription)binding).Description;
}
this.subscription = binding.Subscribe(
value =>
{
this.Value = value;
changed(this);
},
() => completed(this));
}
public object Value
/// <summary>
/// Gets a description of the binding.
/// </summary>
public string Description
{
get;
private set;
}
/// <summary>
/// Ends the binding subscription.
/// </summary>
public void Dispose()
{
if (this.subscription != null)
{
this.subscription.Dispose();
}
}
}
}
}

17
Perspex/Styling/IStyleable.cs

@ -30,16 +30,15 @@ namespace Perspex.Styling
ITemplatedControl TemplatedParent { get; }
/// <summary>
/// Binds a <see cref="PerspexProperty"/> to a style.
/// 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="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>
void SetValue(PerspexProperty property, object value, IObservable<bool> activator);
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
void Bind(
PerspexProperty property,
IObservable<object> source,
BindingPriority priority = BindingPriority.LocalValue);
}
}

2
Perspex/Styling/Selector.cs

@ -79,7 +79,7 @@ namespace Perspex.Styling
selector = selector.MovePrevious();
}
return new StyleActivator(inputs, this.ToString());
return new StyleActivator(inputs);
}
public override string ToString()

1
Perspex/Styling/Selectors.cs

@ -49,7 +49,6 @@ namespace Perspex.Styling
return new StyleActivator(
descendentMatches,
"Descendent",
ActivatorMode.Or);
},
};

3
Perspex/Styling/Style.cs

@ -47,7 +47,8 @@ namespace Perspex.Styling
{
foreach (Setter setter in this.Setters)
{
control.SetValue(setter.Property, setter.Value, activator);
StyleBinding binding = new StyleBinding(activator, setter.Value, description);
control.Bind(setter.Property, binding, BindingPriority.Style);
}
}
}

10
Perspex/Styling/StyleActivator.cs

@ -17,7 +17,7 @@ namespace Perspex.Styling
Or,
}
public class StyleActivator : IObservable<bool>, IObservableDescription
public class StyleActivator : IObservable<bool>
{
ActivatorMode mode;
@ -29,12 +29,10 @@ namespace Perspex.Styling
public StyleActivator(
IEnumerable<IObservable<bool>> inputs,
string description,
ActivatorMode mode = ActivatorMode.And)
{
int i = 0;
this.Description = description;
this.mode = mode;
foreach (IObservable<bool> input in inputs)
@ -58,12 +56,6 @@ namespace Perspex.Styling
private set;
}
public string Description
{
get;
private set;
}
public bool HasCompleted
{
get;

85
Perspex/Styling/StyleBinding.cs

@ -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);
}
}
}
}

5
Perspex/Themes/Default/ButtonStyle.cs

@ -57,7 +57,10 @@ namespace Perspex.Themes.Default
border.Id = "border";
border.Padding = new Thickness(3);
ContentPresenter contentPresenter = new ContentPresenter();
contentPresenter.SetValue(ContentPresenter.ContentProperty, control.GetObservable(Button.ContentProperty));
contentPresenter.Bind(
ContentPresenter.ContentProperty,
control.GetObservable(Button.ContentProperty),
BindingPriority.Template);
border.Content = contentPresenter;
return border;
}

555
Perspex/tkfbyosx.jgd

@ -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…
Cancel
Save