0

Situation

I have textbox, that is binded to a property with a type of double.

When the use tries to input a letter into the box, this shows up:

enter image description here

And also, this shows up in binding failure.

enter image description here

I currently have this validation in place that checks for null value and works fine.

public Dictionary<string, string> Errors { get; } = new Dictionary<string, string>();

public string this[string propertyName]
{
    get
    {
        CollectionErrors();
        return Errors.ContainsKey(propertyName) ? Errors[propertyName] : string.Empty;
    }
}

private void CollectionErrors()
{
    Errors.Clear();
    if(NumberProp == null)
    {
        Errors.Add(nameof(NumberProp), "This value cannot be empty");
    }
}

public string Error => string.Empty;

public bool HasErrors => !Errors.Any(); // To determine if there are any errors

Output

enter image description here

Question

How do I handle the could not be converted?

I tried this so far:

if(NumberProp.GetType() == typeof(string))
{
    Errors.Add(nameof(NumberProp), "NumberProp cannot contain any letters!");
}

But get this error:

System.NullReferenceException: 'Object reference not set to an instance of an object.'

Why I need this

I have a button that is either disabled on failed validation or enabled if everything is ok.

SendDataCommand = new RelayCommand((x) => { SendData(); }, (y) => HasErrors);

The button is enabled when the error displays Value '' could not be converted..

Edit 1

XAML

 <TextBox Text="{Binding NumberProp, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True, TargetNullValue=''}" />

Property

private double? _numberProp;

public double? NumberProp
{
    get { return _numberProp; }
    set
    {
        _numberProp = value;
        OnPropertyChanged(nameof(NumberProp));
    }
}
Eduards
  • 1,734
  • 2
  • 12
  • 37
  • `if(NumberProp != null && NumberProp.GetType() == typeof(string))` ? :) – Rekshino Apr 21 '21 at 13:37
  • @Rekshino Nope - still getting same error. – Eduards Apr 21 '21 at 13:40
  • @LV98: What exactly are you trying to handle and how? You can *never* set a `double?` property to anything else than a valid `double?` value. – mm8 Apr 21 '21 at 13:58
  • @mm8 Basically - I don't want the user to type any other characters other than to make a decimal value. – Eduards Apr 21 '21 at 14:00
  • 1
    Then you need to implement this functionality in the view or control that binds to the property. See my answer. – mm8 Apr 21 '21 at 14:06

2 Answers2

1
    private void CollectionErrors()
    {
        Errors.Clear();
        if (string.IsNullOrWhiteSpace(NumberProp))
        {
            Errors.Add(nameof(NumberProp), "This value cannot be empty");
        }
        else if (decimal.TryParse(NumberProp, out _))
        {
            Errors.Add(nameof(NumberProp), "NumberProp is not a decimal number!");
        }
    }

And it is better not to clear all errors completely, but to clear only the error of the checked property:

public Dictionary<string, string> Errors { get; } = new Dictionary<string, string>();

public string this[string propertyName]
{
    get
    {
        CollectionErrors(propertyName);
        return Errors.ContainsKey(propertyName) ? Errors[propertyName] : string.Empty;
    }
}

private void CollectionErrors(string propertyName)
{
    Errors.Remove(propertyName);
    if (propertyName == nameof(NumberProp))
    {
        if (string.IsNullOrWhiteSpace(NumberProp))
        {
            Errors.Add(nameof(NumberProp), "This value cannot be empty");
        }
        else if (decimal.TryParse(NumberProp, out _))
        {
            Errors.Add(nameof(NumberProp), "NumberProp is not a decimal number!");
        }
    }
}

public string Error => string.Join(Environment.NewLine, Errors.Select(pair => $"[{pair.Key}]=\"{pair.Value}\""));

By convention (contract) INotifyPropertyChanged intreface, the PropertyChanged event should only be raised WHEN the property CHANGES.
Therefore, the correct implementation should be like this:

    private double _number;

    public double Number
    {
        get { return _number; }
        private set
        {
            if (Equals(_number, value))
                return;
            _number = value;
            OnPropertyChanged(nameof(Number));
        }
    }
    private string _numberProp;

    public string NumberProp
    {
        get { return _numberProp; }
        set
        {
            if (Equals(_numberProp, value))
                return;
            _numberProp = value;
            OnPropertyChanged(nameof(NumberProp));
            if (double.TryParse(value, out double number))
                Number = number;
        }
    }

