Data Validation in WPF

This article shows how to create a rule that validates data set through a Binding. It also tackles the problem of passing additional parameters to the validation method.

Suppose we want to build a simple application that lets us pick a number with a Slider and then type another number into a TextBox.

The application must not accept a number that isn't a multiple of the one selected with the Slider.

The user interface might look like this:

Sample UI

The problem

The 2 numbers entered by the user will be stored in a DivisionNumbers class that implements the following interface:

interface IDivisionNumbers : INotifyPropertyChanged
{
  int Dividend { get; set; }
  int Divisor { get; set; }
}

In the constructor of the MainWindow class we set up an instance of this class and assign it to the DataContext property so that UI elements can use Binding.

public MainWindow()
{
  InitializeComponent();
  var nums = new DivisionNumbers();
  nums.Divisor = 1;
  nums.Dividend = 1;
  this.DataContext = nums;
}

The logic that validates the data entered in the TextBox goes into a ValidationRule subclass. An instance of this class is added to the ValidationRules property of the Binding associated with the TextBox. When the user types a number into the text box, our rule checks whether it is a multiple of the number selected with the Slider.<br />Here is the XAML for all of the above:

<TextBox>
  <TextBox.Text>
    <Binding Path="Dividend" UpdateSourceTrigger="PropertyChanged">
      <Binding.ValidationRules>
        <local:IsMultipleOfValidationRule />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>

An incomplete implementation of our rule might look like this:

public class IsMultipleOfValidationRule : ValidationRule
{
  public override ValidationResult Validate(object value, CultureInfo cultureInfo)
  {
    try
    {
      int dividend = (int)Convert.ChangeType(value, typeof(int));
      int divisor = // Somehow get the divisor...
      if (dividend % divisor == 0)
        return ValidationResult.ValidResult;
      return new ValidationResult(
        false,
        "The number is not a multiple of " + divisor);
    }
    catch
    {
      return new ValidationResult( false, "Not a number." );
    }
  }
}

And here is where the problem shows up: how do we get the divisor the user selected inside the Validate method?

We can't add a Divisor property to the IsMultipleOfValidationRule class and bind it, because ValidationRule doesn't derive from DependencyObject.

How can we reach this goal, seemingly blocked by a technical barrier, in the most elegant way?

The solution

Let's add the following XAML fragment to our window:

<Window.Resources>
  <FrameworkElement x:Key="DataContextBridge" />
</Window.Resources>
<Window.DataContext>
  <Binding Mode="OneWayToSource" Path="DataContext" Source="{StaticResource DataContextBridge}" />
</Window.DataContext>

This way we've created a virtual element, DataContextBridge, to which the DataContext of our window is bound.

Now let's create a new class that holds a number:

public class IntegerContainer : FrameworkElement
{
  public int Value
  {
    get { return (int)GetValue( ValueProperty ); }
    set { SetValue( ValueProperty, value ); }
  }
 
  public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
    "Value",
    typeof( int ),
    typeof( IntegerContainer ),
    new UIPropertyMetadata( 0 ) );
}

Next, let's update our validation rule to use IntegerContainer:

public class IsMultipleOfValidationRule : ValidationRule
{
  public IntegerContainer DivisorContainer { get; set; }
 
  public override ValidationResult Validate(
    object value, CultureInfo cultureInfo )
  {
    try
    {
      int dividend = (int)Convert.ChangeType( value, typeof( int ) );
      int divisor = this.DivisorContainer.Value;
      if( dividend % divisor == 0 )
        return ValidationResult.ValidResult;
      return new ValidationResult(
        false,
        "The number is not a multiple of " + divisor );
    }
    catch
    {
      return new ValidationResult( false, "Not a number." );
    }
  }
}

All that's left is to configure our rule in the XAML.

<TextBox>
  <TextBox.Text>
    <Binding Path="Dividend" UpdateSourceTrigger="PropertyChanged">
      <Binding.ValidationRules>
        <local:IsMultipleOfValidationRule>
          <local:IsMultipleOfValidationRule.DivisorContainer>
            <!-- This IntegerContainer is the "child node" of
                 the DataContextBridge element, in the virtual
                 branch attached to the Window's logical tree. -->
            <local:IntegerContainer
              DataContext="{Binding Source={StaticResource DataContextBridge}, Path=DataContext}"
              Value="{Binding Divisor}" />
          </local:IsMultipleOfValidationRule.DivisorContainer>
        </local:IsMultipleOfValidationRule>
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>

Done!

Only registered users can leave a comment.

Sign in or register to leave a comment.