diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index fdb04b6dfc..d1df5fa5e3 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -18,12 +18,14 @@ namespace Avalonia /// The class that is registering the property. /// The property metadata. /// Whether the property inherits its value. + /// A value validation callback. public AttachedProperty( string name, - Type ownerType, + Type ownerType, StyledPropertyMetadata metadata, - bool inherits = false) - : base(name, ownerType, metadata, inherits) + bool inherits = false, + Func validate = null) + : base(name, ownerType, metadata, inherits, validate) { } diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 8e5716a5bf..e1d4a23441 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -304,22 +304,25 @@ namespace Avalonia /// Whether the property inherits its value. /// The default binding mode for the property. /// A value validation callback. + /// A value coercion callback. /// A public static AttachedProperty RegisterAttached( string name, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, - Func validate = null) + Func validate = null, + Func coerce = null) where THost : IAvaloniaObject { Contract.Requires(name != null); var metadata = new StyledPropertyMetadata( defaultValue, - defaultBindingMode: defaultBindingMode); + defaultBindingMode: defaultBindingMode, + coerce: coerce); - var result = new AttachedProperty(name, typeof(TOwner), metadata, inherits); + var result = new AttachedProperty(name, typeof(TOwner), metadata, inherits, validate); var registry = AvaloniaPropertyRegistry.Instance; registry.Register(typeof(TOwner), result); registry.RegisterAttached(typeof(THost), result); @@ -336,22 +339,27 @@ namespace Avalonia /// The default value of the property. /// Whether the property inherits its value. /// The default binding mode for the property. + /// A value validation callback. + /// A value coercion callback. /// A public static AttachedProperty RegisterAttached( string name, Type ownerType, TValue defaultValue = default(TValue), bool inherits = false, - BindingMode defaultBindingMode = BindingMode.OneWay) + BindingMode defaultBindingMode = BindingMode.OneWay, + Func validate = null, + Func coerce = null) where THost : IAvaloniaObject { Contract.Requires(name != null); var metadata = new StyledPropertyMetadata( defaultValue, - defaultBindingMode: defaultBindingMode); + defaultBindingMode: defaultBindingMode, + coerce: coerce); - var result = new AttachedProperty(name, ownerType, metadata, inherits); + var result = new AttachedProperty(name, ownerType, metadata, inherits, validate); var registry = AvaloniaPropertyRegistry.Instance; registry.Register(ownerType, result); registry.RegisterAttached(typeof(THost), result); diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 6f121afbef..38ebbe5bf9 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -358,7 +358,11 @@ namespace Avalonia.Controls /// private static bool SharedSizeGroupPropertyValueValid(string value) { - Contract.Requires(value != null); + // null is default value + if (value == null) + { + return true; + } string id = (string)value; diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs index 8d8dbb03a2..11f5a66400 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs @@ -20,6 +20,16 @@ namespace Avalonia.Base.UnitTests Assert.Equal(100, target.Foo); } + [Fact] + public void Coerces_Set_Value_Attached() + { + var target = new Class1(); + + target.SetValue(Class1.AttachedProperty, 150); + + Assert.Equal(100, target.GetValue(Class1.AttachedProperty)); + } + [Fact] public void Coerces_Bound_Value() { @@ -98,6 +108,12 @@ namespace Avalonia.Base.UnitTests defaultValue: 11, coerce: CoerceFoo); + public static readonly AttachedProperty AttachedProperty = + AvaloniaProperty.RegisterAttached( + "Attached", + defaultValue: 11, + coerce: CoerceFoo); + public int Foo { get => GetValue(FooProperty); diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs index 70de7b449f..9e48c79106 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -3,6 +3,7 @@ using System; using System.Reactive.Subjects; +using Avalonia.Controls; using Xunit; namespace Avalonia.Base.UnitTests @@ -34,6 +35,14 @@ namespace Avalonia.Base.UnitTests Assert.Throws(() => target.SetValue(Class1.FooProperty, 101)); } + [Fact] + public void SetValue_Throws_If_Fails_Validation_Attached() + { + var target = new Class1(); + + Assert.Throws(() => target.SetValue(Class1.AttachedProperty, 101)); + } + [Fact] public void Reverts_To_DefaultValue_If_Binding_Fails_Validation() { @@ -69,6 +78,12 @@ namespace Avalonia.Base.UnitTests defaultValue: 11, validate: ValidateFoo); + public static readonly AttachedProperty AttachedProperty = + AvaloniaProperty.RegisterAttached( + "Attached", + defaultValue: 11, + validate: ValidateFoo); + public static bool ValidateFoo(int value) { return value < 100;