diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index be7a14dcf9..9c6038e79c 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/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
}
///
- /// Sets a property value for a direct property binding.
+ /// Gets the default value for a property.
+ ///
+ /// The property.
+ /// The default value.
+ private object GetDefaultValue(AvaloniaProperty property)
+ {
+ if (property.Inherits && _inheritanceParent != null)
+ {
+ return (_inheritanceParent as AvaloniaObject).GetValueInternal(property);
+ }
+ else
+ {
+ return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
+ }
+ }
+
+ ///
+ /// Gets a 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
+ ///
+ /// The property.
+ /// The value.
+ 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;
+ }
+
+ ///
+ /// Sets the value of a direct property.
///
/// The property.
/// The value.
- ///
- 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);
}
}
///
- /// Converts an unset value to the default value for a direct property.
+ /// Sets the value of a styled property.
///
- /// The value.
/// The property.
- /// The value.
- private object DirectUnsetToDefault(object value, AvaloniaProperty property)
+ /// The value.
+ /// The priority of the value.
+ private void SetStyledValue(AvaloniaProperty property, object value, BindingPriority priority)
{
- return value == AvaloniaProperty.UnsetValue ?
- ((IDirectPropertyMetadata)property.GetMetadata(GetType())).UnsetValue :
- value;
- }
+ var notification = value as BindingNotification;
- ///
- /// Gets the default value for a property.
- ///
- /// The property.
- /// The default value.
- 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);
}
- }
- ///
- /// Gets a 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
- ///
- /// The property.
- /// The value.
- 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);
}
///
diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs
index 9351443e03..1c8a8067f8 100644
--- a/src/Avalonia.Base/Data/BindingNotification.cs
+++ b/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;
}
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs
index ef87147c9f..99645ace76 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs
+++ b/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 NonValidatedDirectProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(NonValidatedDirect),
+ o => o.NonValidatedDirect,
+ (o, v) => o.NonValidatedDirect = v);
+
public static readonly DirectProperty ValidatedDirectProperty =
AvaloniaProperty.RegisterDirect(
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; }