Search code examples
c#wpfvalidationxamlidataerrorinfo

IDataErrorInfo based validation not working


Edit: This is a simplified update of the original version of this post.

In WPF I implemented a UserControl (called 'NumericTextBox') which uses a *DependencyProperty 'Value' that is kept in sync with the Text property of a TextBox (xaml):

<TextBox.Text>
  <Binding Path="Value" 
           Mode="TwoWay"
           ValidatesOnDataErrors="True"
           NotifyOnValidationError="True"
           UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>

For validation purposes I use the IDataErrorInfo interface (xaml.cs):

public partial class NumericTextbox : Textbox, IDataErrorInfo {
    public double Value {
        get { return (double)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(double),  
                                    typeof(NumericTextBox), 
                                    new PropertyMetadata(default(double)));

    public string this[string columnName]
    {
        // Never gets called!
        get { /* Some validation rules here */ }
    }
}

As stated in the source code, the get property actually never gets called, hence no validation happens. Do you see the reason for the problem?

Edit2: Based on ethicallogics' answer I restructered my code. The NumericTextBox now uses an underlying viewmodel class which provides a Dependency Property Value that is bound to the Text property of the TextBox which is declared by NumericTextBox. Additionally NumericTextBox uses the viewmodel as its datacontext. The viewmodel is now responsible for checking changes of the Value property. As the value restrictions of NumericTextBox are customizable (e.g. the Minimum can be adapted) it forwards these settings to the viewmodel object.


Solution

  • Do it like this rather than creating any Dependency Property . Validations are applied at ViewModel not in Control or View . Try it like this I hope this will help.

    public class MyViewModel : INotifyPropertyChanged, IDataErrorInfo
    {
        public MyViewModel()
        {
            Value = 30;
        }
        private double _value;
    
        [Range(1, 80, ErrorMessage = "out of range")]
        public double Value
        {
            get
            {
                return _value;
            }
            set
            {
                _value = value;
                ValidationMessageSetter("Value", value);
            }
        }
    
        private void ValidationMessageSetter(string propertyName, object value)
        {
            Notify(propertyName);
            string validationresult = ValidateProperty(propertyName, value);
            if (!string.IsNullOrEmpty(validationresult) && !_dataErrors.ContainsKey(propertyName))
                _dataErrors.Add(propertyName, validationresult);
            else if (_dataErrors.ContainsKey(propertyName))
                    _dataErrors.Remove(propertyName);
    
        }
    
        #region INotifyPropertyChanged Members
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        #endregion
    
        private void Notify(string str)
        { 
            if(PropertyChanged!=null)
                PropertyChanged(this,new PropertyChangedEventArgs(str));
        }
    
        private string ValidateProperty(string propertyName,object value)
        {
            var results = new List<ValidationResult>(2);
            string error = string.Empty;
    
            bool result = Validator.TryValidateProperty(
                value,
                new ValidationContext(this, null, null)
                {
                    MemberName = propertyName
                },
                results);
    
            if (!result && (value == null || ((value is int || value is long) && (int)value == 0) || (value is decimal && (decimal)value == 0)))
                return null;
    
            if (!result)
            {
                ValidationResult validationResult = results.First();
                error = validationResult.ErrorMessage;
            }
    
            return error;    
        }
    
        #region IDataErrorInfo Members
    
        private Dictionary<string, string> _dataErrors = new Dictionary<string, string>();
    
        public string Error
        {
            get { throw new NotImplementedException(); }
        }
    
        public string this[string columnName]
        {
            get
            {
                if (_dataErrors.ContainsKey(columnName))
                    return _dataErrors[columnName];
                else
                    return null;
            }
        }
    
        #endregion
    }
    
    <TextBox Text="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
    

    I hope this will help.