Browse Source

Remove data validation for non-direct properties.

We're going to say that for the moment only direct properties handle
data validation. This gets around a few thorny issues with data
validation on styled properties.
pull/691/head
Steven Kirk 10 years ago
parent
commit
99a635f31f
  1. 4
      src/Avalonia.Base/AvaloniaObject.cs
  2. 4
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  3. 25
      src/Avalonia.Base/AvaloniaProperty.cs
  4. 16
      src/Avalonia.Base/DirectPropertyMetadata`1.cs
  5. 5
      src/Avalonia.Base/IDirectPropertyMetadata.cs
  6. 18
      src/Avalonia.Base/PropertyMetadata.cs
  7. 8
      src/Avalonia.Base/StyledPropertyMetadata`1.cs
  8. 57
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs
  9. 1
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  10. 126
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Validation.cs

4
src/Avalonia.Base/AvaloniaObject.cs

@ -660,7 +660,7 @@ namespace Avalonia
/// <param name="value">The value.</param>
private void SetDirectValue(AvaloniaProperty property, object value)
{
var metadata = property.GetMetadata(GetType());
var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType());
var notification = value as BindingNotification;
if (notification != null)
@ -686,7 +686,7 @@ namespace Avalonia
{
var accessor = (IDirectPropertyAccessor)GetRegistered(property);
var finalValue = value == AvaloniaProperty.UnsetValue ?
((IDirectPropertyMetadata)metadata).UnsetValue : value;
metadata.UnsetValue : value;
LogPropertySet(property, value, BindingPriority.LocalValue);

4
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -216,11 +216,13 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(binding != null);
var metadata = property.GetMetadata(target.GetType()) as IDirectPropertyMetadata;
var result = binding.Initiate(
target,
property,
anchor,
property.GetMetadata(target.GetType()).EnableDataValidation);
metadata?.EnableDataValidation ?? false);
if (result != null)
{

25
src/Avalonia.Base/AvaloniaProperty.cs

@ -251,9 +251,6 @@ namespace Avalonia
/// <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 validation function.</param>
/// <param name="enableDataValidation">
/// Whether the property is interested in data validation.
/// </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
@ -266,7 +263,6 @@ namespace Avalonia
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TOwner, TValue, TValue> validate = null,
bool enableDataValidation = false,
Action<IAvaloniaObject, bool> notifying = null)
where TOwner : IAvaloniaObject
{
@ -275,8 +271,7 @@ namespace Avalonia
var metadata = new StyledPropertyMetadata<TValue>(
defaultValue,
validate: Cast(validate),
defaultBindingMode: defaultBindingMode,
enableDataValidation: enableDataValidation);
defaultBindingMode: defaultBindingMode);
var result = new StyledProperty<TValue>(
name,
@ -299,17 +294,13 @@ namespace Avalonia
/// <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 validation function.</param>
/// <param name="enableDataValidation">
/// Whether the property is interested in data validation.
/// </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,
Func<THost, TValue, TValue> validate = null,
bool enableDataValidation = false)
Func<THost, TValue, TValue> validate = null)
where THost : IAvaloniaObject
{
Contract.Requires<ArgumentNullException>(name != null);
@ -317,8 +308,7 @@ namespace Avalonia
var metadata = new StyledPropertyMetadata<TValue>(
defaultValue,
validate: Cast(validate),
defaultBindingMode: defaultBindingMode,
enableDataValidation: enableDataValidation);
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty<TValue>(name, typeof(TOwner), metadata, inherits);
AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result);
@ -336,9 +326,6 @@ namespace Avalonia
/// <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 validation function.</param>
/// <param name="enableDataValidation">
/// Whether the property is interested in data validation.
/// </param>
/// <returns>A <see cref="AvaloniaProperty{TValue}"/></returns>
public static AttachedProperty<TValue> RegisterAttached<THost, TValue>(
string name,
@ -346,8 +333,7 @@ namespace Avalonia
TValue defaultValue = default(TValue),
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<THost, TValue, TValue> validate = null,
bool enableDataValidation = false)
Func<THost, TValue, TValue> validate = null)
where THost : IAvaloniaObject
{
Contract.Requires<ArgumentNullException>(name != null);
@ -355,8 +341,7 @@ namespace Avalonia
var metadata = new StyledPropertyMetadata<TValue>(
defaultValue,
validate: Cast(validate),
defaultBindingMode: defaultBindingMode,
enableDataValidation: enableDataValidation);
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty<TValue>(name, ownerType, metadata, inherits);
AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result);

