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:

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.