Search code examples
wpfvalidationmvvmidataerrorinfovalidationrules

Validation between multiple fields in different levels


I have a problem with validations between multiple fields. For example, I have a ViewModel named RangeDateViewModel that contains 2 instances of a class named DateViewModel - they represent a start date and an end date respectively.

So my binding looks like this:

<TextBox Text="{Binding StartDate.Date, ValidateOnDataError=True}">
<TextBox Text="{Binding EndDate.Date, ValidateOnDataError=True}">

My RangeDateViewModel class implements the IDataErrorInfo interface. In my plan, the RangeDateViewModel would validate that the start date is before the end date, by applying the validation logic in the IDataErrorInfo["propertyName"] function like this:

public string this[string columnName]
{
     get
     {
        return ValidationError();
     }
}

The problem is that this is never being called, and instead the IDataErrorInfo properties that reside in each of the DateViewModel classes are being called instead.

I guess this is because the bound property is not in the same level of RangeDateViewModel, but instead inside the child DateViewModel.

I think my need is quite basic and there must be an easy solution for this problem.

I tried using ValidationRules instead of IDataErrorInfo but then I'd problems letting the ViewModel know of the current validation status from the ValidationRules.


Solution

  • Try using the following approach:

    1. Create a DataTemplate for DateViewModel:

      <DataTemplate DataType="{x:Type ViewModels:DateViewModel}">
          <TextBox Text="{Binding Date}">
      </DataTemplate>
      
    2. Bind the instances of this ViewModel to a ContentControl and set ValidateOnDataError to true on that binding:

      <ContentControl Content="{Binding StartDate, ValidateOnDataError=True}" />
      <ContentControl Content="{Binding EndDate, ValidateOnDataError=True}" />
      
    3. In RangeDateViewModel subscribe to the PropertyChanged event of StartDate and EndDate and when raised, raise a PropertyChanged event with StartDate / EndDate:

      StartDate.PropertyChanged += (s, e) => InvokePropertyChanged("StartDate");
      EndDate.PropertyChanged += (s, e) => InvokePropertyChanged("EndDate");