diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj
index 53f67bda25..7a79bd16f2 100644
--- a/Perspex.Base/Perspex.Base.csproj
+++ b/Perspex.Base/Perspex.Base.csproj
@@ -64,6 +64,7 @@
+
diff --git a/Perspex.Base/PerspexObject.cs b/Perspex.Base/PerspexObject.cs
index af82369b78..edc050e59b 100644
--- a/Perspex.Base/PerspexObject.cs
+++ b/Perspex.Base/PerspexObject.cs
@@ -16,6 +16,8 @@ namespace Perspex
using Perspex.Reactive;
using Serilog;
using Serilog.Core.Enrichers;
+ using Perspex.Utilities;
+
///
/// The priority of a binding.
@@ -514,13 +516,13 @@ namespace Perspex
this.GetType()));
}
- if (!PriorityValue.IsValidValue(value, property.PropertyType))
+ if (!TypeUtilities.TryCast(property.PropertyType, value, out value))
{
throw new InvalidOperationException(string.Format(
"Invalid value for Property '{0}': {1} ({2})",
property.Name,
value,
- value.GetType().FullName));
+ value?.GetType().FullName ?? "(null)"));
}
if (!this.values.TryGetValue(property, out v))
diff --git a/Perspex.Base/PerspexProperty.cs b/Perspex.Base/PerspexProperty.cs
index e6184ca510..d0f9d15037 100644
--- a/Perspex.Base/PerspexProperty.cs
+++ b/Perspex.Base/PerspexProperty.cs
@@ -6,6 +6,7 @@
namespace Perspex
{
+ using Perspex.Utilities;
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
@@ -346,7 +347,7 @@ namespace Perspex
/// True if the value is valid, otherwise false.
public bool IsValidValue(object value)
{
- return PriorityValue.IsValidValue(value, this.PropertyType);
+ return TypeUtilities.TryCast(this.PropertyType, value, out value);
}
///
@@ -368,7 +369,7 @@ namespace Perspex
{
Contract.Requires(type != null);
- if (!this.IsValidValue(defaultValue))
+ if (!TypeUtilities.TryCast(this.PropertyType, defaultValue, out defaultValue))
{
throw new InvalidOperationException(string.Format(
"Invalid value for Property '{0}': {1} ({2})",
diff --git a/Perspex.Base/PriorityValue.cs b/Perspex.Base/PriorityValue.cs
index d269d3c2a0..41e6c884fc 100644
--- a/Perspex.Base/PriorityValue.cs
+++ b/Perspex.Base/PriorityValue.cs
@@ -12,17 +12,18 @@ namespace Perspex
using System.Reactive.Subjects;
using System.Reflection;
using System.Text;
+ using Perspex.Utilities;
///
/// Maintains a list of prioritised bindings together with a current value.
///
///
- /// Bindings, in the form of s are added to the object using
+ /// Bindings, in the form of s are added to the object using
/// the method. With the observable is passed a priority, where lower values
/// represent higher priorites. The current is selected from the highest
/// priority binding that doesn't return . Where there
/// are multiple bindings registered with the same priority, the most recently added binding
- /// has a higher priority. Each time the value changes, the observable is
+ /// has a higher priority. Each time the value changes, the observable is
/// fired with the old and new values.
///
internal class PriorityValue
@@ -100,39 +101,6 @@ namespace Perspex
private set;
}
- ///
- /// Checks whether a value is valid for a type.
- ///
- /// The value.
- /// The type.
- /// True if the value is valid, otherwise false.
- public static bool IsValidValue(object value, Type propertyType)
- {
- TypeInfo type = propertyType.GetTypeInfo();
-
- if (value == PerspexProperty.UnsetValue)
- {
- return true;
- }
- else if (value == null)
- {
- if (type.IsValueType &&
- (!type.IsGenericType || !(type.GetGenericTypeDefinition() == typeof(Nullable<>))))
- {
- return false;
- }
- }
- else
- {
- if (!type.IsAssignableFrom(value.GetType().GetTypeInfo()))
- {
- return false;
- }
- }
-
- return true;
- }
-
///
/// Adds a new binding.
///
@@ -172,7 +140,7 @@ namespace Perspex
}
///
- /// Returns diagnostic string that can help the user debug the bindings in effect on
+ /// Returns diagnostic string that can help the user debug the bindings in effect on
/// this object.
///
/// A diagnostic string.
@@ -188,7 +156,7 @@ namespace Perspex
b.AppendLine();
}
- b.Append(this.ValuePriority == level.Key ? "*" : "");
+ b.Append(this.ValuePriority == level.Key ? "*" : string.Empty);
b.Append("Priority ");
b.Append(level.Key);
b.Append(": ");
@@ -199,7 +167,7 @@ namespace Perspex
foreach (var binding in level.Value.Bindings)
{
- b.Append(level.Value.ActiveBindingIndex == binding.Index ? "*" : "");
+ b.Append(level.Value.ActiveBindingIndex == binding.Index ? "*" : string.Empty);
b.Append(binding.Description ?? binding.Observable.GetType().Name);
b.Append(": ");
b.AppendLine(binding.Value?.ToString() ?? "(null)");
@@ -254,7 +222,14 @@ namespace Perspex
/// The priority level that the value came from.
private void UpdateValue(object value, int priority)
{
- this.VerifyValidValue(value);
+ if (!TypeUtilities.TryCast(this.valueType, value, out value))
+ {
+ throw new InvalidOperationException(string.Format(
+ "Invalid value for Property '{0}': {1} ({2})",
+ this.name,
+ value,
+ value?.GetType().FullName ?? "(null)"));
+ }
var old = this.value;
@@ -268,26 +243,10 @@ namespace Perspex
this.changed.OnNext(Tuple.Create(old, this.value));
}
- ///
- /// Throws an exception if is invalid.
- ///
- /// The value.
- private void VerifyValidValue(object value)
- {
- if (!IsValidValue(value, this.valueType))
- {
- throw new InvalidOperationException(string.Format(
- "Invalid value for Property '{0}': {1} ({2})",
- this.name,
- value,
- value.GetType().FullName));
- }
- }
-
///
/// Called when the value for a priority level changes.
///
- /// The changed entry.
+ /// The priority level of the changed entry.
private void ValueChanged(PriorityLevel level)
{
if (level.Priority <= this.ValuePriority)
diff --git a/Perspex.Base/Utilities/TypeUtilities.cs b/Perspex.Base/Utilities/TypeUtilities.cs
new file mode 100644
index 0000000000..6ef08616ae
--- /dev/null
+++ b/Perspex.Base/Utilities/TypeUtilities.cs
@@ -0,0 +1,83 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2015 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Utilities
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection;
+
+ ///
+ /// Provides utilities for working with types at runtime.
+ ///
+ internal static class TypeUtilities
+ {
+ private static readonly Dictionary> Conversions = new Dictionary>() {
+ { typeof(decimal), new List { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } },
+ { typeof(double), new List { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
+ { typeof(float), new List { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
+ { typeof(ulong), new List { typeof(byte), typeof(ushort), typeof(uint), typeof(char) } },
+ { typeof(long), new List { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char) } },
+ { typeof(uint), new List { typeof(byte), typeof(ushort), typeof(char) } },
+ { typeof(int), new List { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char) } },
+ { typeof(ushort), new List { typeof(byte), typeof(char) } },
+ { typeof(short), new List { typeof(byte) } }
+ };
+
+ ///
+ /// Try to cast a value to a type, using implicit conversions if possible.
+ ///
+ /// The type to cast to.
+ /// The value to cast.
+ /// If sucessful, contains the cast value.
+ /// True if the cast was sucessful, otherwise false.
+ public static bool TryCast(Type to, object value, out object result)
+ {
+ Contract.Requires(to != null);
+
+ if (value == null)
+ {
+ var t = to.GetTypeInfo();
+ result = null;
+ return !t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>)));
+ }
+
+ var from = value.GetType();
+
+ if (value == PerspexProperty.UnsetValue)
+ {
+ result = value;
+ return true;
+ }
+ else if (to.GetTypeInfo().IsAssignableFrom(from.GetTypeInfo()))
+ {
+ result = value;
+ return true;
+ }
+ else if (Conversions.ContainsKey(to) && Conversions[to].Contains(from))
+ {
+ result = Convert.ChangeType(value, to);
+ return true;
+ }
+ else
+ {
+ var cast = from.GetTypeInfo()
+ .GetDeclaredMethods("op_Implicit")
+ .FirstOrDefault(m => m.ReturnType == to);
+
+ if (cast != null)
+ {
+ result = cast.Invoke(null, new[] { value });
+ return true;
+ }
+ }
+
+ result = null;
+ return false;
+ }
+ }
+}
diff --git a/Perspex.Themes.Default/ButtonStyle.cs b/Perspex.Themes.Default/ButtonStyle.cs
index 800fdcc2f1..e711c977e7 100644
--- a/Perspex.Themes.Default/ButtonStyle.cs
+++ b/Perspex.Themes.Default/ButtonStyle.cs
@@ -37,7 +37,7 @@ namespace Perspex.Themes.Default
{
new Setter(Button.BackgroundProperty, new SolidColorBrush(0xffdddddd)),
new Setter(Button.BorderBrushProperty, new SolidColorBrush(0xff707070)),
- new Setter(Button.BorderThicknessProperty, 2.0),
+ new Setter(Button.BorderThicknessProperty, 2),
new Setter(Button.ForegroundProperty, new SolidColorBrush(0xff000000)),
},
},
diff --git a/Tests/Perspex.Base.UnitTests/GlobalSuppressions.cs b/Tests/Perspex.Base.UnitTests/GlobalSuppressions.cs
new file mode 100644
index 0000000000..c4e71ed365
--- /dev/null
+++ b/Tests/Perspex.Base.UnitTests/GlobalSuppressions.cs
@@ -0,0 +1,13 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "StyleCop.CSharp.DocumentationRules",
+ "SA1600:Elements must be documented",
+ Justification = "Tests should be self-documenting")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "StyleCop.CSharp.NamingRules",
+ "SA1300:Element must begin with upper-case letter",
+ Justification = "Some tests must begin with lower-case letters")]
\ No newline at end of file
diff --git a/Tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj b/Tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
index 8dd0aa9a74..e0ed27530f 100644
--- a/Tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
+++ b/Tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
@@ -78,6 +78,7 @@
+
diff --git a/Tests/Perspex.Base.UnitTests/PerspexObjectTests.cs b/Tests/Perspex.Base.UnitTests/PerspexObjectTests.cs
index 0ceeaee616..88f14bd565 100644
--- a/Tests/Perspex.Base.UnitTests/PerspexObjectTests.cs
+++ b/Tests/Perspex.Base.UnitTests/PerspexObjectTests.cs
@@ -35,7 +35,7 @@ namespace Perspex.Base.UnitTests
{
string[] names = PerspexObject.GetProperties(typeof(Class2)).Select(x => x.Name).ToArray();
- Assert.Equal(new[] { "Bar", "Flob", "Foo", "Baz", "Qux" }, names);
+ Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux" }, names);
}
[Fact]
@@ -176,7 +176,35 @@ namespace Perspex.Base.UnitTests
{
Class2 target = new Class2();
- target.SetValue(Class2.FlobProperty, 4);
+ target.SetValue((PerspexProperty)Class2.FlobProperty, 4);
+
+ var value = target.GetValue(Class2.FlobProperty);
+ Assert.IsType(value);
+ Assert.Equal(4, value);
+ }
+
+ [Fact]
+ public void SetValue_Respects_Implicit_Conversions()
+ {
+ Class2 target = new Class2();
+
+ target.SetValue((PerspexProperty)Class2.FlobProperty, new ImplictDouble(4));
+
+ var value = target.GetValue(Class2.FlobProperty);
+ Assert.IsType(value);
+ Assert.Equal(4, value);
+ }
+
+ [Fact]
+ public void SetValue_Can_Convert_To_Nullable()
+ {
+ Class2 target = new Class2();
+
+ target.SetValue((PerspexProperty)Class2.FredProperty, 4.0);
+
+ var value = target.GetValue(Class2.FredProperty);
+ Assert.IsType(value);
+ Assert.Equal(4, value);
}
[Fact]
@@ -616,13 +644,13 @@ namespace Perspex.Base.UnitTests
public static readonly PerspexProperty QuxProperty =
PerspexProperty.Register("Qux", coerce: Coerce);
- public int MaxQux { get; set; }
-
public Class1()
{
this.MaxQux = 10;
}
+ public int MaxQux { get; set; }
+
private static int Coerce(PerspexObject instance, int value)
{
return Math.Min(Math.Max(value, 0), ((Class1)instance).MaxQux);
@@ -637,6 +665,9 @@ namespace Perspex.Base.UnitTests
public static readonly PerspexProperty FlobProperty =
PerspexProperty.Register("Flob");
+ public static readonly PerspexProperty FredProperty =
+ PerspexProperty.Register("Fred");
+
static Class2()
{
FooProperty.OverrideDefaultValue(typeof(Class2), "foooverride");
@@ -648,5 +679,20 @@ namespace Perspex.Base.UnitTests
set { this.InheritanceParent = value; }
}
}
+
+ private class ImplictDouble
+ {
+ public ImplictDouble(double value)
+ {
+ this.Value = value;
+ }
+
+ public double Value { get; }
+
+ public static implicit operator double(ImplictDouble v)
+ {
+ return v.Value;
+ }
+ }
}
}