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 border = new Border();
border.Background = new Perspex.Media.SolidColorBrush(0xffffffff); border.Background = new Perspex.Media.SolidColorBrush(0xffffffff);
ContentPresenter contentPresenter = new ContentPresenter(); 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; border.Content = contentPresenter;
return border; return border;
}); });

1
Perspex.UnitTests/Perspex.UnitTests.csproj

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

106
Perspex.UnitTests/PerspexObjectTests.cs

@ -249,7 +249,7 @@ namespace Perspex.UnitTests
Class1 source = new Class1(); Class1 source = new Class1();
source.SetValue(Class1.FooProperty, "initial"); 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)); Assert.AreEqual("initial", target.GetValue(Class1.FooProperty));
} }
@ -273,7 +273,7 @@ namespace Perspex.UnitTests
Class1 source = new Class1(); Class1 source = new Class1();
source.SetValue(Class1.FooProperty, "initial"); 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"); source.SetValue(Class1.FooProperty, "subsequent");
Assert.AreEqual("subsequent", target.GetValue(Class1.FooProperty)); Assert.AreEqual("subsequent", target.GetValue(Class1.FooProperty));
@ -286,7 +286,7 @@ namespace Perspex.UnitTests
Class1 source = new Class1(); Class1 source = new Class1();
source.SetValue(Class1.FooProperty, "initial"); 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); target.ClearValue(Class1.FooProperty);
source.SetValue(Class1.FooProperty, "newvalue"); source.SetValue(Class1.FooProperty, "newvalue");
@ -300,7 +300,7 @@ namespace Perspex.UnitTests
Class1 source = new Class1(); Class1 source = new Class1();
source.SetValue(Class1.FooProperty, "initial"); 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"); target.SetValue(Class1.FooProperty, "reset");
source.SetValue(Class1.FooProperty, "newvalue"); source.SetValue(Class1.FooProperty, "newvalue");
@ -321,87 +321,93 @@ namespace Perspex.UnitTests
[TestMethod] [TestMethod]
public void StyleBinding_Overrides_Default_Value() 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] [TestMethod]
public void StyleBinding_Doesnt_Override_Local_Value() 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, "newvalue");
target.SetValue(Class1.FooProperty, "stylevalue", Observable.Return(true)); //target.SetValue(Class1.FooProperty, "stylevalue", Observable.Return(true));
Assert.AreEqual("newvalue", target.GetValue(Class1.FooProperty)); //Assert.AreEqual("newvalue", target.GetValue(Class1.FooProperty));
} }
[TestMethod] [TestMethod]
public void StyleBinding_Deactivated_Doesnt_Override_Default_Value() 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] [TestMethod]
public void StyleBinding_Toggles_On_Activation() public void StyleBinding_Toggles_On_Activation()
{ {
Class1 target = new Class1(); throw new NotImplementedException();
//Class1 target = new Class1();
Subject<bool> source = new Subject<bool>(); //Subject<bool> source = new Subject<bool>();
target.SetValue(Class1.FooProperty, "stylevalue", source); //target.SetValue(Class1.FooProperty, "stylevalue", source);
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty)); //Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
source.OnNext(true); //source.OnNext(true);
Assert.AreEqual("stylevalue", target.GetValue(Class1.FooProperty)); //Assert.AreEqual("stylevalue", target.GetValue(Class1.FooProperty));
source.OnNext(false); //source.OnNext(false);
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty)); //Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
} }
[TestMethod] [TestMethod]
public void StyleBinding_Detaches_OnCompleted() public void StyleBinding_Detaches_OnCompleted()
{ {
Class1 target = new Class1(); throw new NotImplementedException();
//Class1 target = new Class1();
Subject<bool> source = new Subject<bool>(); //Subject<bool> source = new Subject<bool>();
target.SetValue(Class1.FooProperty, "stylevalue", source); //target.SetValue(Class1.FooProperty, "stylevalue", source);
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty)); //Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
source.OnNext(true); //source.OnNext(true);
Assert.AreEqual("stylevalue", target.GetValue(Class1.FooProperty)); //Assert.AreEqual("stylevalue", target.GetValue(Class1.FooProperty));
source.OnCompleted(); //source.OnCompleted();
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty)); //Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
} }
[TestMethod] [TestMethod]
public void Later_StyleBindings_Have_Precedence() public void Later_StyleBindings_Have_Precedence()
{ {
Class1 target = new Class1(); throw new NotImplementedException();
//Class1 target = new Class1();
Subject<bool> source1 = new Subject<bool>();
Subject<bool> source2 = new Subject<bool>(); //Subject<bool> source1 = new Subject<bool>();
target.SetValue(Class1.FooProperty, "style1", source1); //Subject<bool> source2 = new Subject<bool>();
target.SetValue(Class1.FooProperty, "style2", source2); //target.SetValue(Class1.FooProperty, "style1", source1);
//target.SetValue(Class1.FooProperty, "style2", source2);
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
source1.OnNext(true); //Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
Assert.AreEqual("style1", target.GetValue(Class1.FooProperty)); //source1.OnNext(true);
source2.OnNext(true); //Assert.AreEqual("style1", target.GetValue(Class1.FooProperty));
Assert.AreEqual("style2", target.GetValue(Class1.FooProperty)); //source2.OnNext(true);
source1.OnNext(false); //Assert.AreEqual("style2", target.GetValue(Class1.FooProperty));
Assert.AreEqual("style2", target.GetValue(Class1.FooProperty)); //source1.OnNext(false);
source2.OnNext(false); //Assert.AreEqual("style2", target.GetValue(Class1.FooProperty));
Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty)); //source2.OnNext(false);
source2.OnNext(true); //Assert.AreEqual("foodefault", target.GetValue(Class1.FooProperty));
Assert.AreEqual("style2", target.GetValue(Class1.FooProperty)); //source2.OnNext(true);
source1.OnNext(true); //Assert.AreEqual("style2", target.GetValue(Class1.FooProperty));
Assert.AreEqual("style2", target.GetValue(Class1.FooProperty)); //source1.OnNext(true);
//Assert.AreEqual("style2", target.GetValue(Class1.FooProperty));
} }
private class Class1 : PerspexObject 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() public void Activator_And_Should_Follow_Single_Input()
{ {
var inputs = new[] { new TestSubject<bool>(false) }; 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>(); var result = new TestObserver<bool>();
target.Subscribe(result); target.Subscribe(result);
@ -42,7 +42,7 @@
new TestSubject<bool>(false), new TestSubject<bool>(false),
new TestSubject<bool>(true), new TestSubject<bool>(true),
}; };
var target = new Activator(inputs, "", ActivatorMode.And); var target = new Activator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>(); var result = new TestObserver<bool>();
target.Subscribe(result); target.Subscribe(result);
@ -67,7 +67,7 @@
new TestSubject<bool>(false), new TestSubject<bool>(false),
new TestSubject<bool>(true), new TestSubject<bool>(true),
}; };
var target = new Activator(inputs, "", ActivatorMode.And); var target = new Activator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>(); var result = new TestObserver<bool>();
target.Subscribe(result); target.Subscribe(result);
@ -93,7 +93,7 @@
new TestSubject<bool>(false), new TestSubject<bool>(false),
new TestSubject<bool>(true), new TestSubject<bool>(true),
}; };
var target = new Activator(inputs, "", ActivatorMode.And); var target = new Activator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>(); var result = new TestObserver<bool>();
target.Subscribe(result); target.Subscribe(result);
@ -110,7 +110,7 @@
public void Activator_Or_Should_Follow_Single_Input() public void Activator_Or_Should_Follow_Single_Input()
{ {
var inputs = new[] { new TestSubject<bool>(false) }; 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>(); var result = new TestObserver<bool>();
target.Subscribe(result); target.Subscribe(result);
@ -134,7 +134,7 @@
new TestSubject<bool>(false), new TestSubject<bool>(false),
new TestSubject<bool>(true), new TestSubject<bool>(true),
}; };
var target = new Activator(inputs, "", ActivatorMode.Or); var target = new Activator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>(); var result = new TestObserver<bool>();
target.Subscribe(result); target.Subscribe(result);
@ -158,7 +158,7 @@
new TestSubject<bool>(false), new TestSubject<bool>(false),
new TestSubject<bool>(true), new TestSubject<bool>(true),
}; };
var target = new Activator(inputs, "", ActivatorMode.Or); var target = new Activator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>(); var result = new TestObserver<bool>();
target.Subscribe(result); target.Subscribe(result);
@ -183,7 +183,7 @@
new TestSubject<bool>(false), new TestSubject<bool>(false),
new TestSubject<bool>(true), new TestSubject<bool>(true),
}; };
var target = new Activator(inputs, "", ActivatorMode.Or); var target = new Activator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>(); var result = new TestObserver<bool>();
target.Subscribe(result); target.Subscribe(result);

