Browse Source

Only send BindingNotifications on error.

Assume that a normal value when received by a property with data
validation enabled means no error.
pull/691/head
Steven Kirk 10 years ago
parent
commit
57e646583f
  1. 38
      src/Avalonia.Base/AvaloniaObject.cs
  2. 6
      src/Markup/Avalonia.Markup/Data/Plugins/DataValidatiorBase.cs
  3. 8
      src/Markup/Avalonia.Markup/Data/Plugins/IndeiValidationPlugin.cs
  4. 12
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs
  5. 2
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs
  6. 8
      tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs
  7. 10
      tests/Avalonia.Markup.UnitTests/Data/Plugins/ExceptionValidationPluginTests.cs
  8. 12
      tests/Avalonia.Markup.UnitTests/Data/Plugins/IndeiValidationPluginTests.cs
  9. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Validation.cs

38
src/Avalonia.Base/AvaloniaObject.cs

@ -464,7 +464,7 @@ namespace Avalonia
/// <inheritdoc/> /// <inheritdoc/>
void IPriorityValueOwner.BindingNotificationReceived(PriorityValue sender, BindingNotification notification) void IPriorityValueOwner.BindingNotificationReceived(PriorityValue sender, BindingNotification notification)
{ {
BindingNotificationReceived(sender.Property, notification); UpdateDataValidation(sender.Property, notification);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -499,14 +499,16 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Occurs when a <see cref="BindingNotification"/> is received for a property which has /// Called to update the validation state for properties for which data validation is
/// data validation enabled. /// enabled.
/// </summary> /// </summary>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="notification">The binding notification.</param> /// <param name="status">
protected virtual void BindingNotificationReceived( /// The new validation status. A value of null indicates no validation error.
/// </param>
protected virtual void UpdateDataValidation(
AvaloniaProperty property, AvaloniaProperty property,
BindingNotification notification) BindingNotification status)
{ {
} }
@ -647,20 +649,12 @@ namespace Avalonia
/// <returns></returns> /// <returns></returns>
private void DirectBindingSet(AvaloniaProperty property, object value) private void DirectBindingSet(AvaloniaProperty property, object value)
{ {
var validated = property.GetMetadata(GetType()).EnableDataValidation;
var notification = value as BindingNotification; var notification = value as BindingNotification;
if (notification == null) if (notification != null)
{
SetValue(property, value);
}
else
{ {
if (notification.HasValue) value = notification.Value;
{
SetValue(property, notification.Value);
}
BindingNotificationReceived(property, notification);
if (notification.ErrorType == BindingErrorType.Error) if (notification.ErrorType == BindingErrorType.Error)
{ {
@ -673,6 +667,16 @@ namespace Avalonia
ExceptionUtilities.GetMessage(notification.Error)); ExceptionUtilities.GetMessage(notification.Error));
} }
} }
if (notification?.HasValue != false)
{
SetValue(property, value);
}
if (validated)
{
UpdateDataValidation(property, notification);
}
} }
/// <summary> /// <summary>

6
src/Markup/Avalonia.Markup/Data/Plugins/DataValidatiorBase.cs

@ -68,13 +68,11 @@ namespace Avalonia.Markup.Data.Plugins
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <remarks> /// <remarks>
/// Notifies the observer that the value has changed. The value will be wrapped in a /// Notifies the observer that the value has changed.
/// <see cref="BindingNotification"/> if it is not already a binding notification.
/// </remarks> /// </remarks>
protected virtual void InnerValueChanged(object value) protected virtual void InnerValueChanged(object value)
{ {
var notification = value as BindingNotification ?? new BindingNotification(value); Observer.OnNext(value);
Observer.OnNext(notification);
} }
} }
} }

8
src/Markup/Avalonia.Markup/Data/Plugins/IndeiValidationPlugin.cs

@ -40,7 +40,7 @@ namespace Avalonia.Markup.Data.Plugins
{ {
if (e.PropertyName == _name || string.IsNullOrEmpty(e.PropertyName)) if (e.PropertyName == _name || string.IsNullOrEmpty(e.PropertyName))
{ {
Observer.OnNext(CreateBindingNotification(Value)); Observer.OnNext(BuildResult(Value));
} }
} }
@ -76,10 +76,10 @@ namespace Avalonia.Markup.Data.Plugins
protected override void InnerValueChanged(object value) protected override void InnerValueChanged(object value)
{ {
base.InnerValueChanged(CreateBindingNotification(value)); base.InnerValueChanged(BuildResult(value));
} }
private BindingNotification CreateBindingNotification(object value) private object BuildResult(object value)
{ {
var target = (INotifyDataErrorInfo)_reference.Target; var target = (INotifyDataErrorInfo)_reference.Target;
@ -98,7 +98,7 @@ namespace Avalonia.Markup.Data.Plugins
} }
} }
return new BindingNotification(value); return value;
} }
private Exception GenerateException(IList<string> errors) private Exception GenerateException(IList<string> errors)

12
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs

@ -81,16 +81,16 @@ namespace Avalonia.Markup.UnitTests.Data
observer.SetValue("foo"); observer.SetValue("foo");
observer.SetValue(5); observer.SetValue(5);
Assert.Equal(new[] Assert.Equal(new object[]
{ {
new BindingNotification(0), 0,
// Value is notified twice as ErrorsChanged is always called by IndeiTest. // Value is notified twice as ErrorsChanged is always called by IndeiTest.
new BindingNotification(5), 5,
new BindingNotification(5), 5,
// Value is first signalled without an error as validation hasn't been updated. // Value is first signalled without an error as validation hasn't been updated.
new BindingNotification(-5), -5,
new BindingNotification(new Exception("Must be positive"), BindingErrorType.DataValidationError, -5), new BindingNotification(new Exception("Must be positive"), BindingErrorType.DataValidationError, -5),
// Exception is thrown by trying to set value to "foo". // Exception is thrown by trying to set value to "foo".
@ -100,7 +100,7 @@ namespace Avalonia.Markup.UnitTests.Data
// Value is set then validation is updated. // Value is set then validation is updated.
new BindingNotification(new Exception("Must be positive"), BindingErrorType.DataValidationError, 5), new BindingNotification(new Exception("Must be positive"), BindingErrorType.DataValidationError, 5),
new BindingNotification(5), 5,
}, result); }, result);
} }

