浏览代码

Handle BindingNotifications in SetValue.

As one-time bindings don't set up a binding as such: they just call
`SetValue`.
Steven Kirk 9 年之前
父节点
当前提交
edc538185f

+ 102 - 81
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 - 0
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;
         }

+ 37 - 1
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; }