Browse Source

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.
pull/237/head
Steven Kirk 11 years ago
parent
commit
9b71fc9ce9
  1. 45
      src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs
  2. 61
      tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs
  3. 63
      tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs
  4. 3
      tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

45
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<object>;
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);
}
}
}

61
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<string>("foo");
var data = new { Foo = source };
var target = new ExpressionObserver(data, "Foo");
var result = new List<object>();
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<object>();
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<Class2> Next { get; } = new Subject<Class2>();
}
private class Class2 : NotifyingBase
{
public Class2(string foo)
{
Foo = foo;
}
public string Foo { get; }
}
}
}

63
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<string>();
var data = new { Foo = tcs.Task };
var target = new ExpressionObserver(data, "Foo");
var result = new List<object>();
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<Class2>();
var data = new Class1(tcs.Task);
var target = new ExpressionObserver(data, "Next.Foo");
var result = new List<object>();
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<Class2> next)
{
Next = next;
}
public Task<Class2> Next { get; }
}
private class Class2 : NotifyingBase
{
public Class2(string foo)
{
Foo = foo;
}
public string Foo { get; }
}
}
}

3
tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

@ -72,6 +72,9 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Binding\ExpressionNodeBuilderTests_Errors.cs" />
<Compile Include="Binding\ExpressionObserverTests_Observable.cs" />
<Compile Include="Binding\ExpressionObserverTests_Task.cs" />
<Compile Include="Binding\ExpressionObserverTests_Indexer.cs" />
<Compile Include="Binding\ExpressionObserverTests_Negation.cs" />
<Compile Include="Binding\ExpressionObserverTests_Property.cs" />

Loading…
Cancel
Save