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 67f55055ec..d13a25f210 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() {