From 40ab20146f6950e21b073b6446b8f3f6e9ad6f1c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 21 Feb 2023 11:42:45 +0100 Subject: [PATCH] Clear data validation when binding completes. --- .../PropertyStore/DirectBindingObserver.cs | 11 ++++- .../DirectUntypedBindingObserver.cs | 5 +++ .../LocalValueBindingObserverBase.cs | 9 +++- .../AvaloniaObjectTests_DataValidation.cs | 42 +++++++++++++++++++ .../Data/BindingTests_DataValidation.cs | 24 +++++++++++ 5 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs b/src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs index cbe2435953..4bf98e3f7b 100644 --- a/src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs @@ -9,11 +9,13 @@ namespace Avalonia.PropertyStore IDisposable { private readonly ValueStore _owner; + private readonly bool _hasDataValidation; private IDisposable? _subscription; public DirectBindingObserver(ValueStore owner, DirectPropertyBase property) { _owner = owner; + _hasDataValidation = property.GetMetadata(owner.Owner.GetType())?.EnableDataValidation ?? false; Property = property; } @@ -33,10 +35,17 @@ namespace Avalonia.PropertyStore { _subscription?.Dispose(); _subscription = null; + OnCompleted(); + } + + public void OnCompleted() + { _owner.OnLocalValueBindingCompleted(Property, this); + + if (_hasDataValidation) + _owner.Owner.OnUpdateDataValidation(Property, BindingValueType.UnsetValue, null); } - public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); public void OnError(Exception error) => OnCompleted(); public void OnNext(T value) diff --git a/src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs index 5d60b44bef..1cf108df9b 100644 --- a/src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs @@ -10,11 +10,13 @@ namespace Avalonia.PropertyStore IDisposable { private readonly ValueStore _owner; + private readonly bool _hasDataValidation; private IDisposable? _subscription; public DirectUntypedBindingObserver(ValueStore owner, DirectPropertyBase property) { _owner = owner; + _hasDataValidation = property.GetMetadata(owner.Owner.GetType())?.EnableDataValidation ?? false; Property = property; } @@ -30,6 +32,9 @@ namespace Avalonia.PropertyStore _subscription?.Dispose(); _subscription = null; _owner.OnLocalValueBindingCompleted(Property, this); + + if (_hasDataValidation) + _owner.Owner.OnUpdateDataValidation(Property, BindingValueType.UnsetValue, null); } public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserverBase.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserverBase.cs index 85de33d9e0..5d920cf88d 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserverBase.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserverBase.cs @@ -37,10 +37,17 @@ namespace Avalonia.PropertyStore { _subscription?.Dispose(); _subscription = null; + OnCompleted(); + } + + public void OnCompleted() + { + if (_hasDataValidation) + _owner.Owner.OnUpdateDataValidation(Property, BindingValueType.UnsetValue, null); + _owner.OnLocalValueBindingCompleted(Property, this); } - public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); public void OnError(Exception error) => OnCompleted(); public void OnNext(T value) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs index 71eb521f9d..12cd39046b 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs @@ -92,6 +92,48 @@ namespace Avalonia.Base.UnitTests Assert.Equal(1, target.Notifications.Count); } + [Fact] + public void Disposing_Binding_Subscription_Clears_DataValidation() + { + var target = new Class1(); + var source = new Subject>(); + var property = GetProperty(); + var error = new Exception(); + var sub = target.Bind(property, source); + + source.OnNext(6); + source.OnNext(BindingValue.DataValidationError(error)); + sub.Dispose(); + + Assert.Equal(new Notification[] + { + new(BindingValueType.Value, 6, null), + new(BindingValueType.DataValidationError, 6, error), + new(BindingValueType.UnsetValue, 6, null), + }, target.Notifications); + } + + [Fact] + public void Completing_Binding_Clears_DataValidation() + { + var target = new Class1(); + var source = new Subject>(); + var property = GetProperty(); + var error = new Exception(); + + target.Bind(property, source); + source.OnNext(6); + source.OnNext(BindingValue.DataValidationError(error)); + source.OnCompleted(); + + Assert.Equal(new Notification[] + { + new(BindingValueType.Value, 6, null), + new(BindingValueType.DataValidationError, 6, error), + new(BindingValueType.UnsetValue, 6, null), + }, target.Notifications); + } + protected abstract T GetProperty(); protected abstract T GetNonValidatedProperty(); } diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs index 5de703deb1..35e9370c4c 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs @@ -67,6 +67,30 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Null(target.DataValidationError); } + [Fact] + public void Disposing_Binding_Subscription_Clears_DataValidation() + { + var (target, property) = CreateTarget(); + var binding = new Binding(nameof(ExceptionValidatingModel.Value)) + { + Mode = BindingMode.TwoWay + }; + + target.DataContext = new IndeiValidatingModel + { + Value = 200, + }; + + var sub = target.Bind(property, binding); + + Assert.Equal(200, target.GetValue(property)); + Assert.IsType(target.DataValidationError); + + sub.Dispose(); + + Assert.Null(target.DataValidationError); + } + private protected abstract (DataValidationTestControl, T) CreateTarget(); }