Browse Source

Allow setters to use observables for their value.

Steven Kirk 10 years ago
parent
commit
ac24ccaebd

+ 72 - 0
Tests/Perspex.Styling.UnitTests/StyleTests.cs

@@ -8,6 +8,7 @@ namespace Perspex.Styling.UnitTests
 {
     using System;
     using System.Collections.Generic;
+    using System.Reactive.Subjects;
     using Perspex.Controls;
     using Perspex.Styling;
     using Xunit;
@@ -107,6 +108,77 @@ namespace Perspex.Styling.UnitTests
             Assert.Equal(new[] { "foodefault", "Foo", "Bar", "foodefault" }, values);
         }
 
+        [Fact]
+        public void Style_With_Value_And_Source_Should_Throw_Exception()
+        {
+            var source = new BehaviorSubject<string>("Foo");
+
+            Style style = new Style(x => x.OfType<Class1>())
+            {
+                Setters = new[]
+                {
+                    new Setter
+                    {
+                        Property = Class1.FooProperty,
+                        Source = source,
+                        Value = "Foo",
+                    },
+                },
+            };
+
+            var target = new Class1();
+
+            Assert.Throws<InvalidOperationException>(() => style.Attach(target));
+        }
+
+        [Fact]
+        public void Style_With_Source_Should_Update_Value()
+        {
+            var source = new BehaviorSubject<string>("Foo");
+
+            Style style = new Style(x => x.OfType<Class1>())
+            {
+                Setters = new[]
+                {
+                    new Setter(Class1.FooProperty, source),
+                },
+            };
+
+            var target = new Class1();
+
+            style.Attach(target);
+
+            Assert.Equal("Foo", target.Foo);
+            source.OnNext("Bar");
+            Assert.Equal("Bar", target.Foo);
+        }
+
+        [Fact]
+        public void Style_With_Source_Should_Update_And_Restore_Value()
+        {
+            var source = new BehaviorSubject<string>("Foo");
+
+            Style style = new Style(x => x.OfType<Class1>().Class("foo"))
+            {
+                Setters = new[]
+                {
+                    new Setter(Class1.FooProperty, source),
+                },
+            };
+
+            var target = new Class1();
+
+            style.Attach(target);
+
+            Assert.Equal("foodefault", target.Foo);
+            target.Classes.Add("foo");
+            Assert.Equal("Foo", target.Foo);
+            source.OnNext("Bar");
+            Assert.Equal("Bar", target.Foo);
+            target.Classes.Remove("foo");
+            Assert.Equal("foodefault", target.Foo);
+        }
+
         private class Class1 : Control
         {
             public static readonly PerspexProperty<string> FooProperty =

+ 51 - 1
src/Perspex.Styling/Styling/Setter.cs

@@ -1,29 +1,79 @@
 // -----------------------------------------------------------------------
 // <copyright file="Setter.cs" company="Steven Kirk">
-// Copyright 2014 MIT Licence. See licence.md for more information.
+// Copyright 2015 MIT Licence. See licence.md for more information.
 // </copyright>
 // -----------------------------------------------------------------------
 
 namespace Perspex.Styling
 {
+    using System;
+
+    /// <summary>
+    /// A setter for a <see cref="Style"/>.
+    /// </summary>
+    /// <remarks>
+    /// A <see cref="Setter"/> is used to set a <see cref="PerspexProperty"/> value on a
+    /// <see cref="PerspexObject"/> depending on a condition.
+    /// </remarks>
     public class Setter
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Setter"/> class.
+        /// </summary>
         public Setter()
         {
+
         }
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Setter"/> class.
+        /// </summary>
+        /// <param name="property">The property to set.</param>
+        /// <param name="value">The property value.</param>
         public Setter(PerspexProperty property, object value)
         {
             this.Property = property;
             this.Value = value;
         }
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Setter"/> class.
+        /// </summary>
+        /// <param name="property">The property to set.</param>
+        /// <param name="source">An observable which produces the value for the property.</param>
+        public Setter(PerspexProperty property, IObservable<object> source)
+        {
+            this.Property = property;
+            this.Source = source;
+        }
+
+        /// <summary>
+        /// Gets or sets the property to set.
+        /// </summary>
         public PerspexProperty Property
         {
             get;
             set;
         }
 
+        /// <summary>
+        /// Gets or sets an observable which produces the value for the property.
+        /// </summary>
+        /// <remarks>
+        /// Only one of <see cref="Source"/> and <see cref="Value"/> should be set.
+        /// </remarks>
+        public IObservable<object> Source
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// Gets or sets the property value.
+        /// </summary>
+        /// <remarks>
+        /// Only one of <see cref="Source"/> and <see cref="Value"/> should be set.
+        /// </remarks>
         public object Value
         {
             get;

+ 29 - 2
src/Perspex.Styling/Styling/Style.cs

@@ -46,7 +46,19 @@ namespace Perspex.Styling
                 {
                     foreach (Setter setter in this.Setters)
                     {
-                        control.SetValue(setter.Property, setter.Value, BindingPriority.Style);
+                        if (setter.Source != null && setter.Value != null)
+                        {
+                            throw new InvalidOperationException("Cannot set both Source and Value on a Setter.");
+                        }
+
+                        if (setter.Source == null)
+                        {
+                            control.SetValue(setter.Property, setter.Value, BindingPriority.Style);
+                        }
+                        else
+                        {
+                            control.Bind(setter.Property, setter.Source, BindingPriority.Style);
+                        }
                     }
                 }
             }
@@ -54,7 +66,22 @@ namespace Perspex.Styling
             {
                 foreach (Setter setter in this.Setters)
                 {
-                    var binding = new StyleBinding(match.ObservableResult, setter.Value, description);
+                    if (setter.Source != null && setter.Value != null)
+                    {
+                        throw new InvalidOperationException("Cannot set both Source and Value on a Setter.");
+                    }
+
+                    StyleBinding binding;
+
+                    if (setter.Source == null)
+                    {
+                        binding = new StyleBinding(match.ObservableResult, setter.Value, description);
+                    }
+                    else
+                    {
+                        binding = new StyleBinding(match.ObservableResult, setter.Source, description);
+                    }
+
                     control.Bind(setter.Property, binding, BindingPriority.StyleTrigger);
                 }
             }

+ 47 - 12
src/Perspex.Styling/Styling/StyleBinding.cs

@@ -1,6 +1,6 @@
 // -----------------------------------------------------------------------
 // <copyright file="StyleBinding.cs" company="Steven Kirk">
-// Copyright 2013 MIT Licence. See licence.md for more information.
+// Copyright 2015 MIT Licence. See licence.md for more information.
 // </copyright>
 // -----------------------------------------------------------------------
 
@@ -8,14 +8,16 @@ namespace Perspex.Styling
 {
     using System;
     using System.Reactive;
+    using System.Reactive.Linq;
 
     /// <summary>
     /// Provides an observable for a style.
     /// </summary>
     /// <remarks>
-    /// This class takes an activator and a value. The activator is an observable which produces
-    /// a bool. When the activator produces true, this observable will produce
-    /// <see cref="ActivatedValue"/>. When the activator produces false it will produce
+    /// A <see cref="StyleBinding"/> has two inputs: an activator observable and either an
+    /// <see cref="ActivatedValue"/> or a <see cref="Source"/> observable which produces the
+    /// activated value. When the activator produces true, the <see cref="StyleBinding"/> will
+    /// produce the current activated value. When the activator produces false it will produce
     /// <see cref="PerspexProperty.UnsetValue"/>.
     /// </remarks>
     internal class StyleBinding : ObservableBase<object>, IDescription
@@ -42,12 +44,19 @@ namespace Perspex.Styling
         }
 
         /// <summary>
-        /// Gets a description of the binding.
+        /// Initializes a new instance of the <see cref="StyleBinding"/> class.
         /// </summary>
-        public string Description
+        /// <param name="activator">The activator.</param>
+        /// <param name="source">An observable that produces the activated value.</param>
+        /// <param name="description">The binding description.</param>
+        public StyleBinding(
+            IObservable<bool> activator,
+            IObservable<object> source,
+            string description)
         {
-            get;
-            private set;
+            this.activator = activator;
+            this.Description = description;
+            this.Source = source;
         }
 
         /// <summary>
@@ -59,6 +68,22 @@ namespace Perspex.Styling
             private set;
         }
 
+        /// <summary>
+        /// Gets a description of the binding.
+        /// </summary>
+        public string Description
+        {
+            get;
+        }
+
+        /// <summary>
+        /// Gets an observable which produces the <see cref="ActivatedValue"/>.
+        /// </summary>
+        public IObservable<object> Source
+        {
+            get;
+        }
+
         /// <summary>
         /// Notifies the provider that an observer is to receive notifications.
         /// </summary>
@@ -67,10 +92,20 @@ namespace Perspex.Styling
         protected override IDisposable SubscribeCore(IObserver<object> observer)
         {
             Contract.Requires<NullReferenceException>(observer != null);
-            return this.activator.Subscribe(
-                active => observer.OnNext(active ? this.ActivatedValue : PerspexProperty.UnsetValue),
-                observer.OnError,
-                observer.OnCompleted);
+
+            if (this.Source == null)
+            {
+                return this.activator.Subscribe(
+                    active => observer.OnNext(active ? this.ActivatedValue : PerspexProperty.UnsetValue),
+                    observer.OnError,
+                    observer.OnCompleted);
+            }
+            else
+            {
+                return Observable
+                    .CombineLatest(this.activator, this.Source, (x, y) => new { Active = x, Value = y })
+                    .Subscribe(x => observer.OnNext(x.Active ? x.Value : PerspexProperty.UnsetValue));
+            }
         }
     }
 }