AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
26.11k stars 2.26k forks source link

Binding validation error is not updated properly #16937

Open ruafelianna opened 2 months ago

ruafelianna commented 2 months ago

Describe the bug

I have a TextBox input which is bound to an int property in the view model. First I enter 1234567890 which is a valid int value. Then I add two more 1s: 123456789011 and see a validation error. Then I remove one 1 from the end and the validation message is updated. Then I remove another 1 and the validation message should be reset but I still see the previous validation message.

To Reproduce

  1. Create new Avalonia MVVM project.
  2. In the MainWindowViewModel add an int property:
    private int _number;
    public int Number
    {
    get => _number;
    set => this.RaiseAndSetIfChanged(ref _number, value);
    }
  3. On the MainWindow add a TextBox bound to the property:
    <TextBox Width="300" Text="{Binding Number}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
  4. Enter 123456789011.
  5. Remove last two 1s using Backspace.

Expected behavior

The validation error should be gone but it isn't.

Avalonia version

From 11.1.0-rc1 to the latest one which is currently 11.2.0-beta1.

OS

No response

Additional context

изображение

ruafelianna commented 2 months ago

The same happens when I use a converter. For example, if I enter a number and backspace it then there are no errors. If I enter some invalid input and backspace it then the validation error is not reset.

Also with the converter this happened in older versions too (tested in 11.0.13), but not always.

using Avalonia.Data;
using Avalonia.Data.Converters;
using System;
using System.Globalization;

namespace TestAvaloniaConverter.Converters
{
    internal class SomeConverter : IValueConverter
    {
        public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
            => value?.ToString();

        public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
        {
            if (value is null)
            {
                return null;
            }

            if (value is string str)
            {
                if (string.IsNullOrWhiteSpace(str))
                {
                    return null;
                }

                if (int.TryParse(str, out var res))
                {
                    return res;
                }

                return new BindingNotification(new Exception("not int"), BindingErrorType.DataValidationError);
            }

            return new BindingNotification(new Exception("not string"), BindingErrorType.DataValidationError);
        }
    }
}
xmlns:conv="using:TestAvaloniaConverter.Converters"

...

<Window.Resources>
    <conv:SomeConverter x:Key="SomeConverter" />
</Window.Resources>

<TextBox Width="300" Text="{Binding Number, Converter={StaticResource SomeConverter}}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
ruafelianna commented 2 months ago

Also using BindingNotification in 11.1 gives an exception. I guess the reason might be the same as in #16325.

Version 11.0.13: изображение

Version 11.1.3: изображение

Hzlin7 commented 1 month ago

I got the same problem. Is there any way to solve this problem?

ruafelianna commented 1 month ago

@Hzlin7, as far as I can remember, for now I'm stuck with using binding to a string? instead of a number type and then I apply some validation rules using ReactiveUI.Validation and System.ComponentModel.INotifyDataErrorInfo, which I also had some troubles with (I don't quite remember what the issues were, but I guess that was something related specifically to my project).

ruafelianna commented 1 month ago

@maxkatz6, could you, please, add proper labels to the issue? I am sorry to bother you, it's just it seems like this issue was overlooked.

Ruikoto commented 4 weeks ago

I also encountered this problem, it's frustrating.

aguahombre commented 1 week ago

This used to work correctly in Avalonia 10 but is now broken in 11.

I think BindingExpression.cs is missing the case where a converter returns a BindingNotification with BindingErrorType.Error.

In internal override bool WriteValueToSource(object? value), shouldn't there be a check whether the value is a BindingNotification in the IsDataValidationEnabled code path.

            else if (IsDataValidationEnabled)
            {
                var valueString = value?.ToString() ?? "(null)";
                var valueTypeName = value?.GetType().FullName ?? "null";
                var ex = new InvalidCastException(
                    $"Could not convert '{valueString}' ({valueTypeName}) to {type}.");
                OnDataValidationError(ex);
                return false;
            }

When the converter returns a BindingNotification, call OnDataValidationError with the supplied exception; e.g.

            else if (IsDataValidationEnabled)
            {
                if (value is BindingNotification be && be.ErrorType == BindingErrorType.Error)
                {
                    OnDataValidationError(be.Error);
                    return false;
                }
                var valueString = value?.ToString() ?? "(null)";
                var valueTypeName = value?.GetType().FullName ?? "null";
                var ex = new InvalidCastException(
                    $"Could not convert '{valueString}' ({valueTypeName}) to {type}.");
                OnDataValidationError(ex);
                return false;
            }