WPF integer validation

I'm using INotifyDataErrorInfo, and this implementation: https://kmatyaszek.github.io/wpf-validation/2019/03/13/wpf-validation-using-inotifydataerrorinfo.html

Code in case link dies:

public class MainViewModel : BindableBase, INotifyDataErrorInfo
{
    private string _userName;
    private readonly Dictionary<string, List<string>> _errorsByPropertyName = new Dictionary<string, List<string>>();

    public MainViewModel()
    {
        UserName = null;
    }

    public string UserName
    {
        get => _userName;
        set
        {
            _userName = value;
            ValidateUserName();
            RaisePropertyChanged();
        }
    }

    public bool HasErrors => _errorsByPropertyName.Any();

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public IEnumerable GetErrors(string propertyName)
    {
        return _errorsByPropertyName.ContainsKey(propertyName) ?
            _errorsByPropertyName[propertyName] : null;
    }

    private void OnErrorsChanged(string propertyName)
    {
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }

    private void ValidateUserName()
    {
        ClearErrors(nameof(UserName));
        if (string.IsNullOrWhiteSpace(UserName))
            AddError(nameof(UserName), "Username cannot be empty.");
        if (string.Equals(UserName, "Admin", StringComparison.OrdinalIgnoreCase))
            AddError(nameof(UserName), "Admin is not valid username.");
        if (UserName == null || UserName?.Length <= 5)
            AddError(nameof(UserName), "Username must be at least 6 characters long.");
    }

    private void AddError(string propertyName, string error)
    {
        if (!_errorsByPropertyName.ContainsKey(propertyName))
            _errorsByPropertyName[propertyName] = new List<string>();

        if (!_errorsByPropertyName[propertyName].Contains(error))
        {
            _errorsByPropertyName[propertyName].Add(error);
            OnErrorsChanged(propertyName);
        }
    }

    private void ClearErrors(string propertyName)
    {
        if (_errorsByPropertyName.ContainsKey(propertyName))
        {
            _errorsByPropertyName.Remove(propertyName);
            OnErrorsChanged(propertyName);
        }
    }
}

It works very well, but now i want to validate integer value, not string. Basically user shouldn't be able to input anything else than int, otherwise app will crash. But i have no idea what ValidateIntegerValue() method can i write, since i can't check if int is int, since on back end it's always integer.

1 answer

  • answered 2020-05-22 14:24 mm8

    An int property can only be set an int. Period. And it is not the responsibility of the view model to validate that the view, or a control in the view, doesn't try to set it to any other value because this will always fail. The view model simply exposes the property and it's the responsibility of the view to set it.

    So the first thing to understand is that this kind of validation should take place in the view or the control. Binding to a string property and then convert the value in the view model is a bad idea.

    What you could do instead is to create a custom ValidationRule:

    public class StringToIntValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            int i;
            if (int.TryParse(value.ToString(), out i))
                return new ValidationResult(true, null);
    
            return new ValidationResult(false, "Please enter a valid integer value.");
        }
    }
    

    ...that you associate with the binding:

    <TextBox>
        <TextBox.Text>
            <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <local:StringToIntValidationRule ValidationStep="RawProposedValue"/>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
    

    Please refer to this blog post for more information.

    If you want to prevent the user from being able to type in invalid values in the TextBox, you could handle the PreviewTextInput event or create a custom control. Again, this has nothing to do with the view model.