diff --git a/src/Perspex.Base/PerspexObjectExtensions.cs b/src/Perspex.Base/PerspexObjectExtensions.cs index 52c8042ac8..e91ea6de0a 100644 --- a/src/Perspex.Base/PerspexObjectExtensions.cs +++ b/src/Perspex.Base/PerspexObjectExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -101,6 +102,37 @@ namespace Perspex GetDescription(o, property)); } + /// + /// Gets a subject for a . + /// + /// The property type. + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject GetSubject( + this IPerspexObject o, + PerspexProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + // TODO: Subject.Create is not yet in stable Rx : once it is, remove the + // AnonymousSubject classes from this file and use Subject.Create. + var output = new Subject(); + var result = new AnonymousSubject( + Observer.Create( + x => output.OnNext(x), + e => output.OnError(e), + () => output.OnCompleted()), + o.GetObservable(property)); + o.Bind(property, output, priority); + return result; + } + /// /// Binds a property to a subject according to a . /// @@ -209,5 +241,54 @@ namespace Perspex handler(target)(e); } } + + class AnonymousSubject : ISubject + { + private readonly IObserver _observer; + private readonly IObservable _observable; + + public AnonymousSubject(IObserver observer, IObservable observable) + { + _observer = observer; + _observable = observable; + } + + public void OnCompleted() + { + _observer.OnCompleted(); + } + + public void OnError(Exception error) + { + if (error == null) + throw new ArgumentNullException("error"); + + _observer.OnError(error); + } + + public void OnNext(T value) + { + _observer.OnNext(value); + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + throw new ArgumentNullException("observer"); + + // + // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence. + // + return _observable.Subscribe/*Unsafe*/(observer); + } + } + + class AnonymousSubject : AnonymousSubject, ISubject + { + public AnonymousSubject(IObserver observer, IObservable observable) + : base(observer, observable) + { + } + } } } diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs new file mode 100644 index 0000000000..2d2bf97ac6 --- /dev/null +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs @@ -0,0 +1,49 @@ +// 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 Xunit; + +namespace Perspex.Base.UnitTests +{ + public class PerspexObjectTests_GetSubject + { + [Fact] + public void GetSubject_Returns_Values() + { + var source = new Class1 { Foo = "foo" }; + var target = source.GetSubject(Class1.FooProperty); + var result = new List(); + + target.Subscribe(x => result.Add(x)); + source.Foo = "bar"; + source.Foo = "baz"; + + Assert.Equal(new[] { "foo", "bar", "baz" }, result); + } + + [Fact] + public void GetSubject_Sets_Values() + { + var source = new Class1 { Foo = "foo" }; + var target = source.GetSubject(Class1.FooProperty); + + target.OnNext("bar"); + Assert.Equal("bar", source.Foo); + } + + private class Class1 : PerspexObject + { + public static readonly PerspexProperty FooProperty = + PerspexProperty.Register("Foo", "foodefault"); + + public string Foo + { + get { return GetValue(FooProperty); } + set { SetValue(FooProperty, value); } + } + } + } +}