Browse Source

Added WPF-style validation for AvaloniaProperty.

pull/3287/head
Steven Kirk 7 years ago
parent
commit
aa81db75a0
  1. 7
      src/Avalonia.Base/AvaloniaProperty.cs
  2. 28
      src/Avalonia.Base/BoxedValue.cs
  3. 5
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  4. 4
      src/Avalonia.Base/StyledProperty.cs
  5. 28
      src/Avalonia.Base/StyledPropertyBase.cs
  6. 14
      src/Avalonia.Base/StyledPropertyMetadata`1.cs
  7. 5
      src/Avalonia.Base/ValueStore.cs
  8. 121
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  9. 22
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  10. 43
      src/Avalonia.Controls/AutoCompleteBox.cs
  11. 16
      src/Avalonia.Controls/Calendar/Calendar.cs
  12. 30
      src/Avalonia.Controls/Calendar/DatePicker.cs
  13. 10
      src/Avalonia.Controls/DefinitionBase.cs
  14. 32
      src/Avalonia.Controls/Grid.cs
  15. 82
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs

7
src/Avalonia.Base/AvaloniaProperty.cs

@ -257,6 +257,7 @@ namespace Avalonia
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="notifying">
/// 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<TValue, bool> validate = null,
Action<IAvaloniaObject, bool> 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
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A value validation callback.</param>
/// <returns>A <see cref="AvaloniaProperty{TValue}"/></returns>
public static AttachedProperty<TValue> RegisterAttached<TOwner, THost, TValue>(
string name,
TValue defaultValue = default(TValue),
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay)
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TValue, bool> validate = null)
where THost : IAvaloniaObject
{
Contract.Requires<ArgumentNullException>(name != null);

28
src/Avalonia.Base/BoxedValue.cs

@ -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
{
/// <summary>
/// Represents boxed value of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">Type of stored value.</typeparam>
internal readonly struct BoxedValue<T>
{
public BoxedValue(T value)
{
Boxed = value;
Typed = value;
}
/// <summary>
/// Boxed value.
/// </summary>
public object Boxed { get; }
/// <summary>
/// Typed value.
/// </summary>
public T Typed { get; }
}
}

5
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@ -77,6 +77,11 @@ namespace Avalonia.PropertyStore
private void UpdateValue(BindingValue<T> value)
{
if (value.HasValue && Property.ValidateValue?.Invoke(value.Value) == false)
{
value = Property.GetDefaultValue(_owner.GetType());
}
if (value.Type == BindingValueType.DoNothing)
{
return;

4
src/Avalonia.Base/StyledProperty.cs

@ -17,14 +17,16 @@ namespace Avalonia
/// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="metadata">The property metadata.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="notifying">A <see cref="AvaloniaProperty.Notifying"/> callback.</param>
public StyledProperty(
string name,
Type ownerType,
StyledPropertyMetadata<TValue> metadata,
bool inherits = false,
Func<TValue, bool> validate = null,
Action<IAvaloniaObject, bool> notifying = null)
: base(name, ownerType, metadata, inherits, notifying)
: base(name, ownerType, metadata, inherits, validate, notifying)
{
}

28
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
/// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="metadata">The property metadata.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="notifying">A <see cref="AvaloniaProperty.Notifying"/> callback.</param>
protected StyledPropertyBase(
string name,
Type ownerType,
StyledPropertyMetadata<TValue> metadata,
bool inherits = false,
Func<TValue, bool> validate = null,
Action<IAvaloniaObject, bool> 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}'.");
}
}
/// <summary>
@ -62,6 +70,11 @@ namespace Avalonia
/// </value>
public override bool Inherits => _inherits;
/// <summary>
/// Gets the value validation callback for the property.
/// </summary>
public Func<TValue, bool> ValidateValue { get; }
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>
@ -71,7 +84,7 @@ namespace Avalonia
{
Contract.Requires<ArgumentNullException>(type != null);
return GetMetadata(type).DefaultValue.Typed;
return GetMetadata(type).DefaultValue;
}
/// <summary>
@ -123,6 +136,15 @@ namespace Avalonia
/// <param name="metadata">The metadata.</param>
public void OverrideMetadata(Type type, StyledPropertyMetadata<TValue> 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<ArgumentNullException>(type != null);
return GetMetadata(type).DefaultValue.Boxed;
return GetMetadata(type).DefaultValue;
}
[DebuggerHidden]

14
src/Avalonia.Base/StyledPropertyMetadata`1.cs

