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); }
+ }
+ }
+ }
+}