From a899185afb4bc95d18f48561b73eb38727dd4f40 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 20 Feb 2023 11:55:58 +0100 Subject: [PATCH] Update data validation from EffectiveValue. Requires `ValueEntry`/`BaseValueEntry` to be available to `EffectiveValue`. 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. --- .../PropertyStore/BindingEntryBase.cs | 1 - .../PropertyStore/EffectiveValue.cs | 51 ++++++++++--------- .../PropertyStore/EffectiveValue`1.cs | 26 +++++++--- src/Avalonia.Base/PropertyStore/ValueStore.cs | 7 +-- .../Data/BindingTests_DataValidation.cs | 9 ++-- 5 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 2fe3844a74..a841803ee1 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/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 { diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs index 78f0ad46b7..11a4dd7893 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs @@ -11,9 +11,6 @@ namespace Avalonia.PropertyStore /// internal abstract class EffectiveValue { - private IValueEntry? _valueEntry; - private IValueEntry? _baseValueEntry; - /// /// Gets the current effective value as a boxed value. /// @@ -29,6 +26,16 @@ namespace Avalonia.PropertyStore /// public BindingPriority BasePriority { get; protected set; } + /// + /// Gets the active value entry for the current effective value. + /// + public IValueEntry? ValueEntry { get; private set; } + + /// + /// Gets the active value entry for the current base value. + /// + public IValueEntry? BaseValueEntry { get; private set; } + /// /// Gets a value indicating whether the was overridden by a call to /// . @@ -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(); - } } } diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index c469034f9b..0788b39459 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/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)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)property); - } + ValueEntry?.Unsubscribe(); + BaseValueEntry?.Unsubscribe(); - public void DisposeAndRaiseUnset(ValueStore owner, StyledProperty property) - { + var p = (StyledProperty)property; BindingPriority priority; T oldValue; @@ -150,9 +155,16 @@ namespace Avalonia.PropertyStore if (!EqualityComparer.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); } } diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 0a5084466f..0887f11ec9 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/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 diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs index b76c86e814..5de703deb1 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs +++ b/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()) @@ -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(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(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);