Eliza Bennet
Eliza Bennet

Reputation: 179

How can I use IDataErrorInfo to validate child objects from the parent class?

I have a class, DataModel, that has a bunch of objects which are all of the same class, InputItem. The InputItem class has a nullable decimal property, Value, which I want to validate in the DataModel class (not in the InputItem class). This is because the validation of one InputItem object depends on the value in another InputItem object. I'd like to use IDataErrorInfo for this purpose, but haven't been able to make it work (this code allows me to bind to the values in the InputItem, but doesn't perform any validation).

My DataModel class:

using System.ComponentModel;

public class DataModel : IDataErrorInfo
{
    public InputItem Item1 {get; set;}
    public InputItem Item2 {get; set;}
    public InputItem Item3 {get; set;}
    //... more InputItem objects

    public string Error { get { return string.Empty;} }

    public string this[string propertyName]
    {
        get
        {
            string result = string.Empty;
            switch (propertyName)
            {
                case "Item1":
                case "Item1.Value":
                    if (Item1.Value == null)
                        result = "Item 1 is required.";
                    else if (Item1.Value < 0)
                        result = "Item 1 must be greater than 0.";
                    break;
                case "Item2":
                case "Item2.Value":
                    if (Item2.Value == null)
                        result = "Item 2 is required.";
                    else if (Item2.Value < Item1.Value)
                        result = "Item 2 must be greater than item 1";
                    break;
                case "Item3":
                case "Item3.Value":
                    if (Item3.Value == null)
                        result = empty.String;
                    else if (Item3.Value < Item1.Value)
                        result = "Item 3 must be greater than Item 1."
                    else if (Item3.Value > Item2.Value)
                        result = "Item 3 must be less than Item 2."
            }
            return result;
        }
    }
}

public class InputItem : INotifyPropertyChanged
{
    //Implements INotifyPropertyChanged, but I'm leaving it out to shorten the code

    public string Name {get; set;}

    public decimal? Value {get; set;}

    public string Unit {get; set;}

    public string Tip { get; set; }

    //... more properties

    // ... Standard constructors
}

Ultimately, I want to bind some of the properties of some TextBox objects in a XAML view to the properties in the different InputItem objects, like so (with the view's DataContext set to the DataModel object):

<TextBox Text="{Binding Path=Item1.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>

Previously, I tried setting validation rules for each of the InputItem objects with minimum and maximum allowable values, and then binding the minimum and maximum values inside the validation rules to values in other InputItem objects, but as you can probably tell from my description, this gets complicated and tangled fast. Here's the code I used for the ValidationRule and bindings strategy, in case you're interested:

using System.Globalization;
using System.Windows;
using System.Windows.Controls;

public class NumberValidationRuleWithWrapper : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        decimal result = 0;
        bool canConvert = decimal.TryParse(value as string, out result);
        if (!canConvert)
            return new ValidationResult(false, "Must be a valid number");

        decimal? maxval = this.Wrapper.MaxValue;
        if (maxval != null && !double.IsNaN((double)maxval))
        {
            if (result >= maxval)
                return new ValidationResult(false, "Must be less than " + ((decimal)maxval).ToString());
        }

        decimal? minVal = this.Wrapper.MinValue;
        if (minVal != null && !double.IsNaN((double)minVal))
        {
            if (result <= minVal)
                return new ValidationResult(false, "Must be greater than " + ((decimal)minVal).ToString());
        }
        return ValidationResult.ValidResult;
    }

    public DecimalWrapper Wrapper { get; set; }
}

public class DecimalWrapper : DependencyObject
{
    public static readonly DependencyProperty MaxValueProperty =
        DependencyProperty.Register("MaxValue",
            typeof(double?),
            typeof(DecimalWrapper),
            new FrameworkPropertyMetadata(double.MaxValue));

    public static readonly DependencyProperty MinValueProperty =
        DependencyProperty.Register("MinValue",
            typeof(double?),
            typeof(DecimalWrapper),
            new FrameworkPropertyMetadata(double.MinValue));

    public decimal? MaxValue
    {
        get { return (decimal?)GetValue(MaxValueProperty); }
        set { SetValue(MaxValueProperty, value); }
    }

    public decimal? MinValue
    {
        get { return (decimal?)GetValue(MinValueProperty); }
        set { SetValue(MinValueProperty, value); }
    }
}

I've looked at How to validate child objects by implementing IDataErrorInfo on parent class, but it doesn't apply to this situation (that person really wanted to validate the individual child objects on their own, not through the parent object).

I also looked at this post by Brian Lagunas, but it doesn't apply either, since each of the InputItem objects will have different rules relating to other InputItem objects (e.g. Item1 can be any number, Item2 has to be less than Item1, Item3 has to be between Item1 and Item2, Item4 has to be greater than Item3, etc).

Upvotes: 0

Views: 1268

Answers (1)

mm8
mm8

Reputation: 169210

When you bind to Item1.Value, it is only the IDataErrorInfo implementation of the type to which the Value property belongs, i.e. InputItem, that matters. It doesn't matter whether DataModel implements IDataErrorInfo.

The INotifyDataErrorInfo interface that was introcuded in .NET Framework 4.5 makes WPF data validation a lot more flexible.

If you want to perform the validation in the DataModel class, you could implement this interface in the InputItem class and set the actual errors returned from the GetErrors method and raise the ErrorsChanged event from the DataModel class. There is an example of how to implement the interface available here: https://social.technet.microsoft.com/wiki/contents/articles/19490.wpf-4-5-validating-data-in-using-the-inotifydataerrorinfo-interface.aspx.

Upvotes: 1

Related Questions