Browse Source

Handle BindingNotifications in SetValue.

As one-time bindings don't set up a binding as such: they just call
`SetValue`.
pull/691/head
Steven Kirk 10 years ago
parent
commit
edc538185f
  1. 183
      src/Avalonia.Base/AvaloniaObject.cs
  2. 1
      src/Avalonia.Base/Data/BindingNotification.cs
  3. 38
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs

183
src/Avalonia.Base/AvaloniaObject.cs

@ -262,42 +262,11 @@ namespace Avalonia
if (property.IsDirect)
{
var accessor = (IDirectPropertyAccessor)GetRegistered(property);
LogPropertySet(property, value, priority);
accessor.SetValue(this, DirectUnsetToDefault(value, property));
SetDirectValue(property, value);
}
else
{
PriorityValue v;
var originalValue = value;
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
ThrowNotRegistered(property);
}
if (!TypeUtilities.TryCast(property.PropertyType, value, out value))
{
throw new ArgumentException(string.Format(
"Invalid value for Property '{0}': '{1}' ({2})",
property.Name,
originalValue,
originalValue?.GetType().FullName ?? "(null)"));
}
if (!_values.TryGetValue(property, out v))
{
if (value == AvaloniaProperty.UnsetValue)
{
return;
}
v = CreatePriorityValue(property);
_values.Add(property, v);
}
LogPropertySet(property, value, priority);
v.SetValue(value, (int)priority);
SetStyledValue(property, value, priority);
}
}
@ -361,7 +330,7 @@ namespace Avalonia
subscription = source
.Select(x => CastOrDefault(x, property.PropertyType))
.Do(_ => { }, () => _directBindings.Remove(subscription))
.Subscribe(x => DirectBindingSet(property, x));
.Subscribe(x => SetDirectValue(property, x));
_directBindings.Add(subscription);
@ -642,20 +611,60 @@ namespace Avalonia
}
/// <summary>
/// Sets a property value for a direct property binding.
/// Gets the default value for a property.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The default value.</returns>
private object GetDefaultValue(AvaloniaProperty property)
{
if (property.Inherits && _inheritanceParent != null)
{
return (_inheritanceParent as AvaloniaObject).GetValueInternal(property);
}
else
{
return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
}
}
/// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value
/// without check for registered as this can slow getting the value
/// this method is intended for internal usage in AvaloniaObject only
/// it's called only after check the property is registered
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
private object GetValueInternal(AvaloniaProperty property)
{
object result = AvaloniaProperty.UnsetValue;
PriorityValue value;
if (_values.TryGetValue(property, out value))
{
result = value.Value;
}
if (result == AvaloniaProperty.UnsetValue)
{
result = GetDefaultValue(property);
}
return result;
}
/// <summary>
/// Sets the value of a direct property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <returns></returns>
private void DirectBindingSet(AvaloniaProperty property, object value)
private void SetDirectValue(AvaloniaProperty property, object value)
{
var validated = property.GetMetadata(GetType()).EnableDataValidation;
var metadata = property.GetMetadata(GetType());
var notification = value as BindingNotification;
if (notification != null)
{
value = notification.Value;
if (notification.ErrorType == BindingErrorType.Error)
{
Logger.Error(
@ -666,73 +675,85 @@ namespace Avalonia
property,
ExceptionUtilities.GetMessage(notification.Error));
}
if (notification.HasValue)
{
value = notification.Value;
}
}
if (notification?.HasValue != false)
if (notification == null || notification.HasValue)
{
SetValue(property, value);
var accessor = (IDirectPropertyAccessor)GetRegistered(property);
var finalValue = value == AvaloniaProperty.UnsetValue ?
((IDirectPropertyMetadata)metadata).UnsetValue : value;
LogPropertySet(property, value, BindingPriority.LocalValue);
accessor.SetValue(this, finalValue);
}
if (validated)
if (metadata.EnableDataValidation)
{
UpdateDataValidation(property, notification);
}
}
/// <summary>
/// Converts an unset value to the default value for a direct property.
/// Sets the value of a styled property.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
private object DirectUnsetToDefault(object value, AvaloniaProperty property)
/// <param name="value">The value.</param>
/// <param name="priority">The priority of the value.</param>
private void SetStyledValue(AvaloniaProperty property, object value, BindingPriority priority)
{
return value == AvaloniaProperty.UnsetValue ?
((IDirectPropertyMetadata)property.GetMetadata(GetType())).UnsetValue :
value;
}
var notification = value as BindingNotification;
/// <summary>
/// Gets the default value for a property.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The default value.</returns>
private object GetDefaultValue(AvaloniaProperty property)
{
if (property.Inherits && _inheritanceParent != null)
// We currently accept BindingNotifications for non-direct properties but we just
// strip them to their underlying value.
if (notification != null)
{
return (_inheritanceParent as AvaloniaObject).GetValueInternal(property);
if (!notification.HasValue)
{
return;
}
else
{
value = notification.Value;
}
}
else
var originalValue = value;
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
ThrowNotRegistered(property);
}
}
/// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value
/// without check for registered as this can slow getting the value
/// this method is intended for internal usage in AvaloniaObject only
/// it's called only after check the property is registered
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
private object GetValueInternal(AvaloniaProperty property)
{
object result = AvaloniaProperty.UnsetValue;
PriorityValue value;
if (_values.TryGetValue(property, out value))
if (!TypeUtilities.TryCast(property.PropertyType, value, out value))
{
result = value.Value;
throw new ArgumentException(string.Format(
"Invalid value for Property '{0}': '{1}' ({2})",
property.Name,
originalValue,
originalValue?.GetType().FullName ?? "(null)"));
}
if (result == AvaloniaProperty.UnsetValue)
PriorityValue v;
if (!_values.TryGetValue(property, out v))
{
result = GetDefaultValue(property);
if (value == AvaloniaProperty.UnsetValue)
{
return;
}
v = CreatePriorityValue(property);
_values.Add(property, v);
}
return result;
LogPropertySet(property, value, priority);
v.SetValue(value, (int)priority);
}
/// <summary>

