Browse Source

Added IPerspexObject.GetSubject extension method.

Steven Kirk 10 years ago
parent
commit
36543720ed

+ 81 - 0
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));
         }
 
+        /// <summary>
+        /// Gets a subject for a <see cref="PerspexProperty"/>.
+        /// </summary>
+        /// <typeparam name="T">The property type.</typeparam>
+        /// <param name="o">The object.</param>
+        /// <param name="property">The property.</param>
+        /// <param name="priority">
+        /// The priority with which binding values are written to the object.
+        /// </param>
+        /// <returns>
+        /// An <see cref="ISubject{T}"/> which can be used for two-way binding to/from the 
+        /// property.
+        /// </returns>
+        public static ISubject<T> GetSubject<T>(
+            this IPerspexObject o,
+            PerspexProperty<T> property,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            // TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the 
+            // AnonymousSubject classes from this file and use Subject.Create<T>.
+            var output = new Subject<T>();
+            var result = new AnonymousSubject<T>(
+                Observer.Create<T>(
+                    x => output.OnNext(x),
+                    e => output.OnError(e),
+                    () => output.OnCompleted()),
+                o.GetObservable(property));
+            o.Bind(property, output, priority);
+            return result;
+        }
+
         /// <summary>
         /// Binds a property to a subject according to a <see cref="BindingMode"/>.
         /// </summary>
@@ -209,5 +241,54 @@ namespace Perspex
                 handler(target)(e);
             }
         }
+
+        class AnonymousSubject<T, U> : ISubject<T, U>
+        {
+            private readonly IObserver<T> _observer;
+            private readonly IObservable<U> _observable;
+
+            public AnonymousSubject(IObserver<T> observer, IObservable<U> 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<U> 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<T> : AnonymousSubject<T, T>, ISubject<T>
+        {
+            public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
+                : base(observer, observable)
+            {
+            }
+        }
     }
 }

+ 49 - 0
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<string>();
+
+            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<string> FooProperty =
+                PerspexProperty.Register<Class1, string>("Foo", "foodefault");
+
+            public string Foo
+            {
+                get { return GetValue(FooProperty); }
+                set { SetValue(FooProperty, value); }
+            }
+        }
+    }
+}