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:
WpfTextBoxValidation.zip
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.