Browse Source

Allow PerspexProperty validation to be overridden.

pull/63/head
Steven Kirk 11 years ago
parent
commit
8dafd64ae3
  1. 60
      Perspex.Base/PerspexProperty.cs
  2. 13
      Perspex.Base/PerspexProperty`1.cs
  3. 56
      Tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs

60
Perspex.Base/PerspexProperty.cs

@ -23,7 +23,6 @@ namespace Perspex
/// <summary> /// <summary>
/// Represents an unset property value. /// Represents an unset property value.
/// </summary> /// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields must be private", Justification = "It's readonly")]
public static readonly object UnsetValue = new Unset(); public static readonly object UnsetValue = new Unset();
/// <summary> /// <summary>
@ -41,7 +40,11 @@ namespace Perspex
/// </summary> /// </summary>
private Subject<PerspexPropertyChangedEventArgs> changed = new Subject<PerspexPropertyChangedEventArgs>(); private Subject<PerspexPropertyChangedEventArgs> changed = new Subject<PerspexPropertyChangedEventArgs>();
private Func<PerspexObject, object, object> validate; /// <summary>
/// The validation functions for the property, by type.
/// </summary>
private Dictionary<Type, Func<PerspexObject, object, object>> validation =
new Dictionary<Type, Func<PerspexObject, object, object>>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class. /// Initializes a new instance of the <see cref="PerspexProperty"/> class.
@ -74,7 +77,7 @@ namespace Perspex
this.defaultValues.Add(ownerType, defaultValue); this.defaultValues.Add(ownerType, defaultValue);
this.Inherits = inherits; this.Inherits = inherits;
this.DefaultBindingMode = defaultBindingMode; this.DefaultBindingMode = defaultBindingMode;
this.validate = validate; this.validation.Add(ownerType, validate);
this.IsAttached = isAttached; this.IsAttached = isAttached;
} }
@ -204,7 +207,7 @@ namespace Perspex
TValue defaultValue = default(TValue), TValue defaultValue = default(TValue),
bool inherits = false, bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay, BindingMode defaultBindingMode = BindingMode.OneWay,
Func<PerspexObject, TValue, TValue> validate = null) Func<TOwner, TValue, TValue> validate = null)
where TOwner : PerspexObject where TOwner : PerspexObject
{ {
Contract.Requires<NullReferenceException>(name != null); Contract.Requires<NullReferenceException>(name != null);
@ -215,7 +218,7 @@ namespace Perspex
defaultValue, defaultValue,
inherits, inherits,
defaultBindingMode, defaultBindingMode,
validate, Cast(validate),
false); false);
PerspexObject.Register(typeof(TOwner), result); PerspexObject.Register(typeof(TOwner), result);
@ -343,7 +346,21 @@ namespace Perspex
/// </returns> /// </returns>
public Func<PerspexObject, object, object> GetValidationFunc(Type type) public Func<PerspexObject, object, object> GetValidationFunc(Type type)
{ {
return this.validate; Contract.Requires<NullReferenceException>(type != null);
while (type != null)
{
Func<PerspexObject, object, object> result;
if (this.validation.TryGetValue(type, out result))
{
return result;
}
type = type.GetTypeInfo().BaseType;
}
return null;
} }
/// <summary> /// <summary>
@ -392,6 +409,23 @@ namespace Perspex
this.defaultValues.Add(type, defaultValue); this.defaultValues.Add(type, defaultValue);
} }
/// <summary>
/// Overrides the validation function for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="validation">The validation function.</param>
public void OverrideValidation(Type type, Func<PerspexObject, object, object> validation)
{
Contract.Requires<NullReferenceException>(type != null);
if (this.validation.ContainsKey(type))
{
throw new InvalidOperationException("Validation is already set for this property.");
}
this.validation.Add(type, validation);
}
/// <summary> /// <summary>
/// Gets the string representation of the property. /// Gets the string representation of the property.
/// </summary> /// </summary>
@ -419,6 +453,20 @@ namespace Perspex
this.changed.OnNext(e); this.changed.OnNext(e);
} }
/// <summary>
/// Casts a validation function accepting a typed owner to one accepting a
/// <see cref="Perspex"/>.
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <typeparam name="TValue">The property value type.</typeparam>
/// <param name="f">The typed function.</param>
/// <returns>The untyped function.</returns>
private static Func<PerspexObject, TValue, TValue> Cast<TOwner, TValue>(Func<TOwner, TValue, TValue> f)
where TOwner : PerspexObject
{
return f != null ? (o, v) => f((TOwner)o, v) : (Func < PerspexObject, TValue, TValue > )null;
}
/// <summary> /// <summary>
/// Class representing the <see cref="UnsetValue"/>. /// Class representing the <see cref="UnsetValue"/>.
/// </summary> /// </summary>

13
Perspex.Base/PerspexProperty`1.cs

