From 9b71fc9ce9405e82c971345c18ea88113c700383 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 1 Oct 2015 23:18:51 +0200 Subject: [PATCH] Added support for binding to Observables and Tasks Thread synchronization is not currently implemented. Task tests fail because of this: putting a Thread.Sleep before the Assert makes them pass. Need to work out how we're going to do async unit testing. --- .../Binding/PropertyAccessorNode.cs | 45 +++++++++++-- .../ExpressionObserverTests_Observable.cs | 61 ++++++++++++++++++ .../Binding/ExpressionObserverTests_Task.cs | 63 +++++++++++++++++++ .../Perspex.Markup.UnitTests.csproj | 3 + 4 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs create mode 100644 tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index d304577a32..bb966a08d5 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -3,13 +3,18 @@ using System; using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; using System.Reflection; +using System.Threading.Tasks; +using Perspex.Threading; namespace Perspex.Markup.Binding { internal class PropertyAccessorNode : ExpressionNode { private PropertyInfo _propertyInfo; + private IDisposable _subscription; public PropertyAccessorNode(string propertyName) { @@ -38,7 +43,7 @@ namespace Perspex.Markup.Binding protected override void SubscribeAndUpdate(object target) { - var result = ExpressionValue.None; + bool set = false; if (target != null) { @@ -46,7 +51,8 @@ namespace Perspex.Markup.Binding if (_propertyInfo != null) { - result = new ExpressionValue(_propertyInfo.GetValue(target)); + ReadValue(target); + set = true; var inpc = target as INotifyPropertyChanged; @@ -61,7 +67,10 @@ namespace Perspex.Markup.Binding _propertyInfo = null; } - CurrentValue = result; + if (!set) + { + CurrentValue = ExpressionValue.None; + } } protected override void Unsubscribe(object target) @@ -74,11 +83,39 @@ namespace Perspex.Markup.Binding } } + private void ReadValue(object target) + { + var value = _propertyInfo.GetValue(target); + var observable = value as IObservable; + var task = value as Task; + + if (observable != null) + { + CurrentValue = ExpressionValue.None; + _subscription = observable + .Subscribe(x => CurrentValue = new ExpressionValue(x)); + } + else if (task != null) + { + var resultProperty = task.GetType().GetTypeInfo().GetDeclaredProperty("Result"); + + if (resultProperty != null) + { + task.ContinueWith(x => CurrentValue = new ExpressionValue(resultProperty.GetValue(task))) + .ConfigureAwait(false); + } + } + else + { + CurrentValue = new ExpressionValue(value); + } + } + private void PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == PropertyName) { - CurrentValue = new ExpressionValue(_propertyInfo.GetValue(Target)); + ReadValue(sender); } } } diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs new file mode 100644 index 0000000000..1bdba260b6 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs @@ -0,0 +1,61 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Perspex.Markup.Binding; +using Xunit; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class ExpressionObserverTests_Observable + { + [Fact] + public void Should_Get_Simple_Observable_Value() + { + var source = new BehaviorSubject("foo"); + var data = new { Foo = source }; + var target = new ExpressionObserver(data, "Foo"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + source.OnNext("bar"); + + Assert.Equal(new[] { "foo", "bar" }, result); + } + + [Fact] + public void Should_Get_Property_Value_From_Observable() + { + var data = new Class1(); + var target = new ExpressionObserver(data, "Next.Foo"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Next.OnNext(new Class2("foo")); + + Assert.Equal(new[] { null, "foo" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + } + + private class Class1 : NotifyingBase + { + public Subject Next { get; } = new Subject(); + } + + private class Class2 : NotifyingBase + { + public Class2(string foo) + { + Foo = foo; + } + + public string Foo { get; } + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs new file mode 100644 index 0000000000..1854c3b858 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs @@ -0,0 +1,63 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Perspex.Markup.Binding; +using Xunit; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class ExpressionObserverTests_Task + { + [Fact] + public void Should_Get_Simple_Task_Value() + { + var tcs = new TaskCompletionSource(); + var data = new { Foo = tcs.Task }; + var target = new ExpressionObserver(data, "Foo"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + tcs.SetResult("foo"); + + Assert.Equal(new object[] { null, "foo" }, result.ToArray()); + } + + [Fact] + public void Should_Get_Property_Value_From_Task() + { + var tcs = new TaskCompletionSource(); + var data = new Class1(tcs.Task); + var target = new ExpressionObserver(data, "Next.Foo"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + tcs.SetResult(new Class2("foo")); + + Assert.Equal(new object[] { null, "foo" }, result.ToArray()); + } + + private class Class1 : NotifyingBase + { + public Class1(Task next) + { + Next = next; + } + + public Task Next { get; } + } + + private class Class2 : NotifyingBase + { + public Class2(string foo) + { + Foo = foo; + } + + public string Foo { get; } + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index eae2018e22..ecc0dd5ef7 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -72,6 +72,9 @@ + + +