3
Perspex.UnitTests/Styling/TestControlBase.cs

@ -30,8 +30,9 @@ namespace Perspex.UnitTests.Styling
set; 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(); 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 border = new Border();
border.Background = new Perspex.Media.SolidColorBrush(0xffffffff); border.Background = new Perspex.Media.SolidColorBrush(0xffffffff);
ContentPresenter contentPresenter = new ContentPresenter(); 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; border.Content = contentPresenter;
return border; return border;
} }

1
Perspex/Perspex.csproj

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

222
Perspex/PerspexObject.cs

@ -16,6 +16,27 @@ namespace Perspex
using System.Reflection; using System.Reflection;
using Splat; 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> /// <summary>
/// An object with <see cref="PerspexProperty"/> support. /// An object with <see cref="PerspexProperty"/> support.
/// </summary> /// </summary>
@ -99,15 +120,16 @@ namespace Perspex
/// </summary> /// </summary>
public void BeginDeferChanges() public void BeginDeferChanges()
{ {
foreach (PriorityValue v in this.values.Values) throw new NotImplementedException();
{ //foreach (PriorityValue v in this.values.Values)
v.BeginDeferChanges(); //{
} // v.BeginDeferChanges();
//}
this.Log().Debug(string.Format(
"Defer style changes on {0} (#{1:x8})", //this.Log().Debug(string.Format(
this.GetType().Name, // "Defer style changes on {0} (#{1:x8})",
this.GetHashCode())); // this.GetType().Name,
// this.GetHashCode()));
} }
/// <summary> /// <summary>
@ -116,15 +138,16 @@ namespace Perspex
/// </summary> /// </summary>
public void EndDeferChanges() public void EndDeferChanges()
{ {
foreach (PriorityValue v in this.values.Values) throw new NotImplementedException();
{ //foreach (PriorityValue v in this.values.Values)
v.EndDeferChanges(); //{
} // v.EndDeferChanges();
//}
this.Log().Debug(string.Format(
"End defer style changes on {0} (#{1:x8})", //this.Log().Debug(string.Format(
this.GetType().Name, // "End defer style changes on {0} (#{1:x8})",
this.GetHashCode())); // this.GetType().Name,
// this.GetHashCode()));
} }
/// <summary> /// <summary>
@ -189,19 +212,20 @@ namespace Perspex
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
public void ClearBinding(PerspexProperty property) public void ClearBinding(PerspexProperty property)
{ {
Contract.Requires<NullReferenceException>(property != null); throw new NotImplementedException();
PriorityValue value; //Contract.Requires<NullReferenceException>(property != null);
//PriorityValue value;
if (this.values.TryGetValue(property, out value))
{ //if (this.values.TryGetValue(property, out value))
value.ClearLocalBinding(); //{
// value.ClearLocalBinding();
this.Log().Debug(string.Format(
"Cleared binding on {0}.{1} (#{2:x8})", // this.Log().Debug(string.Format(
this.GetType().Name, // "Cleared binding on {0}.{1} (#{2:x8})",
property.Name, // this.GetType().Name,
this.GetHashCode())); // property.Name,
} // this.GetHashCode()));
//}
} }
/// <summary> /// <summary>
@ -312,7 +336,7 @@ namespace Perspex
if (this.values.TryGetValue(property, out value)) if (this.values.TryGetValue(property, out value))
{ {
result = value.GetEffectiveValue(); result = value.Value;
} }
else else
{ {
@ -364,32 +388,6 @@ namespace Perspex
return this.values.ContainsKey(property); 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> /// <summary>
/// Sets a <see cref="PerspexProperty"/> value. /// Sets a <see cref="PerspexProperty"/> value.
/// </summary> /// </summary>
@ -399,8 +397,6 @@ namespace Perspex
{ {
Contract.Requires<NullReferenceException>(property != null); Contract.Requires<NullReferenceException>(property != null);
IObservable<object> binding = TryCastToObservable(value);
PriorityValue v; PriorityValue v;
if (!this.values.TryGetValue(property, out v)) if (!this.values.TryGetValue(property, out v))
@ -414,57 +410,59 @@ namespace Perspex
this.values.Add(property, v); this.values.Add(property, v);
} }
if (binding == null) throw new NotImplementedException();
{ //v.SetLocalValue(value);
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()));
}
} }
/// <summary> /// <summary>
/// Binds a <see cref="PerspexProperty"/> to a style. /// Sets a <see cref="PerspexProperty"/> value.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The activated value.</param> /// <param name="value">The value.</param>
/// <param name="activator">An observable which activates the value.</param> public void SetValue<T>(PerspexProperty<T> property, T value)
/// <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>(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; /// <summary>
string bindingDescription = description != null ? description.Description : "[unnamed]"; /// 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( 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, this.GetType().Name,
property.Name, property.Name,
this.GetHashCode(), this.GetHashCode()));
bindingDescription)); }
/// <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) 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) private PriorityValue CreatePriorityValue(PerspexProperty property)
{ {
PriorityValue result = new PriorityValue(); PriorityValue result = new PriorityValue();

306
Perspex/PriorityValue.cs

@ -11,184 +11,262 @@ namespace Perspex
using System.Linq; using System.Linq;
using System.Reactive.Disposables; 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; /// <summary>
/// The currently registered binding entries.
private IDisposable localBinding; /// </summary>
private LinkedList<BindingEntry> bindings = new LinkedList<BindingEntry>();
private object lastValue = PerspexProperty.UnsetValue;
/// <summary>
private List<StyleEntry> styles = new List<StyleEntry>(); /// The current observers.
/// </summary>
private List<IObserver<Tuple<object, object>>> observers = private List<IObserver<Tuple<object, object>>> observers =
new List<IObserver<Tuple<object, object>>>(); 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 this.value = PerspexProperty.UnsetValue;
{
return this.localValue;
}
set
{
if (!object.Equals(this.localValue, value))
{
this.localValue = value;
this.Push();
}
}
} }
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) return this.bindings;
{
this.localBinding.Dispose();
}
} }
public void SetLocalValue(object value) /// <summary>
/// Gets the current value.
/// </summary>
public object Value
{ {
if (this.localBinding != null) get { return this.value; }
{
this.localBinding.Dispose();
}
this.LocalValue = 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) BindingEntry entry = new BindingEntry();
{ LinkedListNode<BindingEntry> insert = this.bindings.First;
this.localBinding.Dispose();
}
this.localBinding = binding.Subscribe(value => this.LocalValue = value); while (insert != null && insert.Value.Priority < priority)
}
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(); insert = insert.Next;
} }
}
public object GetEffectiveValue() if (insert == null)
{
if (this.localValue != PerspexProperty.UnsetValue)
{ {
return this.localValue; this.bindings.AddLast(entry);
} }
else else
{ {
foreach (StyleEntry style in Enumerable.Reverse(this.styles)) this.bindings.AddBefore(insert, entry);
{
if (style.Active)
{
return style.Value;
}
}
} }
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) public IDisposable Subscribe(IObserver<Tuple<object, object>> observer)
{ {
Contract.Requires<NullReferenceException>(observer != null);
this.observers.Add(observer); this.observers.Add(observer);
return Disposable.Create(() => this.observers.Remove(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) entry.Dispose();
{ this.bindings.Remove(entry);
this.Push(); 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)) /// <summary>
{ /// Sets the current value and notifies all observers.
foreach (IObserver<Tuple<object, object>> observer in this.observers) /// </summary>
{ /// <param name="value">The new value.</param>
observer.OnNext(Tuple.Create(this.lastValue, value)); /// <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( /// <summary>
IObservable<bool> activator, /// The current value of the binding.
object value, /// </summary>
Action activeChanged, public object Value
Action<StyleEntry> completed)
{ {
Contract.Requires<NullReferenceException>(activator != null); get;
Contract.Requires<NullReferenceException>(activeChanged != null); private set;
}
this.activator = activator; /// <summary>
this.Value = value; /// 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; throw new Exception("PriorityValue.Entry.Start() called more than once.");
activeChanged(); }
},
() => completed(this)); this.Priority = priority;
} this.Value = PerspexProperty.UnsetValue;
public bool Active if (binding is IObservableDescription)
{ {
get; this.Description = ((IObservableDescription)binding).Description;
private set; }
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; get;
private set; 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; } ITemplatedControl TemplatedParent { get; }
/// <summary> /// <summary>
/// Binds a <see cref="PerspexProperty"/> to a style. /// Binds a <see cref="PerspexProperty"/> to an observable.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The activated value.</param> /// <param name="source">The observable.</param>
/// <param name="activator">An observable which activates the value.</param> /// <param name="priority">The priority of the binding.</param>
/// <remarks> void Bind(
/// Style bindings have a lower precedence than local value bindings. They are toggled PerspexProperty property,
/// on or off by <paramref name="activator"/> and can be unbound by the activator IObservable<object> source,
/// completing. BindingPriority priority = BindingPriority.LocalValue);
/// </remarks>
void SetValue(PerspexProperty property, object value, IObservable<bool> activator);
} }
} }

