Browse Source

Update data validation from EffectiveValue.

Requires `ValueEntry`/`BaseValueEntry` to be available to `EffectiveValue<T>`.

Also change the tests to only test values coming back from the model in the style binding tests, as they don't work when setting local values currently. This will be fixed later.
Steven Kirk 2 years ago
parent
commit
a899185afb

+ 0 - 1
src/Avalonia.Base/PropertyStore/BindingEntryBase.cs

@@ -3,7 +3,6 @@ using System.Collections.Generic;
 using Avalonia.Reactive;
 using Avalonia.Data;
 using Avalonia.Threading;
-using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
 
 namespace Avalonia.PropertyStore
 {

+ 26 - 25
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@@ -11,9 +11,6 @@ namespace Avalonia.PropertyStore
     /// </remarks>
     internal abstract class EffectiveValue
     {
-        private IValueEntry? _valueEntry;
-        private IValueEntry? _baseValueEntry;
-
         /// <summary>
         /// Gets the current effective value as a boxed value.
         /// </summary>
@@ -29,6 +26,16 @@ namespace Avalonia.PropertyStore
         /// </summary>
         public BindingPriority BasePriority { get; protected set; }
 
+        /// <summary>
+        /// Gets the active value entry for the current effective value.
+        /// </summary>
+        public IValueEntry? ValueEntry { get; private set; }
+
+        /// <summary>
+        /// Gets the active value entry for the current base value.
+        /// </summary>
+        public IValueEntry? BaseValueEntry { get; private set; }
+
         /// <summary>
         /// Gets a value indicating whether the <see cref="Value"/> was overridden by a call to 
         /// <see cref="AvaloniaObject.SetCurrentValue{T}"/>.
@@ -63,14 +70,14 @@ namespace Avalonia.PropertyStore
         {
             if (Priority == BindingPriority.Unset)
             {
-                _valueEntry?.Unsubscribe();
-                _valueEntry = null;
+                ValueEntry?.Unsubscribe();
+                ValueEntry = null;
             }
 
             if (BasePriority == BindingPriority.Unset)
             {
-                _baseValueEntry?.Unsubscribe();
-                _baseValueEntry = null;
+                BaseValueEntry?.Unsubscribe();
+                BaseValueEntry = null;
             }
         }
 
@@ -135,40 +142,34 @@ namespace Avalonia.PropertyStore
                 // value, then the current entry becomes our base entry.
                 if (Priority > BindingPriority.LocalValue && Priority < BindingPriority.Inherited)
                 {
-                    Debug.Assert(_valueEntry is not null);
-                    _baseValueEntry = _valueEntry;
-                    _valueEntry = null;
+                    Debug.Assert(ValueEntry is not null);
+                    BaseValueEntry = ValueEntry;
+                    ValueEntry = null;
                 }
 
-                if (_valueEntry != entry)
+                if (ValueEntry != entry)
                 {
-                    _valueEntry?.Unsubscribe();
-                    _valueEntry = entry;
+                    ValueEntry?.Unsubscribe();
+                    ValueEntry = entry;
                 }
             }
             else if (Priority <= BindingPriority.Animation)
             {
                 // We've received a non-animation value and have an active animation value, so the
                 // new entry becomes our base entry.
-                if (_baseValueEntry != entry)
+                if (BaseValueEntry != entry)
                 {
-                    _baseValueEntry?.Unsubscribe();
-                    _baseValueEntry = entry;
+                    BaseValueEntry?.Unsubscribe();
+                    BaseValueEntry = entry;
                 }
             }
-            else if (_valueEntry != entry)
+            else if (ValueEntry != entry)
             {
                 // Both the current value and the new value are non-animation values, so the new
                 // entry replaces the existing entry.
-                _valueEntry?.Unsubscribe();
-                _valueEntry = entry;
+                ValueEntry?.Unsubscribe();
+                ValueEntry = entry;
             }
         }
-
-        protected void UnsubscribeValueEntries()
-        {
-            _valueEntry?.Unsubscribe();
-            _baseValueEntry?.Unsubscribe();
-        }
     }
 }

