diff --git a/Perspex.Base/PerspexProperty.cs b/Perspex.Base/PerspexProperty.cs index 3c6333bb47..2c3f089b32 100644 --- a/Perspex.Base/PerspexProperty.cs +++ b/Perspex.Base/PerspexProperty.cs @@ -23,7 +23,6 @@ namespace Perspex /// /// Represents an unset property value. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields must be private", Justification = "It's readonly")] public static readonly object UnsetValue = new Unset(); /// @@ -41,7 +40,11 @@ namespace Perspex /// private Subject changed = new Subject(); - private Func validate; + /// + /// The validation functions for the property, by type. + /// + private Dictionary> validation = + new Dictionary>(); /// /// Initializes a new instance of the class. @@ -74,7 +77,7 @@ namespace Perspex this.defaultValues.Add(ownerType, defaultValue); this.Inherits = inherits; this.DefaultBindingMode = defaultBindingMode; - this.validate = validate; + this.validation.Add(ownerType, validate); this.IsAttached = isAttached; } @@ -204,7 +207,7 @@ namespace Perspex TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, - Func validate = null) + Func validate = null) where TOwner : PerspexObject { Contract.Requires(name != null); @@ -215,7 +218,7 @@ namespace Perspex defaultValue, inherits, defaultBindingMode, - validate, + Cast(validate), false); PerspexObject.Register(typeof(TOwner), result); @@ -343,7 +346,21 @@ namespace Perspex /// public Func GetValidationFunc(Type type) { - return this.validate; + Contract.Requires(type != null); + + while (type != null) + { + Func result; + + if (this.validation.TryGetValue(type, out result)) + { + return result; + } + + type = type.GetTypeInfo().BaseType; + } + + return null; } /// @@ -392,6 +409,23 @@ namespace Perspex this.defaultValues.Add(type, defaultValue); } + /// + /// Overrides the validation function for the property on the specified type. + /// + /// The type. + /// The validation function. + public void OverrideValidation(Type type, Func validation) + { + Contract.Requires(type != null); + + if (this.validation.ContainsKey(type)) + { + throw new InvalidOperationException("Validation is already set for this property."); + } + + this.validation.Add(type, validation); + } + /// /// Gets the string representation of the property. /// @@ -419,6 +453,20 @@ namespace Perspex this.changed.OnNext(e); } + /// + /// Casts a validation function accepting a typed owner to one accepting a + /// . + /// + /// The owner type. + /// The property value type. + /// The typed function. + /// The untyped function. + private static Func Cast(Func f) + where TOwner : PerspexObject + { + return f != null ? (o, v) => f((TOwner)o, v) : (Func < PerspexObject, TValue, TValue > )null; + } + /// /// Class representing the . /// diff --git a/Perspex.Base/PerspexProperty`1.cs b/Perspex.Base/PerspexProperty`1.cs index 16e327410f..b8bb06fa2c 100644 --- a/Perspex.Base/PerspexProperty`1.cs +++ b/Perspex.Base/PerspexProperty`1.cs @@ -67,6 +67,19 @@ namespace Perspex return (TValue)this.GetDefaultValue(typeof(T)); } + /// + /// Overrides the validation function for the property on the specified type. + /// + /// The type. + /// The validation function. + public void OverrideValidation(Func validation) where T : PerspexObject + { + var f = validation != null ? + (o, v) => validation((T)o, (TValue)v) : + (Func)null; + this.OverrideValidation(typeof(T), f); + } + /// /// Converts from a typed validation function to an untyped. /// diff --git a/Tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs b/Tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs index 2bbbc13d3c..2302b5a995 100644 --- a/Tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs +++ b/Tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs @@ -14,7 +14,7 @@ namespace Perspex.Base.UnitTests [Fact] public void SetValue_Causes_Validation() { - Class1 target = new Class1(); + var target = new Class1(); target.SetValue(Class1.QuxProperty, 5); Assert.Throws(() => target.SetValue(Class1.QuxProperty, 25)); @@ -24,7 +24,7 @@ namespace Perspex.Base.UnitTests [Fact] public void SetValue_Causes_Coercion() { - Class1 target = new Class1(); + var target = new Class1(); target.SetValue(Class1.QuxProperty, 5); Assert.Equal(5, target.GetValue(Class1.QuxProperty)); @@ -37,7 +37,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Revalidate_Causes_Recoercion() { - Class1 target = new Class1(); + var target = new Class1(); target.SetValue(Class1.QuxProperty, 7); Assert.Equal(7, target.GetValue(Class1.QuxProperty)); @@ -45,10 +45,25 @@ namespace Perspex.Base.UnitTests target.Revalidate(Class1.QuxProperty); } + [Fact] + public void Validation_Can_Be_Overridden() + { + var target = new Class2(); + Assert.Throws(() => target.SetValue(Class1.QuxProperty, 5)); + } + + [Fact] + public void Validation_Can_Be_Overridden_With_Null() + { + var target = new Class3(); + target.SetValue(Class1.QuxProperty, 50); + Assert.Equal(50, target.GetValue(Class1.QuxProperty)); + } + private class Class1 : PerspexObject { public static readonly PerspexProperty QuxProperty = - PerspexProperty.Register("Qux", validate: Coerce); + PerspexProperty.Register("Qux", validate: Validate); public Class1() { @@ -60,9 +75,9 @@ namespace Perspex.Base.UnitTests public int ErrorQux { get; set; } - private static int Coerce(PerspexObject instance, int value) + private static int Validate(Class1 instance, int value) { - if (value > ((Class1)instance).ErrorQux) + if (value > instance.ErrorQux) { throw new ArgumentOutOfRangeException(); } @@ -70,5 +85,34 @@ namespace Perspex.Base.UnitTests return Math.Min(Math.Max(value, 0), ((Class1)instance).MaxQux); } } + + private class Class2 : PerspexObject + { + public static readonly PerspexProperty QuxProperty = + Class1.QuxProperty.AddOwner(); + + static Class2() + { + QuxProperty.OverrideValidation(Validate); + } + + private static int Validate(Class2 instance, int value) + { + if (value < 100) + { + throw new ArgumentOutOfRangeException(); + } + + return value; + } + } + + private class Class3 : Class2 + { + static Class3() + { + QuxProperty.OverrideValidation(null); + } + } } }