2
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs

@ -85,7 +85,7 @@ namespace Avalonia.Markup.UnitTests.Data
data.Next.OnNext(new Class2("foo")); data.Next.OnNext(new Class2("foo"));
sync.ExecutePostedCallbacks(); sync.ExecutePostedCallbacks();
Assert.Equal(new[] { new BindingNotification("foo") }, result); Assert.Equal(new[] { "foo" }, result);
sub.Dispose(); sub.Dispose();
Assert.Equal(0, data.PropertyChangedSubscriptionCount); Assert.Equal(0, data.PropertyChangedSubscriptionCount);

8
tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs

@ -288,11 +288,11 @@ namespace Avalonia.Markup.UnitTests.Data
target.OnNext("bar"); target.OnNext("bar");
Assert.Equal( Assert.Equal(
new[] new object[]
{ {
new BindingNotification("5.6"), "5.6",
new BindingNotification("1.2"), "1.2",
new BindingNotification("3.4"), "3.4",
new BindingNotification( new BindingNotification(
new InvalidCastException("Error setting 'DoubleValue': Could not convert 'bar' to 'System.Double'"), new InvalidCastException("Error setting 'DoubleValue': Could not convert 'bar' to 'System.Double'"),
BindingErrorType.Error) BindingErrorType.Error)

10
tests/Avalonia.Markup.UnitTests/Data/Plugins/ExceptionValidationPluginTests.cs

@ -14,7 +14,7 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
public class ExceptionValidationPluginTests public class ExceptionValidationPluginTests
{ {
[Fact] [Fact]
public void Produces_BindingNotifications() public void Produces_Correct_Results()
{ {
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin(); var inpcAccessorPlugin = new InpcPropertyAccessorPlugin();
var validatorPlugin = new ExceptionValidationPlugin(); var validatorPlugin = new ExceptionValidationPlugin();
@ -28,12 +28,12 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
validator.SetValue(-2, BindingPriority.LocalValue); validator.SetValue(-2, BindingPriority.LocalValue);
validator.SetValue(6, BindingPriority.LocalValue); validator.SetValue(6, BindingPriority.LocalValue);
Assert.Equal(new[] Assert.Equal(new object[]
{ {
new BindingNotification(0), 0,
new BindingNotification(5), 5,
new BindingNotification(new ArgumentOutOfRangeException("value"), BindingErrorType.DataValidationError), new BindingNotification(new ArgumentOutOfRangeException("value"), BindingErrorType.DataValidationError),
new BindingNotification(6), 6,
}, result); }, result);
} }

12
tests/Avalonia.Markup.UnitTests/Data/Plugins/IndeiValidationPluginTests.cs

@ -14,7 +14,7 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
public class IndeiValidationPluginTests public class IndeiValidationPluginTests
{ {
[Fact] [Fact]
public void Produces_BindingNotifications() public void Produces_Correct_Results()
{ {
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin(); var inpcAccessorPlugin = new InpcPropertyAccessorPlugin();
var validatorPlugin = new IndeiValidationPlugin(); var validatorPlugin = new IndeiValidationPlugin();
@ -29,19 +29,19 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
data.Maximum = 10; data.Maximum = 10;
data.Maximum = 5; data.Maximum = 5;
Assert.Equal(new[] Assert.Equal(new object[]
{ {
new BindingNotification(0), 0,
new BindingNotification(5), 5,
// Value is first signalled without an error as validation hasn't been updated. // Value is first signalled without an error as validation hasn't been updated.
new BindingNotification(6), 6,
// Then the ErrorsChanged event is fired. // Then the ErrorsChanged event is fired.
new BindingNotification(new Exception("Must be less than Maximum"), BindingErrorType.DataValidationError, 6), new BindingNotification(new Exception("Must be less than Maximum"), BindingErrorType.DataValidationError, 6),
// Maximum is changed to 10 so value is now valid. // Maximum is changed to 10 so value is now valid.
new BindingNotification(6), 6,
// And Maximum is changed back to 5. // And Maximum is changed back to 5.
new BindingNotification(new Exception("Must be less than Maximum"), BindingErrorType.DataValidationError, 6), new BindingNotification(new Exception("Must be less than Maximum"), BindingErrorType.DataValidationError, 6),

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

@ -45,10 +45,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
Assert.Equal( Assert.Equal(
new[] new[]
{ {
new BindingNotification(5), null, // 5
new BindingNotification(6), null, // 6
new BindingNotification(new ArgumentOutOfRangeException("value"), BindingErrorType.DataValidationError), new BindingNotification(new ArgumentOutOfRangeException("value"), BindingErrorType.DataValidationError),
new BindingNotification(7), null, // 7
}, },
target.Notifications.AsEnumerable()); target.Notifications.AsEnumerable());
} }
@ -94,7 +94,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
public IList<BindingNotification> Notifications { get; } = new List<BindingNotification>(); public IList<BindingNotification> Notifications { get; } = new List<BindingNotification>();
protected override void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification notification)
{ {
Notifications.Add(notification); Notifications.Add(notification);
} }

Loading…
Cancel
Save