Share via


WPF: Passing a Data Bound Value To a Validation Rule

Introduction

One way of validating and restricting data bound property values in WPF is to use validation rules. There was a question about how to pass a value through data binding to such a validation rule on the MSDN forums the other day and this article provides an example of how you could do this.

ValidationRule

A ValidationRule is a custom class that derives from the abstract System.Windows.Controls.ValidationRule class and implements its Validate method. The method takes the data bound value and a System.Globalization.CultureInfo as arguments and returns a ValidationResult that is either valid or invalid.

As an example, consider the below TextBox that is bound to an "Age" source property and uses the custom AgeValidationRule class to check that the given age is within a valid range (from 10 to 100 in this case):

<TextBox xmlns:local="clr-namespace:WpfApplication1">
    <TextBox.Text>
        <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:AgeValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>
namespace WpfApplication1
{
  public partial  class MainWindow : Window
  {
    public MainWindow() {
      InitializeComponent();
      this.DataContext = new  SampleViewModel();
    }
  }
 
  public class  SampleViewModel
  {
    public int  Age {
      get;
      set;
    }
  }
 
  public class  AgeValidationRule : System.Windows.Controls.ValidationRule
  {
    public override  ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) {
      string text = value.ToString();
      int age;
      int.TryParse(text, out  age);
      if(age < 10 || age > 100)
        return new  ValidationResult(false, "Invalid age.");
 
      return ValidationResult.ValidResult;
    }
  }
}

If you for example want to be able to specify the maximum valid value (100 in the sample code above) without hard coding it in the Validate method, you could just add a CLR property to the custom ValidationRule class and set this one in the XAML markup like this:

<TextBox xmlns:local="clr-namespace:WpfApplication1">
    <TextBox.Text>
        <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:AgeValidationRule MaxAge="100" />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>
public class  AgeValidationRule : System.Windows.Controls.ValidationRule
{
  public int  MaxAge { get; set; }
 
  public override  ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) {
    string text = value.ToString();
    int age;
    int.TryParse(text, out  age);
    if(age < 10 || age > this.MaxAge)
      return new  ValidationResult(false, "Invalid age.");
 
    return ValidationResult.ValidResult;
  }
}

But if you for example want to be able to specify the maximum allowed value in the view model and then bind the MaxAge property of the AgeValidationRule class to this value, you need to define the MaxAge as a depenency property because a target property that you want to bind something to must be a dependency property. And to be able to define dependency property the class should inherit from System.Window.DependencyObject and the ValidationRule doesn't. So how to solve this?

Wrapper

You could create a wrapper class that derives from DependencyObject and exposes a dependency property. Then you add a CLR property to the AgeValidationRule class that returns an instance of this wrapper type:

  

public class  Wrapper : DependencyObject
  {
    public static  readonly DependencyProperty MaxAgeProperty =
         DependencyProperty.Register("MaxAge", typeof(int),
         typeof(Wrapper), new  FrameworkPropertyMetadata(int.MaxValue));
 
    public int  MaxAge {
      get { return (int)GetValue(MaxAgeProperty); }
      set { SetValue(MaxAgeProperty, value); }
    }
  }
 
  public class  AgeValidationRule : System.Windows.Controls.ValidationRule
  {
    public override  ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) {
      string text = value.ToString();
      int age;
      int.TryParse(text, out  age);
      if (age < 10 || age > this.Wrapper.MaxAge)
        return new  ValidationResult(false, "Invalid age.");
 
 
      return ValidationResult.ValidResult;
    }
 
    public Wrapper Wrapper { get; set; }
  }

Then you will be able to set the Wrapper property and bind its MaxAge dependency property in the XAML markup as per below:

<TextBox xmlns:local="clr-namespace:WpfApplication1">
    <TextBox.Text>
        <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:AgeValidationRule>
                    <local:AgeValidationRule.Wrapper>
                        <local:Wrapper MaxAge="{Binding MaxAge}"/>
                    </local:AgeValidationRule.Wrapper>
                </local:AgeValidationRule>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>
public class  SampleViewModel
{
  public int  Age {
    get;
    set;
  }
 
  public int  MaxAge { get  { return  100; } }
}

Data Context

The binding to the MaxAge property of the view model will however not work just yet. Why is that? Because a ValidationRule is not added to the visual tree and thus it doesn't inherit the DataContext of the TextBox. This means that there is no property named "MaxAge" to bind to from the Wrapper object that is created in the XAML markup. 

You could solve this by creating a proxy object that captures the current DataContext in a dependency property and then bind to the MaxAge source property through this dependency property. 

There is a System.Windows.Freezable class in WPF and objects of this type is able to actually inherit the DataContext even when they’re not in the visual tree. So if you create a class that inherits from Freezable and add a dependency property to it to store a reference to the current DataContext, you could then add an instance of it to the resources section of the TextBox as shown below:

  

public class  BindingProxy : System.Windows.Freezable
  {
    protected override  Freezable CreateInstanceCore() {
      return new  BindingProxy();
    }
 
    public object  Data {
      get { return (object)GetValue(DataProperty); }
      set { SetValue(DataProperty, value); }
    }
 
    public static  readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new  PropertyMetadata(null));
  }
<TextBox xmlns:local="clr-namespace:WpfApplication1">
    <TextBox.Resources>
        <local:BindingProxy x:Key="proxy" Data="{Binding}"/>
    </TextBox.Resources>
    <TextBox.Text>
        <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:AgeValidationRule>
                    <local:AgeValidationRule.Wrapper>
                        <local:Wrapper MaxAge="{Binding Data.MaxAge, Source={StaticResource proxy}}"/>
                    </local:AgeValidationRule.Wrapper>
                </local:AgeValidationRule>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Note that the MaxAge property of the Wrapper object is now bound to the MaxAge property of the Data property (this contains a reference to the view model itself) of the proxy object.

And that's what it takes to be able to pass, or bind, a value of a source property to a custom validation rule.

Additional Resources

Data validation in WPF: http://blog.magnusmontin.net/2013/08/26/data-validation-in-wpf/
ValidationRule Class: http://msdn.microsoft.com/en-us/library/system.windows.controls.validationrule(v=vs.110).aspx
Dependency Properties Overview: http://msdn.microsoft.com/en-us/library/ms752914(v=vs.110).aspx
Freezable Objects Overview: /en-us/dotnet/framework/wpf/advanced/dependency-properties-overview