16
src/Avalonia.Base/DirectPropertyMetadata`1.cs

@ -24,16 +24,28 @@ namespace Avalonia
TValue unsetValue = default(TValue),
BindingMode defaultBindingMode = BindingMode.Default,
bool enableDataValidation = false)
: base(defaultBindingMode, enableDataValidation)
: base(defaultBindingMode)
{
UnsetValue = unsetValue;
EnableDataValidation = enableDataValidation;
}
/// <summary>
/// Gets the to use when the property is set to <see cref="AvaloniaProperty.UnsetValue"/>.
/// Gets the value to use when the property is set to <see cref="AvaloniaProperty.UnsetValue"/>.
/// </summary>
public TValue UnsetValue { get; private set; }
/// <summary>
/// Gets a value indicating whether the property is interested in data validation.
/// </summary>
/// <remarks>
/// Data validation is validation performed at the target of a binding, for example in a
/// view model using the INotifyDataErrorInfo interface. Only certain properties on a
/// control (such as a TextBox's Text property) will be interested in recieving data
/// validation messages so this feature must be explicitly enabled by setting this flag.
/// </remarks>
public bool EnableDataValidation { get; }
/// <inheritdoc/>
object IDirectPropertyMetadata.UnsetValue => UnsetValue;

5
src/Avalonia.Base/IDirectPropertyMetadata.cs

@ -12,5 +12,10 @@ namespace Avalonia
/// Gets the to use when the property is set to <see cref="AvaloniaProperty.UnsetValue"/>.
/// </summary>
object UnsetValue { get; }
/// <summary>
/// Gets a value indicating whether the property is interested in data validation.
/// </summary>
bool EnableDataValidation { get; }
}
}

18
src/Avalonia.Base/PropertyMetadata.cs

@ -17,15 +17,10 @@ namespace Avalonia
/// Initializes a new instance of the <see cref="PropertyMetadata"/> class.
/// </summary>
/// <param name="defaultBindingMode">The default binding mode.</param>
/// <param name="enableDataValidation">
/// Whether the property is interested in data validation.
/// </param>
public PropertyMetadata(
BindingMode defaultBindingMode = BindingMode.Default,
bool enableDataValidation = false)
BindingMode defaultBindingMode = BindingMode.Default)
{
_defaultBindingMode = defaultBindingMode;
EnableDataValidation = enableDataValidation;
}
/// <summary>
@ -40,17 +35,6 @@ namespace Avalonia
}
}
/// <summary>
/// Gets a value indicating whether the property is interested in data validation.
/// </summary>
/// <remarks>
/// Data validation is validation performed at the target of a binding, for example in a
/// view model using the INotifyDataErrorInfo interface. Only certain properties on a
/// control (such as a TextBox's Text property) will be interested in recieving data
/// validation messages so this feature must be explicitly enabled by setting this flag.
/// </remarks>
public bool EnableDataValidation { get; }
/// <summary>
/// Merges the metadata with the base metadata.
/// </summary>

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

@ -18,15 +18,11 @@ namespace Avalonia
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="validate">A validation function.</param>
/// <param name="defaultBindingMode">The default binding mode.</param>
/// <param name="enableDataValidation">
/// Whether the property is interested in data validation.
/// </param>
public StyledPropertyMetadata(
TValue defaultValue = default(TValue),
Func<IAvaloniaObject, TValue, TValue> validate = null,
BindingMode defaultBindingMode = BindingMode.Default,
bool enableDataValidation = false)
: base(defaultBindingMode, enableDataValidation)
BindingMode defaultBindingMode = BindingMode.Default)
: base(defaultBindingMode)
{
DefaultValue = defaultValue;
Validate = validate;

57
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs

@ -23,20 +23,6 @@ namespace Avalonia.Base.UnitTests
Assert.Empty(target.Notifications);
}
[Fact(Skip = "Data validation not yet implemented for non-direct properties")]
public void Setting_Validated_Property_Calls_UpdateDataValidation()
{
var target = new Class1();
target.SetValue(Class1.ValidatedProperty, 6);
target.SetValue(Class1.ValidatedProperty, new BindingNotification(new Exception(), BindingErrorType.Error));
target.SetValue(Class1.ValidatedProperty, new BindingNotification(new Exception(), BindingErrorType.DataValidationError));
target.SetValue(Class1.ValidatedProperty, new BindingNotification(7));
target.SetValue(Class1.ValidatedProperty, 8);
Assert.Empty(target.Notifications);
}
[Fact]
public void Setting_Non_Validated_Direct_Property_Does_Not_Call_UpdateDataValidation()
{
@ -92,33 +78,6 @@ namespace Avalonia.Base.UnitTests
Assert.Empty(target.Notifications);
}
[Fact(Skip = "Data validation not yet implemented for non-direct properties")]
public void Binding_Validated_Property_Calls_UpdateDataValidation()
{
var source = new Subject<object>();
var target = new Class1
{
[!Class1.ValidatedProperty] = source.AsBinding(),
};
source.OnNext(6);
source.OnNext(new BindingNotification(new Exception(), BindingErrorType.Error));
source.OnNext(new BindingNotification(new Exception(), BindingErrorType.DataValidationError));
source.OnNext(new BindingNotification(7));
source.OnNext(8);
Assert.Equal(
new[]
{
null, // 6
new BindingNotification(new Exception(), BindingErrorType.Error),
new BindingNotification(new Exception(), BindingErrorType.DataValidationError),
new BindingNotification(7), // 7
null, // 8
},
target.Notifications.AsEnumerable());
}
[Fact]
public void Binding_Validated_Direct_Property_Calls_UpdateDataValidation()
{
@ -150,13 +109,7 @@ namespace Avalonia.Base.UnitTests
{
public static readonly StyledProperty<int> NonValidatedProperty =
AvaloniaProperty.Register<Class1, int>(
nameof(Validated),
enableDataValidation: false);
public static readonly StyledProperty<int> ValidatedProperty =
AvaloniaProperty.Register<Class1, int>(
nameof(Validated),
enableDataValidation: true);
nameof(NonValidated));
public static readonly DirectProperty<Class1, int> NonValidatedDirectProperty =
AvaloniaProperty.RegisterDirect<Class1, int>(
@ -166,7 +119,7 @@ namespace Avalonia.Base.UnitTests
public static readonly DirectProperty<Class1, int> ValidatedDirectProperty =
AvaloniaProperty.RegisterDirect<Class1, int>(
nameof(Validated),
nameof(ValidatedDirect),
o => o.ValidatedDirect,
(o, v) => o.ValidatedDirect = v,
enableDataValidation: true);
@ -180,12 +133,6 @@ namespace Avalonia.Base.UnitTests
set { SetValue(NonValidatedProperty, value); }
}
public int Validated
{
get { return GetValue(ValidatedProperty); }
set { SetValue(ValidatedProperty, value); }
}
public int NonValidatedDirect
{
get { return _direct; }

1
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -95,7 +95,6 @@
<Compile Include="Context\AvaloniaNamespaceRegistryTest.cs" />
<Compile Include="Data\BindingTests_Source.cs" />
<Compile Include="Data\BindingTests_ElementName.cs" />
<Compile Include="Data\BindingTests_Validation.cs" />
<Compile Include="Data\MultiBindingTests.cs" />
<Compile Include="Data\BindingTests_TemplatedParent.cs" />
<Compile Include="Data\BindingTests.cs" />

126
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Validation.cs

@ -1,126 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Data;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Data
{
public class BindingTests_Validation
{
[Fact]
public void Non_Validated_Property_Does_Not_Receive_BindingNotifications()
{
var source = new ValidationTestModel { MustBePositive = 5 };
var target = new TestControl
{
DataContext = source,
[!TestControl.NonValidatedProperty] = new Binding(nameof(source.MustBePositive)),
};
Assert.Empty(target.Notifications);
}
[Fact]
public void Validated_Direct_Property_Receives_BindingNotifications()
{
var source = new ValidationTestModel { MustBePositive = 5 };
var target = new TestControl
{
DataContext = source,
};
target.Bind(
TestControl.ValidatedDirectProperty,
new Binding(nameof(source.MustBePositive), BindingMode.TwoWay));
target.ValidatedDirect = 6;
target.ValidatedDirect = -1;
target.ValidatedDirect = 7;
Assert.Equal(
new[]
{
null, // 5
null, // 6
new BindingNotification(new ArgumentOutOfRangeException("value"), BindingErrorType.DataValidationError),
null, // 7
},
target.Notifications.AsEnumerable());
}
private class TestControl : Control
{
public static readonly StyledProperty<int> NonValidatedProperty =
AvaloniaProperty.Register<TestControl, int>(
nameof(Validated),
enableDataValidation: false);
public static readonly StyledProperty<int> ValidatedProperty =
AvaloniaProperty.Register<TestControl, int>(
nameof(Validated),
enableDataValidation: true);
public static readonly DirectProperty<TestControl, int> ValidatedDirectProperty =
AvaloniaProperty.RegisterDirect<TestControl, int>(
nameof(Validated),
o => o.ValidatedDirect,
(o, v) => o.ValidatedDirect = v,
enableDataValidation: true);
private int _direct;
public int NonValidated
{
get { return GetValue(NonValidatedProperty); }
set { SetValue(NonValidatedProperty, value); }
}
public int Validated
{
get { return GetValue(ValidatedProperty); }
set { SetValue(ValidatedProperty, value); }
}
public int ValidatedDirect
{
get { return _direct; }
set { SetAndRaise(ValidatedDirectProperty, ref _direct, value); }
}
public IList<BindingNotification> Notifications { get; } = new List<BindingNotification>();
protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification notification)
{
Notifications.Add(notification);
}
}
private class ValidationTestModel : NotifyingBase
{
private int _mustBePositive;
public int MustBePositive
{
get { return _mustBePositive; }
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
if (_mustBePositive != value)
{
_mustBePositive = value;
RaisePropertyChanged();
}
}
}
}
}
}
Loading…
Cancel
Save