@ -67,6 +67,19 @@ namespace Perspex
return (TValue)this.GetDefaultValue(typeof(T)); return (TValue)this.GetDefaultValue(typeof(T));
} }
/// <summary>
/// Overrides the validation function for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="validation">The validation function.</param>
public void OverrideValidation<T>(Func<T, TValue, TValue> validation) where T : PerspexObject
{
var f = validation != null ?
(o, v) => validation((T)o, (TValue)v) :
(Func<PerspexObject, object, object>)null;
this.OverrideValidation(typeof(T), f);
}
/// <summary> /// <summary>
/// Converts from a typed validation function to an untyped. /// Converts from a typed validation function to an untyped.
/// </summary> /// </summary>

56
Tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs

@ -14,7 +14,7 @@ namespace Perspex.Base.UnitTests
[Fact] [Fact]
public void SetValue_Causes_Validation() public void SetValue_Causes_Validation()
{ {
Class1 target = new Class1(); var target = new Class1();
target.SetValue(Class1.QuxProperty, 5); target.SetValue(Class1.QuxProperty, 5);
Assert.Throws<ArgumentOutOfRangeException>(() => target.SetValue(Class1.QuxProperty, 25)); Assert.Throws<ArgumentOutOfRangeException>(() => target.SetValue(Class1.QuxProperty, 25));
@ -24,7 +24,7 @@ namespace Perspex.Base.UnitTests
[Fact] [Fact]
public void SetValue_Causes_Coercion() public void SetValue_Causes_Coercion()
{ {
Class1 target = new Class1(); var target = new Class1();
target.SetValue(Class1.QuxProperty, 5); target.SetValue(Class1.QuxProperty, 5);
Assert.Equal(5, target.GetValue(Class1.QuxProperty)); Assert.Equal(5, target.GetValue(Class1.QuxProperty));
@ -37,7 +37,7 @@ namespace Perspex.Base.UnitTests
[Fact] [Fact]
public void Revalidate_Causes_Recoercion() public void Revalidate_Causes_Recoercion()
{ {
Class1 target = new Class1(); var target = new Class1();
target.SetValue(Class1.QuxProperty, 7); target.SetValue(Class1.QuxProperty, 7);
Assert.Equal(7, target.GetValue(Class1.QuxProperty)); Assert.Equal(7, target.GetValue(Class1.QuxProperty));
@ -45,10 +45,25 @@ namespace Perspex.Base.UnitTests
target.Revalidate(Class1.QuxProperty); target.Revalidate(Class1.QuxProperty);
} }
[Fact]
public void Validation_Can_Be_Overridden()
{
var target = new Class2();
Assert.Throws<ArgumentOutOfRangeException>(() => 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 private class Class1 : PerspexObject
{ {
public static readonly PerspexProperty<int> QuxProperty = public static readonly PerspexProperty<int> QuxProperty =
PerspexProperty.Register<Class1, int>("Qux", validate: Coerce); PerspexProperty.Register<Class1, int>("Qux", validate: Validate);
public Class1() public Class1()
{ {
@ -60,9 +75,9 @@ namespace Perspex.Base.UnitTests
public int ErrorQux { get; set; } 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(); throw new ArgumentOutOfRangeException();
} }
@ -70,5 +85,34 @@ namespace Perspex.Base.UnitTests
return Math.Min(Math.Max(value, 0), ((Class1)instance).MaxQux); return Math.Min(Math.Max(value, 0), ((Class1)instance).MaxQux);
} }
} }
private class Class2 : PerspexObject
{
public static readonly PerspexProperty<int> QuxProperty =
Class1.QuxProperty.AddOwner<Class2>();
static Class2()
{
QuxProperty.OverrideValidation<Class2>(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<Class3>(null);
}
}
} }
} }

Loading…
Cancel
Save