Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to validate two properties which depend on each other?

I have view model with 2 properties: A and B and I want to validate that A < B.

Below is my simplified implementation where I use custom validation rule. Since each property is validated independently, it lead to an anoying issue: if entered A value is invalid, than it stay so even after changing B, since validation of B doesn't know anything about A.

This can be seen on this demo:

A is invalid after entering 11, that's correct since 11 > 2. Changing B to 22 doesn't re-evalute A, I have to edit A to have validation passed.

What I want? I want that after enering 22 into B the red border (validation error) disappears and A = 11, B = 22 would be source values in view model.

How can I in B validation somehow force A validation after new B value is synchronized with source?


View model:

public class ViewModel : INotifyPropertyChanged
{
    int _a;
    public int A
    {
        get => _a;
        set
        {
            _a = value;
            OnPropertyChanged();
        }
    }

    int _b;
    public int B
    {
        get => _b;
        set
        {
            _b = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public virtual void OnPropertyChanged([CallerMemberName] string property = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}

View:

<StackPanel>
    <TextBox Margin="10" Text="{local:MyBinding A}" />
    <TextBox Margin="10" Text="{local:MyBinding B}" />
</StackPanel>

View code:

public MainWindow()
{
    InitializeComponent();
    DataContext = new ViewModel { A = 1, B = 2 };
}

Binding:

public class MyBinding : Binding
{
    public MyBinding(string path) : base(path)
    {
        UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        ValidationRules.Add(new MyValidationRule());
    }
}

Validation rule:

public class MyValidationRule : ValidationRule
{
    public MyValidationRule() : base(ValidationStep.ConvertedProposedValue, false) { }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo) => ValidationResult.ValidResult; // not used

    public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingExpressionBase owner)
    {
        var binding = owner as BindingExpression;
        var vm = binding?.DataItem as ViewModel;
        switch (binding.ResolvedSourcePropertyName)
        {
            case nameof(vm.A):
                if ((int)value >= vm.B)
                    return new ValidationResult(false, "A should be smaller than B");
                break;
            case nameof(vm.B):
                if ((int)value <= vm.A)
                    return new ValidationResult(false, "B should be bigger than A");
                break;
        }
        return base.Validate(value, cultureInfo, owner);
    }
}
like image 894
Sinatr Avatar asked Oct 16 '19 10:10

Sinatr


1 Answers

ValidationRules don't support invalidating a property when setting another property.

What you should do is to implement INotifyDataErrorInfo in your view model and raise the ErrorsChanged event whenever you want to refresh the validation status for a property.

There is an example available in the this TechNet article.

like image 95
mm8 Avatar answered Nov 15 '22 08:11

mm8