Search code examples
c#wpfbindingidataerrorinfo

How to generate an error for a property of a class


I've one ViewModel that has a property. This property is a "PhysicalValue". This PhysicalValue is a class that is composed from a Value and an Unit.

public Class MyViewModel:INotifyPropertyChanged:IDataErrorInfo {
    public PhysicalValue Target {get => _target; set => {_target = value; NotifyPropertyChanged("Target");}}

    public string this[string columnName]
    {
        get
        {
            if (columnName == "Target")
            {
                if(_target.Value>5000){
                    return "out of spec value";
                }
            }
            return String.Empty;
        }
    }   
}

I've one control that should edit the value of this PhysicalValue:

[...]
<dxe:TextEdit EditValue="{Binding Target.Value, ValidatesOnDataErrors =true}"></dxe:TextEdit>
[...]

But I've no error for it(probably because it search for Target.Value for an error. I've tried another approach, bind directly to Target, but this doesn't work, because I need the convertBack to known which Unit was used originally to rebuild a PhysicalValue.

How would you solve this?


Solution

  • The EditValue property of your TextEdit control is bound to PhysicalValue.Value. When you set Binding.ValidatesOnDataErrors to true, the binding engine checks if the class that has the target property implements IDataErrorInfo. So for this to work the PhysicalValue class needs to implement IDataErrorInfo:

    class PhysicalValue : IDataErrorInfo
    {
        public double Value { get; set; }
    
        public object Unit { get; set; }
    
        public string Error => this["Value"] + this["Unit"];
    
        public PhysicalValue(int v, object u)
        {
            Value = v;
            Unit = u;
        }
    
        public string this[string columnName]
        {
            get
            {
                if (columnName == "Value")
                {
                    if (Value > 5000)
                    {
                        return "out of spec value";
                    }
                }
                return String.Empty;
            }
        }
    }
    

    If the PhysicalValue class can not hold the validation logic or can not implement the IDataErrorInfo interface you could create a proxy class that handles the validation and bind to that instead. Here is a small example:

    class PhysicalValueValidator : IDataErrorInfo
    {
        private readonly PhysicalValue _physicalValue;
        private double _maxValue;
    
        public double Value
        {
            get { return _physicalValue.Value; }
            set { _physicalValue.Value = value; }
        }
    
        public PhysicalValueValidator(PhysicalValue pv)
        {
            _physicalValue = pv;
            _maxValue = 5000;
        }
    
        public void SetMaxValue(double maxValue)
        {
            _maxValue = maxValue;
        }
    
        public string this[string columnName]
        {
            get
            {
                if (columnName == "Value")
                {
                    if (Value > _maxValue)
                    {
                        return "out of spec value";
                    }
                }
                return String.Empty;
            }
        }
    
        public string Error => this["Value"];
    }
    

    The PhysicalValueValidator class exposes the SetMaxValue(..) method to update the validation logic from your panes viewmodel. The viewmodel could look like this:

    class MyViewModel 
    {
        private PhysicalValue _target;
    
        public PhysicalValueValidator TargetValidator { get; }
    
        public MyViewModel()
        { 
            _target = new PhysicalValue(5, 10);
            TargetValidator = new PhysicalValueValidator(_target);
    
            // update validation Logic...
            TargetValidator.SetMaxValue(6000);
        }
    }
    

    In your xaml code, bind to TargetValidator.Value instead of Target.Value.

    For more information on IDataErrorInfo check this link: IDataErrorInfo