2
Perspex/Styling/Selector.cs

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

1
Perspex/Styling/Selectors.cs

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

3
Perspex/Styling/Style.cs

@ -47,7 +47,8 @@ namespace Perspex.Styling
{ {
foreach (Setter setter in this.Setters) 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, Or,
} }
public class StyleActivator : IObservable<bool>, IObservableDescription public class StyleActivator : IObservable<bool>
{ {
ActivatorMode mode; ActivatorMode mode;
@ -29,12 +29,10 @@ namespace Perspex.Styling
public StyleActivator( public StyleActivator(
IEnumerable<IObservable<bool>> inputs, IEnumerable<IObservable<bool>> inputs,
string description,
ActivatorMode mode = ActivatorMode.And) ActivatorMode mode = ActivatorMode.And)
{ {
int i = 0; int i = 0;
this.Description = description;
this.mode = mode; this.mode = mode;
foreach (IObservable<bool> input in inputs) foreach (IObservable<bool> input in inputs)
@ -58,12 +56,6 @@ namespace Perspex.Styling
private set; private set;
} }
public string Description
{
get;
private set;
}
public bool HasCompleted public bool HasCompleted
{ {
get; 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.Id = "border";
border.Padding = new Thickness(3); border.Padding = new Thickness(3);
ContentPresenter contentPresenter = new ContentPresenter(); 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; border.Content = contentPresenter;
return border; 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