diff --git a/Perspex.Base/PerspexObject.cs b/Perspex.Base/PerspexObject.cs index 7ec12f13e2..db61e03932 100644 --- a/Perspex.Base/PerspexObject.cs +++ b/Perspex.Base/PerspexObject.cs @@ -647,16 +647,16 @@ namespace Perspex } /// - /// Forces the specified property to be re-coerced. + /// Forces the specified property to be revalidated. /// /// The property. - public void CoerceValue(PerspexProperty property) + public void Revalidate(PerspexProperty property) { PriorityValue value; if (this.values.TryGetValue(property, out value)) { - value.Coerce(); + value.Revalidate(); } } @@ -670,17 +670,17 @@ namespace Perspex } /// - /// Forces re-coercion of properties when a property value changes. + /// Forces revalidation of properties when a property value changes. /// - /// The property to that affects coercion. + /// The property to that affects validation. /// The affected properties. - protected static void AffectsCoercion(PerspexProperty property, params PerspexProperty[] affected) + protected static void AffectsValidation(PerspexProperty property, params PerspexProperty[] affected) { property.Changed.Subscribe(e => { foreach (var p in affected) { - e.Sender.CoerceValue(p); + e.Sender.Revalidate(p); } }); } @@ -700,14 +700,15 @@ namespace Perspex /// The . private PriorityValue CreatePriorityValue(PerspexProperty property) { - Func coerce = null; + Func validate = property.GetValidationFunc(this.GetType()); + Func validate2 = null; - if (property.Coerce != null) + if (validate != null) { - coerce = v => property.Coerce(this, v); + validate2 = v => validate(this, v); } - PriorityValue result = new PriorityValue(property.Name, property.PropertyType, coerce); + PriorityValue result = new PriorityValue(property.Name, property.PropertyType, validate2); result.Changed.Subscribe(x => { diff --git a/Perspex.Base/PerspexProperty.cs b/Perspex.Base/PerspexProperty.cs index 7cfec4387d..3c6333bb47 100644 --- a/Perspex.Base/PerspexProperty.cs +++ b/Perspex.Base/PerspexProperty.cs @@ -41,6 +41,8 @@ namespace Perspex /// private Subject changed = new Subject(); + private Func validate; + /// /// Initializes a new instance of the class. /// @@ -50,7 +52,7 @@ namespace Perspex /// The default value of the property. /// Whether the property inherits its value. /// The default binding mode for the property. - /// A coercion function. + /// A validation function. /// Whether the property is an attached property. public PerspexProperty( string name, @@ -59,7 +61,7 @@ namespace Perspex object defaultValue, bool inherits = false, BindingMode defaultBindingMode = BindingMode.Default, - Func coerce = null, + Func validate = null, bool isAttached = false) { Contract.Requires(name != null); @@ -72,7 +74,7 @@ namespace Perspex this.defaultValues.Add(ownerType, defaultValue); this.Inherits = inherits; this.DefaultBindingMode = defaultBindingMode; - this.Coerce = coerce; + this.validate = validate; this.IsAttached = isAttached; } @@ -116,14 +118,6 @@ namespace Perspex /// public BindingMode DefaultBindingMode { get; } - /// - /// Gets the property's coerce function. - /// - /// - /// The property's coerce function. - /// - public Func Coerce { get; } - /// /// Gets a value indicating whether this is an attached property. /// @@ -203,14 +197,14 @@ namespace Perspex /// The default value of the property. /// Whether the property inherits its value. /// The default binding mode for the property. - /// A coercion function. + /// A validation function. /// A public static PerspexProperty Register( string name, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, - Func coerce = null) + Func validate = null) where TOwner : PerspexObject { Contract.Requires(name != null); @@ -221,7 +215,7 @@ namespace Perspex defaultValue, inherits, defaultBindingMode, - coerce, + validate, false); PerspexObject.Register(typeof(TOwner), result); @@ -239,14 +233,14 @@ namespace Perspex /// The default value of the property. /// Whether the property inherits its value. /// The default binding mode for the property. - /// A coercion function. + /// A validation function. /// A public static PerspexProperty RegisterAttached( string name, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, - Func coerce = null) + Func validate = null) { Contract.Requires(name != null); @@ -256,7 +250,7 @@ namespace Perspex defaultValue, inherits, defaultBindingMode, - coerce, + validate, true); PerspexObject.Register(typeof(THost), result); @@ -274,7 +268,7 @@ namespace Perspex /// The default value of the property. /// Whether the property inherits its value. /// The default binding mode for the property. - /// A coercion function. + /// A validation function. /// A public static PerspexProperty RegisterAttached( string name, @@ -282,7 +276,7 @@ namespace Perspex TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, - Func coerce = null) + Func validate = null) { Contract.Requires(name != null); @@ -292,7 +286,7 @@ namespace Perspex defaultValue, inherits, defaultBindingMode, - coerce, + validate, true); PerspexObject.Register(typeof(THost), result); @@ -340,6 +334,18 @@ namespace Perspex return this.defaultValues[this.OwnerType]; } + /// + /// Gets the validation function for the property on the specified type. + /// + /// The type. + /// + /// The validation function, or null if no validation function registered for this type. + /// + public Func GetValidationFunc(Type type) + { + return this.validate; + } + /// /// Checks whether the is valid for the property. /// diff --git a/Perspex.Base/PerspexProperty`1.cs b/Perspex.Base/PerspexProperty`1.cs index ebd28467e2..16e327410f 100644 --- a/Perspex.Base/PerspexProperty`1.cs +++ b/Perspex.Base/PerspexProperty`1.cs @@ -22,7 +22,7 @@ namespace Perspex /// The default value of the property. /// Whether the property inherits its value. /// The default binding mode for the property. - /// A coercion function. + /// A validation function. /// Whether the property is an attached property. public PerspexProperty( string name, @@ -30,7 +30,7 @@ namespace Perspex TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.Default, - Func coerce = null, + Func validate = null, bool isAttached = false) : base( name, @@ -39,7 +39,7 @@ namespace Perspex defaultValue, inherits, defaultBindingMode, - Convert(coerce), + Convert(validate), isAttached) { Contract.Requires(name != null); @@ -68,10 +68,10 @@ namespace Perspex } /// - /// Converts from a typed coercion function to an untyped. + /// Converts from a typed validation function to an untyped. /// - /// The typed coercion function. - /// Te untyped coercion function. + /// The typed validation function. + /// The untyped validation function. private static Func Convert(Func f) { return f != null ? (o, v) => f(o, (TValue)v) : (Func)null; diff --git a/Perspex.Base/PriorityValue.cs b/Perspex.Base/PriorityValue.cs index 41e6c884fc..aa312507c0 100644 --- a/Perspex.Base/PriorityValue.cs +++ b/Perspex.Base/PriorityValue.cs @@ -54,23 +54,23 @@ namespace Perspex private object value; /// - /// The function used to coerce the value, if any. + /// The function used to validate the value, if any. /// - private Func coerce; + private Func validate; /// /// Initializes a new instance of the class. /// /// The name of the property. /// The value type. - /// An optional coercion function. - public PriorityValue(string name, Type valueType, Func coerce = null) + /// An optional validation function. + public PriorityValue(string name, Type valueType, Func validate = null) { this.name = name; this.valueType = valueType; this.value = PerspexProperty.UnsetValue; this.ValuePriority = int.MaxValue; - this.coerce = coerce; + this.validate = validate; } /// @@ -180,11 +180,11 @@ namespace Perspex } /// - /// Causes a re-coercion of the value. + /// Causes a revalidation of the value. /// - public void Coerce() + public void Revalidate() { - if (this.coerce != null) + if (this.validate != null) { PriorityLevel level; @@ -233,9 +233,9 @@ namespace Perspex var old = this.value; - if (this.coerce != null) + if (this.validate != null) { - value = this.coerce(value); + value = this.validate(value); } this.ValuePriority = priority; diff --git a/Perspex.Controls/Primitives/SelectingItemsControl.cs b/Perspex.Controls/Primitives/SelectingItemsControl.cs index 20b9c01fe0..5c1d18f8b3 100644 --- a/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -30,7 +30,7 @@ namespace Perspex.Controls.Primitives PerspexProperty.Register( nameof(SelectedIndex), defaultValue: -1, - coerce: CoerceSelectedIndex); + validate: ValidateSelectedIndex); /// /// Defines the property. @@ -38,7 +38,7 @@ namespace Perspex.Controls.Primitives public static readonly PerspexProperty SelectedItemProperty = PerspexProperty.Register( nameof(SelectedItem), - coerce: CoerceSelectedItem); + validate: ValidateSelectedItem); /// /// Event that should be raised by items that implement to @@ -258,7 +258,7 @@ namespace Perspex.Controls.Primitives /// The object on which the property has changed. /// The proposed value. /// The coerced value. - private static int CoerceSelectedIndex(PerspexObject o, int value) + private static int ValidateSelectedIndex(PerspexObject o, int value) { var control = o as SelectingItemsControl; @@ -293,7 +293,7 @@ namespace Perspex.Controls.Primitives /// The object on which the property has changed. /// The proposed value. /// The coerced value. - private static object CoerceSelectedItem(PerspexObject o, object value) + private static object ValidateSelectedItem(PerspexObject o, object value) { var control = o as SelectingItemsControl; diff --git a/Perspex.Controls/ScrollViewer.cs b/Perspex.Controls/ScrollViewer.cs index c46c9e0606..9ccf4d12eb 100644 --- a/Perspex.Controls/ScrollViewer.cs +++ b/Perspex.Controls/ScrollViewer.cs @@ -17,7 +17,7 @@ namespace Perspex.Controls PerspexProperty.Register("Extent"); public static readonly PerspexProperty OffsetProperty = - PerspexProperty.Register("Offset", coerce: CoerceOffset); + PerspexProperty.Register("Offset", validate: ValidateOffset); public static readonly PerspexProperty ViewportProperty = PerspexProperty.Register("Viewport"); @@ -51,8 +51,8 @@ namespace Perspex.Controls static ScrollViewer() { - PerspexObject.AffectsCoercion(ExtentProperty, OffsetProperty); - PerspexObject.AffectsCoercion(ViewportProperty, OffsetProperty); + PerspexObject.AffectsValidation(ExtentProperty, OffsetProperty); + PerspexObject.AffectsValidation(ViewportProperty, OffsetProperty); } public ScrollViewer() @@ -143,7 +143,7 @@ namespace Perspex.Controls return double.IsNaN(result) ? 0 : Math.Round(result); } - private static Vector CoerceOffset(PerspexObject o, Vector value) + private static Vector ValidateOffset(PerspexObject o, Vector value) { ScrollViewer scrollViewer = o as ScrollViewer; diff --git a/Perspex.Controls/TextBox.cs b/Perspex.Controls/TextBox.cs index 43258abb2b..59148088cb 100644 --- a/Perspex.Controls/TextBox.cs +++ b/Perspex.Controls/TextBox.cs @@ -27,13 +27,13 @@ namespace Perspex.Controls PerspexProperty.Register("AcceptsTab"); public static readonly PerspexProperty CaretIndexProperty = - PerspexProperty.Register("CaretIndex", coerce: CoerceCaretIndex); + PerspexProperty.Register("CaretIndex", validate: ValidateCaretIndex); public static readonly PerspexProperty SelectionStartProperty = - PerspexProperty.Register("SelectionStart", coerce: CoerceCaretIndex); + PerspexProperty.Register("SelectionStart", validate: ValidateCaretIndex); public static readonly PerspexProperty SelectionEndProperty = - PerspexProperty.Register("SelectionEnd", coerce: CoerceCaretIndex); + PerspexProperty.Register("SelectionEnd", validate: ValidateCaretIndex); public static readonly PerspexProperty TextProperty = TextBlock.TextProperty.AddOwner(); @@ -297,7 +297,7 @@ namespace Perspex.Controls } } - private static int CoerceCaretIndex(PerspexObject o, int value) + private static int ValidateCaretIndex(PerspexObject o, int value) { var text = o.GetValue(TextProperty); var length = (text != null) ? text.Length : 0; diff --git a/Perspex.Controls/TreeView.cs b/Perspex.Controls/TreeView.cs index cdad058371..4b1a068c07 100644 --- a/Perspex.Controls/TreeView.cs +++ b/Perspex.Controls/TreeView.cs @@ -17,7 +17,7 @@ namespace Perspex.Controls public class TreeView : ItemsControl { public static readonly PerspexProperty SelectedItemProperty = - PerspexProperty.Register("SelectedItem", coerce: CoerceSelectedItem); + PerspexProperty.Register("SelectedItem", validate: ValidateSelectedItem); static TreeView() { @@ -158,7 +158,7 @@ namespace Perspex.Controls e.Handled = true; } - private static object CoerceSelectedItem(PerspexObject o, object value) + private static object ValidateSelectedItem(PerspexObject o, object value) { //var control = o as SelectingItemsControl; diff --git a/Tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj b/Tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj index d1fa0f4dd0..52cee55ccb 100644 --- a/Tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj +++ b/Tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj @@ -80,7 +80,7 @@ - + diff --git a/Tests/Perspex.Base.UnitTests/PerspexObjectTests_Coercion.cs b/Tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs similarity index 62% rename from Tests/Perspex.Base.UnitTests/PerspexObjectTests_Coercion.cs rename to Tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs index bc1784720d..2bbbc13d3c 100644 --- a/Tests/Perspex.Base.UnitTests/PerspexObjectTests_Coercion.cs +++ b/Tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------- -// +// // Copyright 2015 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- @@ -7,22 +7,18 @@ namespace Perspex.Base.UnitTests { using System; - using System.Linq; - using System.Reactive.Linq; - using System.Reactive.Subjects; using Xunit; - public class PerspexObjectTests_Coercion + public class PerspexObjectTests_Validation { [Fact] - public void CoerceValue_Causes_Recoercion() + public void SetValue_Causes_Validation() { Class1 target = new Class1(); - target.SetValue(Class1.QuxProperty, 7); - Assert.Equal(7, target.GetValue(Class1.QuxProperty)); - target.MaxQux = 5; - target.CoerceValue(Class1.QuxProperty); + target.SetValue(Class1.QuxProperty, 5); + Assert.Throws(() => target.SetValue(Class1.QuxProperty, 25)); + Assert.Equal(5, target.GetValue(Class1.QuxProperty)); } [Fact] @@ -38,20 +34,39 @@ namespace Perspex.Base.UnitTests Assert.Equal(10, target.GetValue(Class1.QuxProperty)); } + [Fact] + public void Revalidate_Causes_Recoercion() + { + Class1 target = new Class1(); + + target.SetValue(Class1.QuxProperty, 7); + Assert.Equal(7, target.GetValue(Class1.QuxProperty)); + target.MaxQux = 5; + target.Revalidate(Class1.QuxProperty); + } + private class Class1 : PerspexObject { public static readonly PerspexProperty QuxProperty = - PerspexProperty.Register("Qux", coerce: Coerce); + PerspexProperty.Register("Qux", validate: Coerce); public Class1() { this.MaxQux = 10; + this.ErrorQux = 20; } public int MaxQux { get; set; } + public int ErrorQux { get; set; } + private static int Coerce(PerspexObject instance, int value) { + if (value > ((Class1)instance).ErrorQux) + { + throw new ArgumentOutOfRangeException(); + } + return Math.Min(Math.Max(value, 0), ((Class1)instance).MaxQux); } } diff --git a/Tests/Perspex.Base.UnitTests/PriorityValueTests.cs b/Tests/Perspex.Base.UnitTests/PriorityValueTests.cs index 2fc273dd65..4d4fc77026 100644 --- a/Tests/Perspex.Base.UnitTests/PriorityValueTests.cs +++ b/Tests/Perspex.Base.UnitTests/PriorityValueTests.cs @@ -290,7 +290,7 @@ namespace Perspex.Base.UnitTests } [Fact] - public void Coerce_Should_ReCoerce_Value() + public void Revalidate_Should_ReCoerce_Value() { var max = 10; var target = new PriorityValue("Test", typeof(int), x => Math.Min((int)x, max)); @@ -302,7 +302,7 @@ namespace Perspex.Base.UnitTests source.OnNext(15); Assert.Equal(10, target.Value); max = 12; - target.Coerce(); + target.Revalidate(); Assert.Equal(12, target.Value); }