@ -12,25 +12,27 @@ namespace Avalonia
/// </summary>
public class StyledPropertyMetadata<TValue> : PropertyMetadata, IStyledPropertyMetadata
{
private Optional<TValue> _defaultValue;
/// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyMetadata{TValue}"/> class.
/// </summary>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="defaultBindingMode">The default binding mode.</param>
public StyledPropertyMetadata(
TValue defaultValue = default,
Optional<TValue> defaultValue = default,
BindingMode defaultBindingMode = BindingMode.Default)
: base(defaultBindingMode)
{
DefaultValue = new BoxedValue<TValue>(defaultValue);
_defaultValue = defaultValue;
}
/// <summary>
/// Gets the default value for the property.
/// </summary>
internal BoxedValue<TValue> DefaultValue { get; private set; }
internal TValue DefaultValue => _defaultValue.ValueOrDefault();
object IStyledPropertyMetadata.DefaultValue => DefaultValue.Boxed;
object IStyledPropertyMetadata.DefaultValue => DefaultValue;
/// <inheritdoc/>
public override void Merge(PropertyMetadata baseMetadata, AvaloniaProperty property)
@ -39,9 +41,9 @@ namespace Avalonia
if (baseMetadata is StyledPropertyMetadata<TValue> src)
{
if (DefaultValue.Boxed == null)
if (!_defaultValue.HasValue)
{
DefaultValue = src.DefaultValue;
_defaultValue = src.DefaultValue;
}
}
}

5
src/Avalonia.Base/ValueStore.cs

@ -66,6 +66,11 @@ namespace Avalonia
public void SetValue<T>(StyledPropertyBase<T> 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);

121
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -201,21 +201,13 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> ColumnHeaderHeightProperty =
AvaloniaProperty.Register<DataGrid, double>(
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);
}
/// <summary>
@ -261,8 +253,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<int> FrozenColumnCountProperty =
AvaloniaProperty.Register<DataGrid, int>(
nameof(FrozenColumnCount)/*,
validate: ValidateFrozenColumnCount*/);
nameof(FrozenColumnCount),
validate: ValidateFrozenColumnCount);
/// <summary>
/// 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<DataGridGridLinesVisibility> GridLinesVisibilityProperty =
AvaloniaProperty.Register<DataGrid, DataGridGridLinesVisibility>(nameof(GridLinesVisibility));
@ -395,30 +379,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> MaxColumnWidthProperty =
AvaloniaProperty.Register<DataGrid, double>(
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;
}
/// <summary>
@ -433,29 +399,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> MinColumnWidthProperty =
AvaloniaProperty.Register<DataGrid, double>(
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;
}
/// <summary>
@ -482,20 +431,13 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> RowHeightProperty =
AvaloniaProperty.Register<DataGrid, double>(
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);
}
/// <summary>
@ -510,20 +452,13 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> RowHeaderWidthProperty =
AvaloniaProperty.Register<DataGrid, double>(
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);
}
/// <summary>

22
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@ -67,26 +67,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> SublevelIndentProperty =
AvaloniaProperty.Register<DataGridRowGroupHeader, double>(
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;
}
/// <summary>

43
src/Avalonia.Controls/AutoCompleteBox.cs

@ -377,8 +377,8 @@ namespace Avalonia.Controls
/// dependency property.</value>
public static readonly StyledProperty<int> MinimumPrefixLengthProperty =
AvaloniaProperty.Register<AutoCompleteBox, int>(
nameof(MinimumPrefixLength), 1/*,
validate: ValidateMinimumPrefixLength*/);
nameof(MinimumPrefixLength), 1,
validate: IsValidMinimumPrefixLength);
/// <summary>
/// Identifies the
@ -391,8 +391,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<TimeSpan> MinimumPopulateDelayProperty =
AvaloniaProperty.Register<AutoCompleteBox, TimeSpan>(
nameof(MinimumPopulateDelay),
TimeSpan.Zero/*,
validate: ValidateMinimumPopulateDelay*/);
TimeSpan.Zero,
validate: IsValidMinimumPopulateDelay);
/// <summary>
/// Identifies the
@ -405,8 +405,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> MaxDropDownHeightProperty =
AvaloniaProperty.Register<AutoCompleteBox, double>(
nameof(MaxDropDownHeight),
double.PositiveInfinity/*,
validate: ValidateMaxDropDownHeight*/);
double.PositiveInfinity,
validate: IsValidMaxDropDownHeight);
/// <summary>
/// Identifies the
@ -494,8 +494,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<AutoCompleteFilterMode> FilterModeProperty =
AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterMode>(
nameof(FilterMode),
defaultValue: AutoCompleteFilterMode.StartsWith/*,
validate: ValidateFilterMode*/);
defaultValue: AutoCompleteFilterMode.StartsWith,
validate: IsValidFilterMode);
/// <summary>
/// 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<ArgumentOutOfRangeException>(value >= -1);
return value;
}
private static TimeSpan ValidateMinimumPopulateDelay(AutoCompleteBox control, TimeSpan value)
{
Contract.Requires<ArgumentOutOfRangeException>(value.TotalMilliseconds >= 0.0);
return value;
}
private static bool IsValidMinimumPrefixLength(int value) => value >= -1;
private static double ValidateMaxDropDownHeight(AutoCompleteBox control, double value)
{
Contract.Requires<ArgumentOutOfRangeException>(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<ArgumentException>(IsValidFilterMode(value));
return value;
}
/// <summary>
/// Handle the change of the IsEnabled property.

16
src/Avalonia.Controls/Calendar/Calendar.cs

@ -351,8 +351,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty<CalendarMode> DisplayModeProperty =
AvaloniaProperty.Register<Calendar, CalendarMode>(
nameof(DisplayMode)/*,
validate: ValidateDisplayMode*/);
nameof(DisplayMode),
validate: IsValidDisplayMode);
/// <summary>
/// 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