1
src/Avalonia.Base/Data/BindingNotification.cs

@ -66,6 +66,7 @@ namespace Avalonia.Data
throw new ArgumentException($"'errorType' may not be None");
}
Value = AvaloniaProperty.UnsetValue;
Error = error;
ErrorType = errorType;
}

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

@ -37,6 +37,20 @@ namespace Avalonia.Base.UnitTests
Assert.Empty(target.Notifications);
}
[Fact]
public void Setting_Non_Validated_Direct_Property_Does_Not_Call_UpdateDataValidation()
{
var target = new Class1();
target.SetValue(Class1.NonValidatedDirectProperty, 6);
target.SetValue(Class1.NonValidatedDirectProperty, new BindingNotification(new Exception(), BindingErrorType.Error));
target.SetValue(Class1.NonValidatedDirectProperty, new BindingNotification(new Exception(), BindingErrorType.DataValidationError));
target.SetValue(Class1.NonValidatedDirectProperty, new BindingNotification(7));
target.SetValue(Class1.NonValidatedDirectProperty, 8);
Assert.Empty(target.Notifications);
}
[Fact]
public void Setting_Validated_Direct_Property_Calls_UpdateDataValidation()
{
@ -48,7 +62,16 @@ namespace Avalonia.Base.UnitTests
target.SetValue(Class1.ValidatedDirectProperty, new BindingNotification(7));
target.SetValue(Class1.ValidatedDirectProperty, 8);
Assert.Empty(target.Notifications);
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]
@ -135,6 +158,12 @@ namespace Avalonia.Base.UnitTests
nameof(Validated),
enableDataValidation: true);
public static readonly DirectProperty<Class1, int> NonValidatedDirectProperty =
AvaloniaProperty.RegisterDirect<Class1, int>(
nameof(NonValidatedDirect),
o => o.NonValidatedDirect,
(o, v) => o.NonValidatedDirect = v);
public static readonly DirectProperty<Class1, int> ValidatedDirectProperty =
AvaloniaProperty.RegisterDirect<Class1, int>(
nameof(Validated),
@ -142,6 +171,7 @@ namespace Avalonia.Base.UnitTests
(o, v) => o.ValidatedDirect = v,
enableDataValidation: true);
private int _nonValidatedDirect;
private int _direct;
public int NonValidated
@ -156,6 +186,12 @@ namespace Avalonia.Base.UnitTests
set { SetValue(ValidatedProperty, value); }
}
public int NonValidatedDirect
{
get { return _direct; }
set { SetAndRaise(NonValidatedDirectProperty, ref _nonValidatedDirect, value); }
}
public int ValidatedDirect
{
get { return _direct; }

Loading…
Cancel
Save