From 3427b7655bbac81a029f3a2c89548d4ddf87bdbe Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 27 Oct 2015 18:13:38 +0100 Subject: [PATCH] Added PerspexProperty plugin to ExpressionObserver. --- .../Perspex.Markup/Data/ExpressionObserver.cs | 3 +- .../Plugins/InpcPropertyAccessorPlugin.cs | 12 --- .../Plugins/PerspexPropertyAccessorPlugin.cs | 101 ++++++++++++++++++ .../Perspex.Markup/Perspex.Markup.csproj | 1 + src/Perspex.Base/PerspexProperty.cs | 5 + ...ExpressionObserverTests_PerspexProperty.cs | 45 ++++++++ .../Perspex.Markup.UnitTests.csproj | 1 + 7 files changed, 155 insertions(+), 13 deletions(-) create mode 100644 src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs create mode 100644 tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_PerspexProperty.cs diff --git a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs index df210eec73..5648266e3d 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs @@ -21,7 +21,8 @@ namespace Perspex.Markup.Data public static readonly IList PropertyAccessors = new List { - new InpcPropertyAccessorPlugin() + new PerspexPropertyAccessorPlugin(), + new InpcPropertyAccessorPlugin(), }; private Func _root; diff --git a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs index ea0659c10b..f6907fddfd 100644 --- a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs @@ -54,18 +54,6 @@ namespace Perspex.Markup.Data.Plugins } } - private IObservable GetObservable( - INotifyPropertyChanged inpc, - PropertyInfo property, - object o) - { - return Observable.FromEventPattern( - x => inpc.PropertyChanged += x, - x => inpc.PropertyChanged -= x) - .Where(e => e.EventArgs.PropertyName == property.Name) - .Select(_ => property.GetValue(o)); - } - private class Accessor : IPropertyAccessor { private object _instance; diff --git a/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs new file mode 100644 index 0000000000..8e8fba45cb --- /dev/null +++ b/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs @@ -0,0 +1,101 @@ +// 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.ComponentModel; +using System.Reactive.Linq; +using System.Reflection; + +namespace Perspex.Markup.Data.Plugins +{ + /// + /// Reads a property from a . + /// + public class PerspexPropertyAccessorPlugin : IPropertyAccessorPlugin + { + /// + /// Checks whether this plugin can handle accessing the properties of the specified object. + /// + /// The object. + /// True if the plugin can handle the object; otherwise false. + public bool Match(object instance) + { + Contract.Requires(instance != null); + + return instance is PerspexObject; + } + + /// + /// Starts monitoring the value of a property on an object. + /// + /// The object. + /// The property name. + /// A function to call when the property changes. + /// + /// An interface through which future interactions with the + /// property will be made, or null if the property was not found. + /// + public IPropertyAccessor Start(object instance, string propertyName, Action changed) + { + Contract.Requires(instance != null); + Contract.Requires(propertyName != null); + Contract.Requires(changed != null); + + var o = (PerspexObject)instance; + var p = PerspexPropertyRegistry.Instance.FindRegistered(o, propertyName); + + if (p != null) + { + return new Accessor(o, p, changed); + } + else + { + return null; + } + } + + private class Accessor : IPropertyAccessor + { + private PerspexObject _instance; + private PerspexProperty _property; + private IDisposable _subscription; + + public Accessor(PerspexObject instance, PerspexProperty property, Action changed) + { + Contract.Requires(instance != null); + Contract.Requires(property != null); + + _instance = instance; + _property = property; + _subscription = instance.GetObservable(property).Skip(1).Subscribe(changed); + } + + public Type PropertyType + { + get { return _property.PropertyType; } + } + + public object Value + { + get { return _instance.GetValue(_property); } + } + + public void Dispose() + { + _subscription?.Dispose(); + _subscription = null; + } + + public bool SetValue(object value) + { + if (!_property.IsReadOnly) + { + _instance.SetValue(_property, value); + return true; + } + + return false; + } + } + } +} diff --git a/src/Markup/Perspex.Markup/Perspex.Markup.csproj b/src/Markup/Perspex.Markup/Perspex.Markup.csproj index eea1576051..24a8c29a03 100644 --- a/src/Markup/Perspex.Markup/Perspex.Markup.csproj +++ b/src/Markup/Perspex.Markup/Perspex.Markup.csproj @@ -42,6 +42,7 @@ + diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs index 2dd679ce8d..9ce204ef4b 100644 --- a/src/Perspex.Base/PerspexProperty.cs +++ b/src/Perspex.Base/PerspexProperty.cs @@ -270,6 +270,11 @@ namespace Perspex /// public bool IsDirect { get; } + /// + /// Gets a value indicating whether this is a readonly property. + /// + public bool IsReadOnly => IsDirect && Setter == null; + /// /// Gets an observable that is fired when this property is initialized on a /// new instance. diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_PerspexProperty.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_PerspexProperty.cs new file mode 100644 index 0000000000..4092f8744b --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_PerspexProperty.cs @@ -0,0 +1,45 @@ +// 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 Perspex.Markup.Data; +using Xunit; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class ExpressionObserverTests_PerspexProperty + { + [Fact] + public async void Should_Get_Simple_Property_Value() + { + var data = new Class1(); + var target = new ExpressionObserver(data, "Foo"); + var result = await target.Take(1); + + Assert.Equal("foo", result); + } + + [Fact] + public void Should_Track_Simple_Property_Value() + { + var data = new Class1(); + var target = new ExpressionObserver(data, "Foo"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x)); + data.SetValue(Class1.FooProperty, "bar"); + + Assert.Equal(new[] { "foo", "bar" }, result); + + sub.Dispose(); + } + + private class Class1 : PerspexObject + { + public static readonly PerspexProperty FooProperty = + PerspexProperty.Register("Foo", defaultValue: "foo"); + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index 5fdecc1cdb..66c470fc05 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -73,6 +73,7 @@ +