Browse Source

Handle invalid values in direct property bindings.

Invalid values will be converted to default(TPropertyValue).
Steven Kirk 10 years ago
parent
commit
a400a89003

+ 3 - 1
src/Perspex.Base/PerspexObject.cs

@@ -605,7 +605,9 @@ namespace Perspex
                     property,
                     GetDescription(source));
 
-                return source.Subscribe(x => SetValue(property, x));
+                return source
+                    .Select(x => TypeUtilities.CastOrDefault(x, property.PropertyType, false))
+                    .Subscribe(x => SetValue(property, x));
             }
             else
             {

+ 30 - 2
src/Perspex.Base/Utilities/TypeUtilities.cs

@@ -32,8 +32,9 @@ namespace Perspex.Utilities
         /// <param name="to">The type to cast to.</param>
         /// <param name="value">The value to cast.</param>
         /// <param name="result">If sucessful, contains the cast value.</param>
+        /// <param name="allowUnset">Allow <see cref="PerspexProperty.UnsetValue"/>.</param>
         /// <returns>True if the cast was sucessful, otherwise false.</returns>
-        public static bool TryCast(Type to, object value, out object result)
+        public static bool TryCast(Type to, object value, out object result, bool allowUnset = true)
         {
             Contract.Requires<ArgumentNullException>(to != null);
 
@@ -46,7 +47,7 @@ namespace Perspex.Utilities
 
             var from = value.GetType();
 
-            if (value == PerspexProperty.UnsetValue)
+            if (allowUnset && value == PerspexProperty.UnsetValue)
             {
                 result = value;
                 return true;
@@ -77,5 +78,32 @@ namespace Perspex.Utilities
             result = null;
             return false;
         }
+
+        /// <summary>
+        /// Casts a value to a type, returning the default for that type if the value could not be
+        /// cast.
+        /// </summary>
+        /// <param name="value">The value to cast.</param>
+        /// <param name="type">The type to cast to..</param>
+        /// <param name="allowUnset">Allow <see cref="PerspexProperty.UnsetValue"/>.</param>
+        /// <returns>A value of <paramref name="type"/>.</returns>
+        public static object CastOrDefault(object value, Type type, bool allowUnset = true)
+        {
+            var typeInfo = type.GetTypeInfo();
+            object result;
+
+            if (TypeUtilities.TryCast(type, value, out result, allowUnset))
+            {
+                return result;
+            }
+            else if (typeInfo.IsValueType)
+            {
+                return Activator.CreateInstance(type);
+            }
+            else
+            {
+                return null;
+            }
+        }
     }
 }

+ 49 - 1
tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs

@@ -148,6 +148,45 @@ namespace Perspex.Base.UnitTests
             Assert.Equal("second", target.Foo);
         }
 
+        [Fact]
+        public void Bind_Handles_Wrong_Type()
+        {
+            var target = new Class1();
+            var source = new Subject<object>();
+
+            var sub = target.Bind(Class1.FooProperty, source);
+
+            source.OnNext(45);
+
+            Assert.Equal(null, target.Foo);
+        }
+
+        [Fact]
+        public void Bind_Handles_Wrong_Value_Type()
+        {
+            var target = new Class1();
+            var source = new Subject<object>();
+
+            var sub = target.Bind(Class1.BazProperty, source);
+
+            source.OnNext("foo");
+
+            Assert.Equal(0, target.Baz);
+        }
+
+        [Fact]
+        public void Bind_Handles_UnsetValue()
+        {
+            var target = new Class1();
+            var source = new Subject<object>();
+
+            var sub = target.Bind(Class1.BazProperty, source);
+
+            source.OnNext(PerspexProperty.UnsetValue);
+
+            Assert.Equal(0, target.Baz);
+        }
+
         [Fact]
         public void ReadOnly_Property_Cannot_Be_Set()
         {
@@ -295,9 +334,12 @@ namespace Perspex.Base.UnitTests
             public static readonly PerspexProperty<string> BarProperty =
                 PerspexProperty.RegisterDirect<Class1, string>("Bar", o => o.Bar);
 
-            private string _foo = "initial";
+            public static readonly PerspexProperty<int> BazProperty =
+                PerspexProperty.RegisterDirect<Class1, int>("Bar", o => o.Baz, (o,v) => o.Baz = v);
 
+            private string _foo = "initial";
             private string _bar = "bar";
+            private int _baz = 5;
 
             public string Foo
             {
@@ -309,6 +351,12 @@ namespace Perspex.Base.UnitTests
             {
                 get { return _bar; }
             }
+
+            public int Baz
+            {
+                get { return _baz; }
+                set { SetAndRaise(BazProperty, ref _baz, value); }
+            }
         }
 
         private class Class2 : PerspexObject