0

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.

7
  • can you clearify what your input for your CheckInt() function should be? is it string? or even object? you can try Int32.TryParse(), see learn.microsoft.com/en-us/dotnet/api/… Commented May 22, 2020 at 12:48
  • Thats the problem, i don't know. Property would be for example public int Integer {get;set;}. And i can't check if int is int. On back end it's always int, on front end it can be everything. And i want to make sure user can't input anything else than integer. Commented May 22, 2020 at 12:50
  • so, you simply cant use exact that property. you have 2 ways to solve that. Youse a more generic property (object) or a Setter Method like SetMyInt(object o) and use that, otherwise you have to check before setting the property. Commented May 22, 2020 at 12:53
  • So, for every int property (and basically everything thats not string) i need "helper" property? Thats problematic, but doable. Commented May 22, 2020 at 13:00
  • yes, its the only way. Types are strict. Pushing anything to int will result in an exception, thats good, so you can handle the exception exactly on that point where it it is caused. Or the soft, more user friendly way, take care Commented May 22, 2020 at 13:04

1 Answer 1

0

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.

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

14 Comments

Alright, but then why does INotifyDataErrorInfo exists? Or it's more about using both depending on situation.
@gaijn: It's used to implement data validation in the view model. It cannot be used to validate how controls in the view set source properties.
Ok i have managed to make it work, but i don't get one thing. INotifyDataErrorInfo has this fancy HasErrors property, so i can easily block any control if there are errors. Can i do something similar here? If not, user still can break things.
You can't easily block any control unless you set some property of the view model somehow. You might be better of preventing specific characters from being entered into the TextBox depending on your exact requirements.
Another issue - 0,5 turns into 5. Well, INotifyDataErrorInfo might not be perfect, but at least it works.
|

Your Answer

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