From 613760fcffce8dcfce95eaf92f867e192409513f Mon Sep 17 00:00:00 2001 From: drone <93078773+drone1400@users.noreply.github.com> Date: Wed, 30 Apr 2025 19:31:47 +0300 Subject: [PATCH] Fixes for DataValidationErrors not working correctly with Bindings (issue #18693) (#18694) * Added unit test for AutoCompleteBox DataValidationErrors - checks that when the TextBox control's Errors are bound to the parent AutoCompleteBox, they get updated correctly in both parent AutoCompleteBox control and TextBox control * Fix issue with DataValidationErrors causing issues with AutoCompleteBox because of Bindings - when binding a control's DataValidationErrors.Errors property to the same property of another control, the use of a static bool flag "s_overridingErrors" was preventing the control with the Binding from being updated correctly - issue is resolved by replacing static bool flag with a private AttachedProperty in DataValidationErrors.cs * Clean up Asserts in previous AutoCompleteBoxTests.cs changes --- src/Avalonia.Controls/DataValidationErrors.cs | 16 +++++++++----- .../AutoCompleteBoxTests.cs | 22 +++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/DataValidationErrors.cs b/src/Avalonia.Controls/DataValidationErrors.cs index 243032e725..f11c621030 100644 --- a/src/Avalonia.Controls/DataValidationErrors.cs +++ b/src/Avalonia.Controls/DataValidationErrors.cs @@ -18,7 +18,6 @@ namespace Avalonia.Controls [PseudoClasses(":error")] public class DataValidationErrors : ContentControl { - private static bool s_overridingErrors; /// /// Defines the DataValidationErrors.Errors attached property. @@ -49,6 +48,12 @@ namespace Avalonia.Controls /// private static readonly AttachedProperty?> OriginalErrorsProperty = AvaloniaProperty.RegisterAttached?>("OriginalErrors"); + + /// + /// Prevents executing ErrorsChanged after they are updated internally from OnErrorsOrConverterChanged + /// + private static readonly AttachedProperty OverridingErrorsInternallyProperty = + AvaloniaProperty.RegisterAttached("OverridingErrorsInternally", defaultValue: false); private Control? _owner; @@ -96,9 +101,10 @@ namespace Avalonia.Controls private static void ErrorsChanged(AvaloniaPropertyChangedEventArgs e) { - if (s_overridingErrors) return; - var control = (Control)e.Sender; + + if (control.GetValue(OverridingErrorsInternallyProperty)) return; + var errors = (IEnumerable?)e.NewValue; // Update original errors @@ -140,14 +146,14 @@ namespace Avalonia.Controls .Where(e => e is not null))? .ToArray(); - s_overridingErrors = true; + control.SetCurrentValue(OverridingErrorsInternallyProperty, true); try { control.SetCurrentValue(ErrorsProperty, newErrors!); } finally { - s_overridingErrors = false; + control.SetCurrentValue(OverridingErrorsInternallyProperty, false); } control.SetValue(HasErrorsProperty, newErrors?.Any() == true); diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index a50e501023..870c1363ca 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -421,6 +421,28 @@ namespace Avalonia.Controls.UnitTests }); } + [Fact] + public void Text_Validation_TextBox_Errors_Binding() + { + RunTest((control, textbox) => + { + // simulate the TemplateBinding that would be used within the AutoCompleteBox control theme for the inner PART_TextBox + // DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" + textbox.Bind(DataValidationErrors.ErrorsProperty, control.GetBindingObservable(DataValidationErrors.ErrorsProperty)); + + var exception = new InvalidCastException("failed validation"); + var textObservable = new BehaviorSubject(new BindingNotification(exception, BindingErrorType.DataValidationError)); + control.Bind(AutoCompleteBox.TextProperty, textObservable); + Dispatcher.UIThread.RunJobs(); + + Assert.True(DataValidationErrors.GetHasErrors(control)); + Assert.Equal([exception], DataValidationErrors.GetErrors(control)); + + Assert.True(DataValidationErrors.GetHasErrors(textbox)); + Assert.Equal([exception], DataValidationErrors.GetErrors(textbox)); + }); + } + [Fact] public void SelectedItem_Validation() {