diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index a39b8e6e1d..31a7f21a1b 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -257,6 +257,7 @@ 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 method that gets called before and after the property starts being notified on an /// object; the bool argument will be true before and false afterwards. This callback is @@ -268,6 +269,7 @@ namespace Avalonia TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, + Func validate = null, Action notifying = null) where TOwner : IAvaloniaObject { @@ -282,6 +284,7 @@ namespace Avalonia typeof(TOwner), metadata, inherits, + validate, notifying); AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result); return result; @@ -297,12 +300,14 @@ 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 public static AttachedProperty RegisterAttached( string name, TValue defaultValue = default(TValue), bool inherits = false, - BindingMode defaultBindingMode = BindingMode.OneWay) + BindingMode defaultBindingMode = BindingMode.OneWay, + Func validate = null) where THost : IAvaloniaObject { Contract.Requires(name != null); diff --git a/src/Avalonia.Base/BoxedValue.cs b/src/Avalonia.Base/BoxedValue.cs deleted file mode 100644 index 5fc515f299..0000000000 --- a/src/Avalonia.Base/BoxedValue.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -namespace Avalonia -{ - /// - /// Represents boxed value of type . - /// - /// Type of stored value. - internal readonly struct BoxedValue - { - public BoxedValue(T value) - { - Boxed = value; - Typed = value; - } - - /// - /// Boxed value. - /// - public object Boxed { get; } - - /// - /// Typed value. - /// - public T Typed { get; } - } -} diff --git a/src/Avalonia.Base/PropertyStore/BindingEntry.cs b/src/Avalonia.Base/PropertyStore/BindingEntry.cs index 2a7cf098a5..a53dda2aef 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntry.cs @@ -77,6 +77,11 @@ namespace Avalonia.PropertyStore private void UpdateValue(BindingValue value) { + if (value.HasValue && Property.ValidateValue?.Invoke(value.Value) == false) + { + value = Property.GetDefaultValue(_owner.GetType()); + } + if (value.Type == BindingValueType.DoNothing) { return; diff --git a/src/Avalonia.Base/StyledProperty.cs b/src/Avalonia.Base/StyledProperty.cs index 4eb85a046e..62443b424c 100644 --- a/src/Avalonia.Base/StyledProperty.cs +++ b/src/Avalonia.Base/StyledProperty.cs @@ -17,14 +17,16 @@ namespace Avalonia /// The type of the class that registers the property. /// The property metadata. /// Whether the property inherits its value. + /// A value validation callback. /// A callback. public StyledProperty( string name, Type ownerType, StyledPropertyMetadata metadata, bool inherits = false, + Func validate = null, Action notifying = null) - : base(name, ownerType, metadata, inherits, notifying) + : base(name, ownerType, metadata, inherits, validate, notifying) { } diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index 129b1f3c12..8e7bfd6467 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections.Generic; using System.Diagnostics; using Avalonia.Data; using Avalonia.Reactive; @@ -23,12 +22,14 @@ namespace Avalonia /// The type of the class that registers the property. /// The property metadata. /// Whether the property inherits its value. + /// A value validation callback. /// A callback. protected StyledPropertyBase( string name, Type ownerType, StyledPropertyMetadata metadata, bool inherits = false, + Func validate = null, Action notifying = null) : base(name, ownerType, metadata, notifying) { @@ -41,6 +42,13 @@ namespace Avalonia } _inherits = inherits; + ValidateValue = validate; + + if (validate?.Invoke(metadata.DefaultValue) == false) + { + throw new ArgumentException( + $"'{metadata.DefaultValue}' is not a valid default value for '{name}'."); + } } /// @@ -62,6 +70,11 @@ namespace Avalonia /// public override bool Inherits => _inherits; + /// + /// Gets the value validation callback for the property. + /// + public Func ValidateValue { get; } + /// /// Gets the default value for the property on the specified type. /// @@ -71,7 +84,7 @@ namespace Avalonia { Contract.Requires(type != null); - return GetMetadata(type).DefaultValue.Typed; + return GetMetadata(type).DefaultValue; } /// @@ -123,6 +136,15 @@ namespace Avalonia /// The metadata. public void OverrideMetadata(Type type, StyledPropertyMetadata metadata) { + if (ValidateValue != null) + { + if (!ValidateValue(metadata.DefaultValue)) + { + throw new ArgumentException( + $"'{metadata.DefaultValue}' is not a valid default value for '{Name}'."); + } + } + base.OverrideMetadata(type, metadata); } @@ -209,7 +231,7 @@ namespace Avalonia { Contract.Requires(type != null); - return GetMetadata(type).DefaultValue.Boxed; + return GetMetadata(type).DefaultValue; } [DebuggerHidden] diff --git a/src/Avalonia.Base/StyledPropertyMetadata`1.cs b/src/Avalonia.Base/StyledPropertyMetadata`1.cs index d4ce137e0a..50236e2c7c 100644 --- a/src/Avalonia.Base/StyledPropertyMetadata`1.cs +++ b/src/Avalonia.Base/StyledPropertyMetadata`1.cs @@ -12,25 +12,27 @@ namespace Avalonia /// public class StyledPropertyMetadata : PropertyMetadata, IStyledPropertyMetadata { + private Optional _defaultValue; + /// /// Initializes a new instance of the class. /// /// The default value of the property. /// The default binding mode. public StyledPropertyMetadata( - TValue defaultValue = default, + Optional defaultValue = default, BindingMode defaultBindingMode = BindingMode.Default) : base(defaultBindingMode) { - DefaultValue = new BoxedValue(defaultValue); + _defaultValue = defaultValue; } /// /// Gets the default value for the property. /// - internal BoxedValue DefaultValue { get; private set; } + internal TValue DefaultValue => _defaultValue.ValueOrDefault(); - object IStyledPropertyMetadata.DefaultValue => DefaultValue.Boxed; + object IStyledPropertyMetadata.DefaultValue => DefaultValue; /// public override void Merge(PropertyMetadata baseMetadata, AvaloniaProperty property) @@ -39,9 +41,9 @@ namespace Avalonia if (baseMetadata is StyledPropertyMetadata src) { - if (DefaultValue.Boxed == null) + if (!_defaultValue.HasValue) { - DefaultValue = src.DefaultValue; + _defaultValue = src.DefaultValue; } } } diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 44ee1c4c8f..09961b399b 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -66,6 +66,11 @@ namespace Avalonia public void SetValue(StyledPropertyBase property, T value, BindingPriority priority) { + if (property.ValidateValue?.Invoke(value) == false) + { + throw new ArgumentException($"{value} is not a valid value for '{property.Name}."); + } + if (_values.TryGetValue(property, out var slot)) { SetExisting(slot, property, value, priority); diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index a217d02ecd..86133d5fdb 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -201,21 +201,13 @@ namespace Avalonia.Controls public static readonly StyledProperty ColumnHeaderHeightProperty = AvaloniaProperty.Register( nameof(ColumnHeaderHeight), - defaultValue: double.NaN/*, - validate: ValidateColumnHeaderHeight*/); + defaultValue: double.NaN, + validate: IsValidColumnHeaderHeight); - private static double ValidateColumnHeaderHeight(DataGrid grid, double value) + private static bool IsValidColumnHeaderHeight(double value) { - if (value < DATAGRID_minimumColumnHeaderHeight) - { - throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(ColumnHeaderHeight), DATAGRID_minimumColumnHeaderHeight); - } - if (value > DATAGRID_maxHeadersThickness) - { - throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(ColumnHeaderHeight), DATAGRID_maxHeadersThickness); - } - - return value; + return double.IsNaN(value) || + (value >= DATAGRID_minimumColumnHeaderHeight && value <= DATAGRID_maxHeadersThickness); } /// @@ -261,8 +253,8 @@ namespace Avalonia.Controls public static readonly StyledProperty FrozenColumnCountProperty = AvaloniaProperty.Register( - nameof(FrozenColumnCount)/*, - validate: ValidateFrozenColumnCount*/); + nameof(FrozenColumnCount), + validate: ValidateFrozenColumnCount); /// /// Gets or sets the number of columns that the user cannot scroll horizontally. @@ -273,15 +265,7 @@ namespace Avalonia.Controls set { SetValue(FrozenColumnCountProperty, value); } } - private static int ValidateFrozenColumnCount(DataGrid grid, int value) - { - if (value < 0) - { - throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(FrozenColumnCount), 0); - } - - return value; - } + private static bool ValidateFrozenColumnCount(int value) => value >= 0; public static readonly StyledProperty GridLinesVisibilityProperty = AvaloniaProperty.Register(nameof(GridLinesVisibility)); @@ -395,30 +379,12 @@ namespace Avalonia.Controls public static readonly StyledProperty MaxColumnWidthProperty = AvaloniaProperty.Register( nameof(MaxColumnWidth), - defaultValue: DATAGRID_defaultMaxColumnWidth/*, - validate: ValidateMaxColumnWidth*/); + defaultValue: DATAGRID_defaultMaxColumnWidth, + validate: IsValidColumnWidth); - private static double ValidateMaxColumnWidth(DataGrid grid, double value) + private static bool IsValidColumnWidth(double value) { - if (double.IsNaN(value)) - { - throw DataGridError.DataGrid.ValueCannotBeSetToNAN(nameof(MaxColumnWidth)); - } - if (value < 0) - { - throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(MaxColumnWidth), 0); - } - if (grid.MinColumnWidth > value) - { - throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(MaxColumnWidth), nameof(MinColumnWidth)); - } - - if (value < 0) - { - throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(FrozenColumnCount), 0); - } - - return value; + return !double.IsNaN(value) && value > 0; } /// @@ -433,29 +399,12 @@ namespace Avalonia.Controls public static readonly StyledProperty MinColumnWidthProperty = AvaloniaProperty.Register( nameof(MinColumnWidth), - defaultValue: DATAGRID_defaultMinColumnWidth/*, - validate: ValidateMinColumnWidth*/); + defaultValue: DATAGRID_defaultMinColumnWidth, + validate: IsValidMinColumnWidth); - private static double ValidateMinColumnWidth(DataGrid grid, double value) + private static bool IsValidMinColumnWidth(double value) { - if (double.IsNaN(value)) - { - throw DataGridError.DataGrid.ValueCannotBeSetToNAN(nameof(MinColumnWidth)); - } - if (value < 0) - { - throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(MinColumnWidth), 0); - } - if (double.IsPositiveInfinity(value)) - { - throw DataGridError.DataGrid.ValueCannotBeSetToInfinity(nameof(MinColumnWidth)); - } - if (grid.MaxColumnWidth < value) - { - throw DataGridError.DataGrid.ValueMustBeLessThanOrEqualTo(nameof(value), nameof(MinColumnWidth), nameof(MaxColumnWidth)); - } - - return value; + return !double.IsNaN(value) && !double.IsPositiveInfinity(value) && value >= 0; } /// @@ -482,20 +431,13 @@ namespace Avalonia.Controls public static readonly StyledProperty RowHeightProperty = AvaloniaProperty.Register( nameof(RowHeight), - defaultValue: double.NaN/*, - validate: ValidateRowHeight*/); - private static double ValidateRowHeight(DataGrid grid, double value) + defaultValue: double.NaN, + validate: IsValidRowHeight); + private static bool IsValidRowHeight(double value) { - if (value < DataGridRow.DATAGRIDROW_minimumHeight) - { - throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(RowHeight), 0); - } - if (value > DataGridRow.DATAGRIDROW_maximumHeight) - { - throw DataGridError.DataGrid.ValueMustBeLessThanOrEqualTo(nameof(value), nameof(RowHeight), DataGridRow.DATAGRIDROW_maximumHeight); - } - - return value; + return double.IsNaN(value) || + (value >= DataGridRow.DATAGRIDROW_minimumHeight && + value <= DataGridRow.DATAGRIDROW_maximumHeight); } /// @@ -510,20 +452,13 @@ namespace Avalonia.Controls public static readonly StyledProperty RowHeaderWidthProperty = AvaloniaProperty.Register( nameof(RowHeaderWidth), - defaultValue: double.NaN/*, - validate: ValidateRowHeaderWidth*/); - private static double ValidateRowHeaderWidth(DataGrid grid, double value) + defaultValue: double.NaN, + validate: IsValidRowHeaderWidth); + private static bool IsValidRowHeaderWidth(double value) { - if (value < DATAGRID_minimumRowHeaderWidth) - { - throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(RowHeaderWidth), DATAGRID_minimumRowHeaderWidth); - } - if (value > DATAGRID_maxHeadersThickness) - { - throw DataGridError.DataGrid.ValueMustBeLessThanOrEqualTo(nameof(value), nameof(RowHeaderWidth), DATAGRID_maxHeadersThickness); - } - - return value; + return double.IsNaN(value) || + (value >= DATAGRID_minimumRowHeaderWidth && + value <= DATAGRID_maxHeadersThickness); } /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index bede7f481e..69dfed761f 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -67,26 +67,12 @@ namespace Avalonia.Controls public static readonly StyledProperty SublevelIndentProperty = AvaloniaProperty.Register( nameof(SublevelIndent), - defaultValue: DataGrid.DATAGRID_defaultRowGroupSublevelIndent/*, - validate: ValidateSublevelIndent*/); + defaultValue: DataGrid.DATAGRID_defaultRowGroupSublevelIndent, + validate: IsValidSublevelIndent); - private static double ValidateSublevelIndent(DataGridRowGroupHeader header, double value) + private static bool IsValidSublevelIndent(double value) { - // We don't need to revert to the old value if our input is bad because we never read this property value - if (double.IsNaN(value)) - { - throw DataGridError.DataGrid.ValueCannotBeSetToNAN(nameof(SublevelIndent)); - } - else if (double.IsInfinity(value)) - { - throw DataGridError.DataGrid.ValueCannotBeSetToInfinity(nameof(SublevelIndent)); - } - else if (value < 0) - { - throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(SublevelIndent), 0); - } - - return value; + return !double.IsNaN(value) && !double.IsInfinity(value) && value >= 0; } /// diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index 1e1a62f4a4..6deddef0d0 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -377,8 +377,8 @@ namespace Avalonia.Controls /// dependency property. public static readonly StyledProperty MinimumPrefixLengthProperty = AvaloniaProperty.Register( - nameof(MinimumPrefixLength), 1/*, - validate: ValidateMinimumPrefixLength*/); + nameof(MinimumPrefixLength), 1, + validate: IsValidMinimumPrefixLength); /// /// Identifies the @@ -391,8 +391,8 @@ namespace Avalonia.Controls public static readonly StyledProperty MinimumPopulateDelayProperty = AvaloniaProperty.Register( nameof(MinimumPopulateDelay), - TimeSpan.Zero/*, - validate: ValidateMinimumPopulateDelay*/); + TimeSpan.Zero, + validate: IsValidMinimumPopulateDelay); /// /// Identifies the @@ -405,8 +405,8 @@ namespace Avalonia.Controls public static readonly StyledProperty MaxDropDownHeightProperty = AvaloniaProperty.Register( nameof(MaxDropDownHeight), - double.PositiveInfinity/*, - validate: ValidateMaxDropDownHeight*/); + double.PositiveInfinity, + validate: IsValidMaxDropDownHeight); /// /// Identifies the @@ -494,8 +494,8 @@ namespace Avalonia.Controls public static readonly StyledProperty FilterModeProperty = AvaloniaProperty.Register( nameof(FilterMode), - defaultValue: AutoCompleteFilterMode.StartsWith/*, - validate: ValidateFilterMode*/); + defaultValue: AutoCompleteFilterMode.StartsWith, + validate: IsValidFilterMode); /// /// Identifies the @@ -546,26 +546,11 @@ namespace Avalonia.Controls o => o.AsyncPopulator, (o, v) => o.AsyncPopulator = v); - private static int ValidateMinimumPrefixLength(AutoCompleteBox control, int value) - { - Contract.Requires(value >= -1); - - return value; - } - - private static TimeSpan ValidateMinimumPopulateDelay(AutoCompleteBox control, TimeSpan value) - { - Contract.Requires(value.TotalMilliseconds >= 0.0); - - return value; - } + private static bool IsValidMinimumPrefixLength(int value) => value >= -1; - private static double ValidateMaxDropDownHeight(AutoCompleteBox control, double value) - { - Contract.Requires(value >= 0.0); + private static bool IsValidMinimumPopulateDelay(TimeSpan value) => value.TotalMilliseconds >= 0.0; - return value; - } + private static bool IsValidMaxDropDownHeight(double value) => value >= 0.0; private static bool IsValidFilterMode(AutoCompleteFilterMode mode) { @@ -590,12 +575,6 @@ namespace Avalonia.Controls return false; } } - private static AutoCompleteFilterMode ValidateFilterMode(AutoCompleteBox control, AutoCompleteFilterMode value) - { - Contract.Requires(IsValidFilterMode(value)); - - return value; - } /// /// Handle the change of the IsEnabled property. diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index 58b7d7cb47..94f8ad41a0 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -351,8 +351,9 @@ namespace Avalonia.Controls public static readonly StyledProperty DisplayModeProperty = AvaloniaProperty.Register( - nameof(DisplayMode)/*, - validate: ValidateDisplayMode*/); + nameof(DisplayMode), + validate: IsValidDisplayMode); + /// /// Gets or sets a value indicating whether the calendar is displayed in /// months, years, or decades. @@ -417,17 +418,6 @@ namespace Avalonia.Controls } OnDisplayModeChanged(new CalendarModeChangedEventArgs((CalendarMode)e.OldValue, mode)); } - private static CalendarMode ValidateDisplayMode(Calendar o, CalendarMode mode) - { - if(IsValidDisplayMode(mode)) - { - return mode; - } - else - { - throw new ArgumentOutOfRangeException(nameof(mode), "Invalid DisplayMode"); - } - } private static bool IsValidDisplayMode(CalendarMode mode) { return mode == CalendarMode.Month diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs index aa3a8fae3d..b4d4fed9fc 100644 --- a/src/Avalonia.Controls/Calendar/DatePicker.cs +++ b/src/Avalonia.Controls/Calendar/DatePicker.cs @@ -189,14 +189,14 @@ namespace Avalonia.Controls public static readonly StyledProperty SelectedDateFormatProperty = AvaloniaProperty.Register( nameof(SelectedDateFormat), - defaultValue: DatePickerFormat.Short/*, - validate: ValidateSelectedDateFormat*/); + defaultValue: DatePickerFormat.Short, + validate: IsValidSelectedDateFormat); public static readonly StyledProperty CustomDateFormatStringProperty = AvaloniaProperty.Register( nameof(CustomDateFormatString), - defaultValue: "d"/*, - validate: ValidateDateFormatString*/); + defaultValue: "d", + validate: IsValidDateFormatString); public static readonly DirectProperty TextProperty = AvaloniaProperty.RegisterDirect( @@ -1146,27 +1146,9 @@ namespace Avalonia.Controls || value == DatePickerFormat.Short || value == DatePickerFormat.Custom; } - private static DatePickerFormat ValidateSelectedDateFormat(DatePicker dp, DatePickerFormat format) + private static bool IsValidDateFormatString(string formatString) { - if(IsValidSelectedDateFormat(format)) - { - return format; - } - else - { - throw new ArgumentOutOfRangeException(nameof(format), "DatePickerFormat value is not valid."); - } - } - private static string ValidateDateFormatString(DatePicker dp, string formatString) - { - if(string.IsNullOrWhiteSpace(formatString)) - { - throw new ArgumentException("DateFormatString value is not valid.", nameof(formatString)); - } - else - { - return formatString; - } + return !string.IsNullOrWhiteSpace(formatString); } private static DateTime DiscardDayTime(DateTime d) { diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index eae6cf5e30..6f121afbef 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -356,7 +356,7 @@ namespace Avalonia.Controls /// b) contains only letters, digits and underscore ('_'). /// c) does not start with a digit. /// - private static string SharedSizeGroupPropertyValueValid(Control _, string value) + private static bool SharedSizeGroupPropertyValueValid(string value) { Contract.Requires(value != null); @@ -380,11 +380,11 @@ namespace Avalonia.Controls if (i == id.Length) { - return value; + return true; } } - throw new ArgumentException("Invalid SharedSizeGroup string."); + return false; } /// @@ -750,8 +750,8 @@ namespace Avalonia.Controls /// public static readonly AttachedProperty SharedSizeGroupProperty = AvaloniaProperty.RegisterAttached( - "SharedSizeGroup"/*, - validate: SharedSizeGroupPropertyValueValid*/); + "SharedSizeGroup", + validate: SharedSizeGroupPropertyValueValid); /// /// Static ctor. Used for static registration of properties. diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 7b57288e76..1781067abb 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -2740,12 +2740,8 @@ namespace Avalonia.Controls public static readonly AttachedProperty ColumnProperty = AvaloniaProperty.RegisterAttached( "Column", - defaultValue: 0/*, - validate: (_, v) => - { - if (v >= 0) return v; - else throw new ArgumentException("Invalid Grid.Column value."); - }*/); + defaultValue: 0, + validate: v => v >= 0); /// /// Row property. This is an attached property. @@ -2761,12 +2757,8 @@ namespace Avalonia.Controls public static readonly AttachedProperty RowProperty = AvaloniaProperty.RegisterAttached( "Row", - defaultValue: 0/*, - validate: (_, v) => - { - if (v >= 0) return v; - else throw new ArgumentException("Invalid Grid.Row value."); - }*/); + defaultValue: 0, + validate: v => v >= 0); /// /// ColumnSpan property. This is an attached property. @@ -2781,12 +2773,8 @@ namespace Avalonia.Controls public static readonly AttachedProperty ColumnSpanProperty = AvaloniaProperty.RegisterAttached( "ColumnSpan", - defaultValue: 1/*, - validate: (_, v) => - { - if (v >= 1) return v; - else throw new ArgumentException("Invalid Grid.ColumnSpan value."); - }*/); + defaultValue: 1, + validate: v => v >= 0); /// /// RowSpan property. This is an attached property. @@ -2801,12 +2789,8 @@ namespace Avalonia.Controls public static readonly AttachedProperty RowSpanProperty = AvaloniaProperty.RegisterAttached( "RowSpan", - defaultValue: 1/*, - validate: (_, v) => - { - if (v >= 1) return v; - else throw new ArgumentException("Invalid Grid.RowSpan value."); - }*/); + defaultValue: 1, + validate: v => v >= 0); /// /// IsSharedSizeScope property marks scoping element for shared size. diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs new file mode 100644 index 0000000000..70de7b449f --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -0,0 +1,82 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reactive.Subjects; +using Xunit; + +namespace Avalonia.Base.UnitTests +{ + public class AvaloniaObjectTests_Validation + { + [Fact] + public void Registration_Throws_If_DefaultValue_Fails_Validation() + { + Assert.Throws(() => + new StyledProperty( + "BadDefault", + typeof(Class1), + new StyledPropertyMetadata(101), + validate: Class1.ValidateFoo)); + } + + [Fact] + public void Metadata_Override_Throws_If_DefaultValue_Fails_Validation() + { + Assert.Throws(() => Class1.FooProperty.OverrideDefaultValue(101)); + } + + [Fact] + public void SetValue_Throws_If_Fails_Validation() + { + var target = new Class1(); + + Assert.Throws(() => target.SetValue(Class1.FooProperty, 101)); + } + + [Fact] + public void Reverts_To_DefaultValue_If_Binding_Fails_Validation() + { + var target = new Class1(); + var source = new Subject(); + + target.Bind(Class1.FooProperty, source); + source.OnNext(150); + + Assert.Equal(11, target.GetValue(Class1.FooProperty)); + } + + [Fact] + public void Reverts_To_DefaultValue_Even_In_Presence_Of_Other_Bindings() + { + var target = new Class1(); + var source1 = new Subject(); + var source2 = new Subject(); + + target.Bind(Class1.FooProperty, source1); + target.Bind(Class1.FooProperty, source2); + source1.OnNext(42); + source2.OnNext(150); + + Assert.Equal(11, target.GetValue(Class1.FooProperty)); + } + + private class Class1 : AvaloniaObject + { + public static readonly StyledProperty FooProperty = + AvaloniaProperty.Register( + "Qux", + defaultValue: 11, + validate: ValidateFoo); + + public static bool ValidateFoo(int value) + { + return value < 100; + } + } + + private class Class2 : AvaloniaObject + { + } + } +}