TextBox Validation – How to bind to properties of the Validation of a TextBox?
Have you ever wanted to have a TextBox that requires data or specifically formatted data and you want to enforce this validation and display this to the user.
Well, I figured this out, with a great deal of pain, but hey, it works.
You can download this project in it’s entirety here:
I started with a new WPF Project in Visual Studio. Here is a general overview of the steps I took to make this example project happen.
MainWindow.xaml
- Here, create a simple form to fill out using TextBlock and TextBox pairs in a Grid.
- Configure the TextBox elements to use binding.
- Configure the TextBox elements to use the appropriate Validation. (See the validation examples we will create below.)
- Below the form you created in a StackPanel, add some TextBlock elements to display the Validation’s ErrorConent.
- Bind each TextBlock element to the TextBox in the form they pertain to.
- Bind the Visibility of each TextBlock to the Errors. (We will use the converter below to return Visible if there is ErrorContent and Collapsed if not.)
<Window x:Class="WpfTextBoxValidation.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfTextBoxValidation" Title="MainWindow" Height="350" Width="525"> <Grid Name="MainGrid"> <Grid.Resources> <Style TargetType="TextBox"> <Setter Property="MaxWidth" Value="200" /> </Style> </Grid.Resources> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" MinWidth="200" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <!-- Labels --> <TextBlock Text="FirstName" /> <TextBlock Text="LastName" Grid.Row="1" /> <TextBlock Text="Age" Grid.Row="2" /> <TextBlock Text="Phone" Grid.Row="3" /> <!-- TextBlocks --> <TextBox Name="TextBoxFirstName" Grid.Column="1"> <TextBox.Text> <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged" > <Binding.ValidationRules> <local:TextBoxNotEmptyValidationRule x:Name="FirstNameValidation" ValidatesOnTargetUpdated="True" Message="You must enter a first name."/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <TextBox Name="TextBoxLastName" Grid.Row="1" Grid.Column="1"> <TextBox.Text> <Binding Path="LastName" UpdateSourceTrigger="PropertyChanged" > <Binding.ValidationRules> <local:TextBoxNotEmptyValidationRule x:Name="LastNameValidation" ValidatesOnTargetUpdated="True" Message="You must enter a last name."/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <TextBox Name="TextBoxAge" Grid.Row="2" Grid.Column="1"> <TextBox.Text> <Binding Path="Age" UpdateSourceTrigger="PropertyChanged" > <Binding.ValidationRules> <local:OverThirteenValidationRule x:Name="AgeValidation" ValidatesOnTargetUpdated="True"/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <TextBox Name="TextBoxPhone" Grid.Row="3" Grid.Column="1"> <TextBox.Text> <Binding Path="Phone" UpdateSourceTrigger="PropertyChanged" > <Binding.ValidationRules> <local:TextBoxNotEmptyValidationRule x:Name="PhoneValidation" ValidatesOnTargetUpdated="True" Message="You must enter a phone number."/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <!-- Validation List --> <StackPanel Grid.Row="4" Grid.ColumnSpan="2"> <StackPanel.Resources> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="Red" /> </Style> <local:ErrorCollectionToVisibility x:Key="ToVisibility" /> </StackPanel.Resources> <TextBlock Visibility="{Binding ElementName=TextBoxFirstName, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}"> <TextBlock.Text> <MultiBinding StringFormat="FirstName - {0}"> <Binding ElementName="TextBoxFirstName" Path="(Validation.Errors)[0].ErrorContent"/> </MultiBinding> </TextBlock.Text> </TextBlock> <TextBlock Visibility="{Binding ElementName=TextBoxLastName, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">> <TextBlock.Text> <MultiBinding StringFormat="LastName - {0}"> <Binding ElementName="TextBoxLastName" Path="(Validation.Errors)[0].ErrorContent"/> </MultiBinding> </TextBlock.Text> </TextBlock> <TextBlock Visibility="{Binding ElementName=TextBoxAge, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">> <TextBlock.Text> <MultiBinding StringFormat="Age - {0}"> <Binding ElementName="TextBoxAge" Path="(Validation.Errors)[0].ErrorContent"/> </MultiBinding> </TextBlock.Text> </TextBlock> <TextBlock Visibility="{Binding ElementName=TextBoxPhone, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">> <TextBlock.Text> <MultiBinding StringFormat="Phone - {0}"> <Binding ElementName="TextBoxPhone" Path="(Validation.Errors)[0].ErrorContent"/> </MultiBinding> </TextBlock.Text> </TextBlock> <!--Text="{Binding ElementName=DirectoryBox, Path=(Validation.Errors)[0].ErrorContent}"--> </StackPanel> </Grid> </Window>
MainWindow.xaml.cs
To this file we add a Person class and then create and instance of it and set it the instance as the DataContext.
using System; using System.Windows; using System.ComponentModel; namespace WpfTextBoxValidation { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Person p = new Person(); MainGrid.DataContext = p; } } public class Person { public String FirstName { get; set; } public String LastName { get; set; } public int Age { get; set; } public String Phone { get; set; } } }
ErrorCollectionToVisibility.cs
This class is used to convert the Validation.Error collection to a Visibility.
There is a property Validation.HasError but for some reason it is not available to bind to. If it were, I would bind to it and use the built-in BooleanToVisibility converter. But since, I can’t, I used this ErrorCollectionToVisibility converter which simply returns Visible if there is at least one item in the collection or Collapse if the Collection is null or empty.
using System; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace WpfTextBoxValidation { class ErrorCollectionToVisibility : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { ReadOnlyCollection<ValidationError> collection = value as ReadOnlyCollection<ValidationError>; if (collection != null && collection.Count > 0) return Visibility.Visible; else return Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return new object(); } } }
OverThirteenValidationRule.cs
Here we check if the value is greater than 13 and if not, we return the false ValidationResult.
using System; using System.Windows.Controls; namespace WpfTextBoxValidation { public class OverThirteenValidationRule : ValidationRule { public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { if (value != null) { int age = 0; try { age = Convert.ToInt32(value); } catch { return new ValidationResult(false, "You must be older than 13!"); } if (age > 13) return ValidationResult.ValidResult; } return new ValidationResult(false, "You must be older than 13!"); } } }
TextBoxNotEmptyValidationRule.cs
This validation just makes sure there is at least one character in a TextBlock.
using System; using System.Windows.Controls; namespace WpfTextBoxValidation { public class TextBoxNotEmptyValidationRule : ValidationRule { public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { string str = value as string; if (str != null) { if (str.Length > 0) return ValidationResult.ValidResult; } return new ValidationResult(false, Message); } public String Message { get; set; } } }
You have now learned to bind to Validation.ErrorContent.