30
src/Avalonia.Controls/Calendar/DatePicker.cs

@ -189,14 +189,14 @@ namespace Avalonia.Controls
public static readonly StyledProperty<DatePickerFormat> SelectedDateFormatProperty =
AvaloniaProperty.Register<DatePicker, DatePickerFormat>(
nameof(SelectedDateFormat),
defaultValue: DatePickerFormat.Short/*,
validate: ValidateSelectedDateFormat*/);
defaultValue: DatePickerFormat.Short,
validate: IsValidSelectedDateFormat);
public static readonly StyledProperty<string> CustomDateFormatStringProperty =
AvaloniaProperty.Register<DatePicker, string>(
nameof(CustomDateFormatString),
defaultValue: "d"/*,
validate: ValidateDateFormatString*/);
defaultValue: "d",
validate: IsValidDateFormatString);
public static readonly DirectProperty<DatePicker, string> TextProperty =
AvaloniaProperty.RegisterDirect<DatePicker, string>(
@ -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)
{

10
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.
/// </remarks>
private static string SharedSizeGroupPropertyValueValid(Control _, string value)
private static bool SharedSizeGroupPropertyValueValid(string value)
{
Contract.Requires<ArgumentNullException>(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;
}
/// <remark>
@ -750,8 +750,8 @@ namespace Avalonia.Controls
/// </remarks>
public static readonly AttachedProperty<string> SharedSizeGroupProperty =
AvaloniaProperty.RegisterAttached<DefinitionBase, Control, string>(
"SharedSizeGroup"/*,
validate: SharedSizeGroupPropertyValueValid*/);
"SharedSizeGroup",
validate: SharedSizeGroupPropertyValueValid);
/// <summary>
/// Static ctor. Used for static registration of properties.

32
src/Avalonia.Controls/Grid.cs

@ -2740,12 +2740,8 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<int> ColumnProperty =
AvaloniaProperty.RegisterAttached<Grid, Control, int>(
"Column",
defaultValue: 0/*,
validate: (_, v) =>
{
if (v >= 0) return v;
else throw new ArgumentException("Invalid Grid.Column value.");
}*/);
defaultValue: 0,
validate: v => v >= 0);
/// <summary>
/// Row property. This is an attached property.
@ -2761,12 +2757,8 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<int> RowProperty =
AvaloniaProperty.RegisterAttached<Grid, Control, int>(
"Row",
defaultValue: 0/*,
validate: (_, v) =>
{
if (v >= 0) return v;
else throw new ArgumentException("Invalid Grid.Row value.");
}*/);
defaultValue: 0,
validate: v => v >= 0);
/// <summary>
/// ColumnSpan property. This is an attached property.
@ -2781,12 +2773,8 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<int> ColumnSpanProperty =
AvaloniaProperty.RegisterAttached<Grid, Control, int>(
"ColumnSpan",
defaultValue: 1/*,
validate: (_, v) =>
{
if (v >= 1) return v;
else throw new ArgumentException("Invalid Grid.ColumnSpan value.");
}*/);
defaultValue: 1,
validate: v => v >= 0);
/// <summary>
/// RowSpan property. This is an attached property.
@ -2801,12 +2789,8 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<int> RowSpanProperty =
AvaloniaProperty.RegisterAttached<Grid, Control, int>(
"RowSpan",
defaultValue: 1/*,
validate: (_, v) =>
{
if (v >= 1) return v;
else throw new ArgumentException("Invalid Grid.RowSpan value.");
}*/);
defaultValue: 1,
validate: v => v >= 0);
/// <summary>
/// IsSharedSizeScope property marks scoping element for shared size.

82
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<ArgumentException>(() =>
new StyledProperty<int>(
"BadDefault",
typeof(Class1),
new StyledPropertyMetadata<int>(101),
validate: Class1.ValidateFoo));
}
[Fact]
public void Metadata_Override_Throws_If_DefaultValue_Fails_Validation()
{
Assert.Throws<ArgumentException>(() => Class1.FooProperty.OverrideDefaultValue<Class2>(101));
}
[Fact]
public void SetValue_Throws_If_Fails_Validation()
{
var target = new Class1();
Assert.Throws<ArgumentException>(() => target.SetValue(Class1.FooProperty, 101));
}
[Fact]
public void Reverts_To_DefaultValue_If_Binding_Fails_Validation()
{
var target = new Class1();
var source = new Subject<int>();
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<int>();
var source2 = new Subject<int>();
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<int> FooProperty =
AvaloniaProperty.Register<Class1, int>(
"Qux",
defaultValue: 11,
validate: ValidateFoo);
public static bool ValidateFoo(int value)
{
return value < 100;
}
}
private class Class2 : AvaloniaObject
{
}
}
}
Loading…
Cancel
Save