+ 19 - 7
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using Avalonia.Data;
+using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
 
 namespace Avalonia.PropertyStore
 {
@@ -61,6 +62,12 @@ namespace Avalonia.PropertyStore
             UpdateValueEntry(value, priority);
 
             SetAndRaiseCore(owner,  (StyledProperty<T>)value.Property, GetValue(value), priority, false);
+
+            if (priority > BindingPriority.LocalValue &&
+                value.GetDataValidationState(out var state, out var error))
+            {
+                owner.Owner.OnUpdateDataValidation(value.Property, state, error);
+            }
         }
 
         public void SetLocalValueAndRaise(
@@ -128,12 +135,10 @@ namespace Avalonia.PropertyStore
 
         public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property)
         {
-            UnsubscribeValueEntries();
-            DisposeAndRaiseUnset(owner, (StyledProperty<T>)property);
-        }
+            ValueEntry?.Unsubscribe();
+            BaseValueEntry?.Unsubscribe();
 
-        public void DisposeAndRaiseUnset(ValueStore owner, StyledProperty<T> property)
-        {
+            var p = (StyledProperty<T>)property;
             BindingPriority priority;
             T oldValue;
 
@@ -150,9 +155,16 @@ namespace Avalonia.PropertyStore
 
             if (!EqualityComparer<T>.Default.Equals(oldValue, Value))
             {
-                owner.Owner.RaisePropertyChanged(property, Value, oldValue, priority, true);
+                owner.Owner.RaisePropertyChanged(p, Value, oldValue, priority, true);
                 if (property.Inherits)
-                    owner.OnInheritedEffectiveValueDisposed(property, Value);
+                    owner.OnInheritedEffectiveValueDisposed(p, Value);
+            }
+
+            if (ValueEntry?.GetDataValidationState(out _, out _) ??
+                BaseValueEntry?.GetDataValidationState(out _, out _) ??
+                false)
+            {
+                owner.Owner.OnUpdateDataValidation(p, BindingValueType.UnsetValue, null);
             }
         }
 

+ 4 - 3
src/Avalonia.Base/PropertyStore/ValueStore.cs

@@ -838,8 +838,6 @@ namespace Avalonia.PropertyStore
                         break;
                 }
 
-                current?.EndReevaluation();
-
                 if (current?.Priority == BindingPriority.Unset)
                 {
                     if (current.BasePriority == BindingPriority.Unset)
@@ -852,6 +850,8 @@ namespace Avalonia.PropertyStore
                         current.RemoveAnimationAndRaise(this, property);
                     }
                 }
+
+                current?.EndReevaluation();
             }
             finally
             {
@@ -923,7 +923,6 @@ namespace Avalonia.PropertyStore
                 for (var i = _effectiveValues.Count - 1; i >= 0; --i)
                 {
                     _effectiveValues.GetKeyValue(i, out var key, out var e);
-                    e.EndReevaluation();
 
                     if (e.Priority == BindingPriority.Unset)
                     {
@@ -933,6 +932,8 @@ namespace Avalonia.PropertyStore
                         if (i > _effectiveValues.Count)
                             break;
                     }
+
+                    e.EndReevaluation();
                 }
             }
             finally

+ 5 - 4
tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs

@@ -89,9 +89,10 @@ namespace Avalonia.Markup.UnitTests.Data
                     Mode = BindingMode.TwoWay
                 };
 
+                var model = new IndeiValidatingModel();
                 var root = new TestRoot
                 {
-                    DataContext = new IndeiValidatingModel(),
+                    DataContext = model,
                     Styles =
                     {
                         new Style(x => x.Is<DataValidationTestControl>())
@@ -109,13 +110,13 @@ namespace Avalonia.Markup.UnitTests.Data
 
                 Assert.Equal(20, target.GetValue(property));
 
-                target.SetValue(property, 200);
+                model.Value = 200;
 
                 Assert.Equal(200, target.GetValue(property));
                 Assert.IsType<DataValidationException>(target.DataValidationError);
                 Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
 
-                target.SetValue(property, 10);
+                model.Value = 10;
 
                 Assert.Equal(10, target.GetValue(property));
                 Assert.Null(target.DataValidationError);
@@ -166,7 +167,7 @@ namespace Avalonia.Markup.UnitTests.Data
                 Assert.IsType<DataValidationException>(target.DataValidationError);
                 Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
 
-                target.SetValue(property, 10);
+                model.Value = 10;
 
                 Assert.Equal(10, target.GetValue(property));
                 Assert.Null(target.DataValidationError);