5

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.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Dror
  • 2,548
  • 4
  • 33
  • 51

2 Answers2

1

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");
    
Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • Thanks Daniel! I tried what you'd suggested but apparently its still not good enough. The IDataErrorInfo property is indeed being accesses but only at the initialization of the template, and not later when the actual data is changed. I guess this is because the StartDate and EndDate are complex object that they themselves are not being changed, but properties inside them, and this is not sufficient form them to raise the PropertyChanged. Maybe I should somehow raise an event when the inner date properties change? – Dror Dec 06 '11 at 11:15
  • Thanks again Daniel! This has worked though I still has one last minor issue I'm facing. With this solution the result is the two controls marked in red when the value is invalid. I'd like that instead of them being marked in red, the Stackpanel that holds these two fields will be marked in read. Preferably these two fields will not be marked, but it's not a must. I tried applying a DataTrigger on a bool property on the RangeDateViewModel named "HasErrors" that will set "Validation.HasError" to true, but unfortunately it's a read only property. I hope you could help me with this issue as well. – Dror Dec 06 '11 at 12:17
  • 1
    @Dror: Just take it up one more step: Create a data template for the RangeDateViewModel instead of DateViewModel... Adjust the other two points accordingly. – Daniel Hilgarth Dec 06 '11 at 14:40
  • Thanks Daniel! For some odd reason I thought about it but didn't actually consider it seriously. This solution requires some event subscribing and unsubscribing, but I guess this is the best solution that exists so far. Thanks a lot! – Dror Dec 06 '11 at 15:23
1

I had the problem that public string this[string columnName] was simply not called just the other week.

The solution was simple. The binding WPF binding engine could not follow the nesting of my ViewModels.

I had assumed that I needed to implement the property in the ViewModel that is the current DataContext, but instead it needs to be implemented in the ViewModel that is bound to the control.

Example:

<TextBox Text="{Binding Path=ProductViewModel.DescriptionViewModel.ProductName,
                                    Mode=TwoWay,
                                    ValidatesOnDataErrors=True,
                                    NotifyOnValidationError=True}" />

Here DescriptionViewModel is the class that contains the bound property. IDataErrorInfo needs to be implemented in that class (not in ProductViewModel or another class up the hierarchy that may contain it) then everything will work fine.

Jens H
  • 4,590
  • 2
  • 25
  • 35
  • Hi Sensei, thanks for your comment. I am aware that IDataErrorInfo is working fine when it's implemented in the lower class in the binding hierarchy, but I got 2 problems with it. 1 - The I got two classes involved in the validation logic but each of these lower level classes don't know each other. 2 - I don't want just one of the fields to be marked in red if the value is invalid. I want the whole control to be marked in red. – Dror Dec 06 '11 at 12:36