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.
Steven Kirk 9 years ago
parent
commit
99a635f31f

+ 2 - 2
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);
 

+ 3 - 1
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)
             {

+ 5 - 20
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);

+ 14 - 2
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 - 0
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; }
     }
 }

+ 1 - 17
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>

+ 2 - 6
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;

+ 2 - 55
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; }

+ 0 - 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" />

+ 0 - 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();
-                    }
-                }
-            }
-        }
-    }
-}