Question

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.

Was it helpful?

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.

OTHER TIPS

The IDataErrorInfo interface should be implemented on the object that is being bound to, not on the object that has the DependencyProperty.

In your example, if you want to get validation using this mechanism then your view model needs to do something like the below for the Value property:

public class ViewModel : IDataErrorInfo
{
    public string this[string columnName]
    {
        // Never gets called!
        get
        { 
            if (columnName == "Value")
                return GetValidationMessageForValueField();

            return null;
        }
    }
}

I'm guessing that what you actually want to do is to validate when someone enters a non-numeric value in the TextBox..? If this is the case the you probably want to take a different approach than using IDataErrorInfo

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top