0

Using MvvmCommunityToolkit and maybe is irrelevant, but I'm also using MaterialDesignForXAML UI library that sets default material design style for textbox if not is declared explictly.

I have a Quantity property where the user can write the value of quantity manually.

When the user delete all the content the textbox shows

Value "" could be not converted

instead of the defined ErrorMessage that says

The field is required

(I guess because Required data annotation doesn't work in this case).

I don't know if I am missing something obvious here.

XAML:

<TextBox Width="60"
         Margin="0,0,5,0"
         Text="{Binding Quantity, 
         UpdateSourceTrigger=PropertyChanged,
         ValidatesOnNotifyDataErrors=True}" />

View model (inheriting from observable validator):

private int quantity = 1;

[Required]
[Range(0, int.MaxValue, ErrorMessage = "I never will be displayed but why not")]
public int Quantity
{
    get => quantity;
    set
    {
        if (value >= 0 && value <= int.MaxValue)
        {
            SetProperty(ref quantity, value, true);
            OnPropertyChanged("LineTotal");
            OnPropertyChanged("Price");
            SaleViewModel.CalculateTotal();
        }
    }
}
3
  • 1
    Change the type of the Quantity property to int? instead of int. Commented Nov 7 at 15:52
  • @RobertHarvey I tried that, doesn't working, I guess the texbox only asume the null value for strings but if is binded to a int? or int with nothing in textbox the "" value could be not converted appears again. Commented Nov 7 at 17:57
  • Since it's an "int", and you can control the length with .MaxLength, (in this case) I usually just use a TextChanged event handler to discard anything that wasn't a "digit". For decimals, I get fancier. (e.g. "one" decimal point allowed "in"; etc.). If it's a "small" list of ints (eg. months), I might use a "picklist" or some other "tap/click" variation. Commented Nov 7 at 18:48

1 Answer 1

0

The conversion error occurs even before the Quantity property is assigned a value, so validation in the ViewModel won't even be called. The purpose of validation in the ViewModel is to determine the validity of an already received int value.

There are several approaches to solving your problem. The most important thing is to define the solution concept. There are two main ones:

1. The ViewModel is indifferent to user input. It only works with already received values ​​of the correct type.

In this case, determining the validity of a value for conversion to the required type should be handled at the View level.

One option is to limit the number of characters in user input, as suggested by "Gerry Schmitz." You can also use:

  • handling the Validation.Error event. Replace the error message in the event;
  • a custom converter with type casting;
  • replacing the Validation.ErrorTemplate template;
  • a custom ValidationRule, roughly implemented like this:
public class IntValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        if (value is string stringValue)
        {
            if (string.IsNullOrWhiteSpace(stringValue))
            {
                return new ValidationResult(false, "The field cannot be empty");
            }
            
            if (!int.TryParse(stringValue, out int result))
            {
                return new ValidationResult(false, "Enter an integer");
            }
            
            if (result < 0)
            {
                return new ValidationResult(false, "The value cannot be negative");
            }
        }
        
        return ValidationResult.ValidResult;
    }
}

And then this validator is used in the binding:

    <TextBox.Text>
        <Binding Path="Quantity" 
                 UpdateSourceTrigger="PropertyChanged"
                 ValidatesOnNotifyDataErrors="True">
            <Binding.ValidationRules>
                <local:IntValidationRule ValidationStep="RawProposedValue"/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>

2. The ViewModel completely controls user input.

Since user input is always a string value, validating that string value and converting it to the required type becomes the responsibility of the ViewModel.

This requires creating a string property in the ViewModel. For example, the following implementation might be used:

using CommunityToolkit.Mvvm.ComponentModel;

public partial class YourViewModel : ObservableValidator
{
    private int quantity = 1;
    private string quantityString = "1";

    [Required]
    [Range(0, int.MaxValue, ErrorMessage = "The value must be in the range from 0 to 2147483647")]
    public int Quantity
    {
        get => quantity;
        set
        {
            if (SetProperty(ref quantity, value))
            {
                QuantityString = value.ToString();
                OnPropertyChanged(nameof(LineTotal));
                OnPropertyChanged(nameof(Price));
                SaleViewModel.CalculateTotal();
            }
        }
    }

    public string QuantityString
    {
        get => quantityString;
        set
        {
            if (SetProperty(ref quantityString, value))
            {
                ValidateProperty(value, nameof(QuantityString));
                
                if (int.TryParse(value, out int result))
                {
                    Quantity = result;
                    ClearErrors(nameof(QuantityString));
                }
                else if (!string.IsNullOrWhiteSpace(value))
                {
                    AddError(nameof(QuantityString), "Enter an integer");
                }
            }
        }
    }

    // Custom validation for QuantityString
    private static void ValidateQuantityString(string value, ValidationContext context)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return; // The required attribute will handle empty values ​​automatically.
        }
        
        if (!int.TryParse(value, out _))
        {
            throw new ValidationException("Enter an integer");
        }
    }
}

Сan create many other options, but to do this you first need to decide what exactly you need and which conceptual option suits you.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.