A complete example of a class implementation with two bound properties.
NumberString - Used to bind to a TextBox.
Number - Contains the value of the NumberString property converted to double.
Error checking is implemented for both properties.
Also notice the [CallerMemberName] attribute.
It allows you not to specify the property name in the methods.

public class Data : INotifyPropertyChanged, IDataErrorInfo
{

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    private double _number;

    public double Number
    {
        get { return _number; }
        private set
        {
            if (Equals(_number, value))
                return;
            _number = value;
            CollectionErrors();
            OnPropertyChanged();
        }
    }


    private string _numberProp;

    public string NumberProp
    {
        get { return _numberProp; }
        set
        {
            if (Equals(_numberProp, value))
                return;
            _numberProp = value;
            CollectionErrors();
            OnPropertyChanged();
            if (double.TryParse(value, out double number))
                Number = number;
        }
    }
    public Dictionary<string, string> Errors { get; } = new Dictionary<string, string>();

    public string this[string propertyName] => Errors[propertyName];

    private void CollectionErrors([CallerMemberName] string propertyName = null)
    {
        if (propertyName == null)
            return;

        Errors.Remove(propertyName);
        if (propertyName == nameof(NumberProp))
        {
            if (string.IsNullOrWhiteSpace(NumberProp))
            {
                Errors[nameof(NumberProp)] = "This value cannot be empty";
            }
            else if (decimal.TryParse(NumberProp, out _))
            {
                Errors[nameof(NumberProp)] = "NumberProp is not a decimal number!";
            }
        }
        else if (propertyName == nameof(Number))
        {
            if (Number < 0 || Number > 1000)
                Errors[nameof(Number)] = "The number must be in the range 0 ... 1000!";
        }
    }

    public string Error => string.Join(Environment.NewLine, Errors.Select(pair => $"[{pair.Key}]=\"{pair.Value}\""));
}
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • Cannot convert from `double to string` have added the `.ToString()` to solve that issue, but still has same error.. – Eduards Apr 21 '21 at 13:43
  • The error does not occur in this section of the code. Show the implementation of the NumberProp property and how you get a number from it. – EldHasp Apr 21 '21 at 13:54
  • The NumberProp property must be of type string. And in its setter, if there is no error, there should be a conversion to double or decimal of the private field or other public property. – EldHasp Apr 21 '21 at 14:05
  • I supplemented my answer with the correct implementation of the property. Read it. – EldHasp Apr 21 '21 at 14:14
1

You cannot set a double? property to anything else than a valid double? value. Period.

If you want to customize the error message that you are getting when trying to convert a value like "a" to a double, you could use a ValidationRule in the view as I suggested here.

If you want to prevent the user from entering anything else than digits, you could for example handle the PreviewTextInput event for the TextBox control:

How do I get a TextBox to only accept numeric input in WPF?

It's not the responsibility of the view model to validate how the view or any other component sets any of its properties. This logic belongs to the control or the view.

If you really do want to handle this in the view model alone, you could change the type of your source property to string and then add another double? property that returns the double representation of the current string value.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • Well, as I've already mentioned, you cannot set your `double?` property to a `string` value so if you want to handle this in the view model alone, you need to change the type of your property to for example `string`. You can then have another `double?` property maybe. – mm8 Apr 21 '21 at 14:47
  • I understand that I can't set the property of `double` to a `string` value.. but this still bypasses to enabling the button. I think I'll try the method you mentioned to set the property to string and implement the necessary `if` statements. – Eduards Apr 21 '21 at 14:54
  • 1
    @LV98: I included the option of using a `string` property in the answer. – mm8 Apr 21 '21 at 14:57
  • Managed to do it. I tried both methods. I strongly would recommend for anyone else to go with the 2nd option as it can be re-used for all other textboxes rather than confusing yourself with multiple properties. – Eduards Apr 21 '21 at 19:29