Bookmark and Share Share...    Subscribe to this feed Feed   About Christian Moser  


Data Validation in WPF

What we want to do is a simple entry form for an e-mail address. If the user enters an invalid e-mail address, the border of the textbox gets red and the tooltip is showing the reason.

Implementing a ValidationRule (.NET 3.0 style)

In this example I am implementing an generic validation rule that takes a regular expression as validation rule. If the expression matches the data is treated as valid.

 
/// <summary>
/// Validates a text against a regular expression
/// </summary>
public class RegexValidationRule : ValidationRule
{
    private string _pattern;
    private Regex _regex;
 
    public string Pattern
    {
        get { return _pattern; }
        set
        {
            _pattern = value;
            _regex = new Regex(_pattern, RegexOptions.IgnoreCase);
        }
    }
 
    public RegexValidationRule()
    {
    }
 
    public override ValidationResult Validate(object value, CultureInfo ultureInfo)
    {
        if (value == null || !_regex.Match(value.ToString()).Success)
        {
            return new ValidationResult(false, "The value is not a valid e-mail address");
        }
        else
        {
            return new ValidationResult(true, null);
        }
    }
}
 
 

First thing I need to do is place a regular expression pattern as string to the windows resources

 
<Window.Resources>
        <sys:String x:Key="emailRegex">^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@
        [a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]
        *[a-zA-Z]$</sys:String>    
</Window.Resources>
 
 

Build a converter to convert ValidationErrors to a multi-line string

The following converter combines a list of ValidationErrors into a string. This makes the binding much easier. In many samples on the web you see the following binding expression:

{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}

This expression works if there is one validation error. But if you don't have any validation errors the data binding fails. This slows down your application and causes the following message in your debug window:

System.Windows.Data Error: 16 : Cannot get ‘Item[]‘ value (type ‘ValidationError’) from ‘(Validation.Errors)’ (type ‘ReadOnlyObservableCollection`1′). BindingExpression:Path=(0).[0].ErrorContent; DataItem=’TextBox’...

The converter is both, a value converter and a markup extension. This allows you to create and use it at the same time.

 
[ValueConversion(typeof(ReadOnlyObservableCollection<ValidationError>), typeof(string))]
public class ValidationErrorsToStringConverter : MarkupExtension, IValueConverter
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return new ValidationErrorsToStringConverter();
    }
 
    public object Convert(object value, Type targetType, object parameter, 
        CultureInfo culture)
    {
        ReadOnlyObservableCollection<ValidationError> errors =
            value as ReadOnlyObservableCollection<ValidationError>;
 
        if (errors == null)
        {
            return string.Empty;
        }
 
        return string.Join("\n", (from e in errors 
                                  select e.ErrorContent as string).ToArray());
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, 
        CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
 
 

Create an ErrorTemplate for the TextBox

Next thing is to create an error template for the text box.

 
<ControlTemplate x:Key="TextBoxErrorTemplate" TargetType="Control">
    <Grid ClipToBounds="False" >
        <Image HorizontalAlignment="Right" VerticalAlignment="Top" 
               Width="16" Height="16" Margin="0,-8,-8,0" 
               Source="{StaticResource ErrorImage}" 
               ToolTip="{Binding ElementName=adornedElement, 
                         Path=AdornedElement.(Validation.Errors), 
                         Converter={k:ValidationErrorsToStringConverter}}"/>
        <Border BorderBrush="Red" BorderThickness="1" Margin="-1">
            <AdornedElementPlaceholder Name="adornedElement" />
        </Border>
    </Grid>
</ControlTemplate>
 

The ValidationRule and the ErrorTemplate in Action

Finally we can add the validation rule to our binding expression that binds the Text property of a textbox to a EMail property of our business object.

 
<TextBox x:Name="txtEMail" Template={StaticResource TextBoxErrorTemplate}>
    <TextBox.Text>
        <Binding Path="EMail" UpdateSourceTrigger="PropertyChanged" >
            <Binding.ValidationRules>
                <local:RegexValidationRule Pattern="{StaticResource emailRegex}"/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>
 
 

How to manually force a Validation

If you want to force a data validation you can manually call UpdateSource() on the binding expression. A useful scenario could be to validate on LostFocus() even when the value is empty or to initially mark all required fields. In this case you cann call ForceValidation() in the Loaded event of the window. That is the time, when the databinding is established.

The following code shows how to get the binding expression from a property of a control.

 
private void ForceValidation()
{
  txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();    
}
 
 




Last modified: 2009-07-06 13:53:43
Copyright (c) by Christian Moser, 2011.

 Comments on this article

Show all comments
Ceri
Commented on 15.January 2010
For those of you who can't get get this example to work...try this link http://www.switchonthecode.com/tutorials/wpf-tutorial-binding-validation-rules. It will help fill in the missing bits.
Ceri
Commented on 15.January 2010
For those of you who can't get get this example to work...try this link http://www.switchonthecode.com/tutorials/wpf-tutorial-binding-validation-rules. It will help fill in the missing bits.
Sandy
Commented on 27.January 2010
Thanks Ceri, that was a better example and with source code and solution file to understand
jd
Commented on 8.February 2010
is the tooltip supposed to open when there is an error. When I do this, the tooltip has the message but only opens when I hover the mouse over.
jd
Commented on 8.February 2010
is the tooltip supposed to open when there is an error. When I do this, the tooltip has the message but only opens when I hover the mouse over.
jd
Commented on 8.February 2010
is the tooltip supposed to open when there is an error. When I do this, the tooltip has the message but only opens when I hover the mouse over.
Mike
Commented on 26.February 2010
This is a good article but I agree with others who requested a project download... when you're new to WPF it's not obvious sometimes what goes where or what references are needed etc. Remember, not everyone's a guru!!
kap
Commented on 17.March 2010
Plz post the complete Window1.xaml content... Important things are missing.
Error
Commented on 17.March 2010
Error:
This is wrong: Template={StaticResource TextBoxErrorTemplate}>

It must be: <TextBox x:Name="txtEMail" Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate>

Now it works!
Klara
Commented on 29.March 2010
Is it possible to use DataAnnotations with xaml and if so, any chance of a short example code?
Christian...
Commented on 18.May 2010
Hello, Everyone thanks for your comment, i m looking forward to publish my new site which is on WCF it might help you please go through www.wcf.ru
Alex
Commented on 18.May 2010
OK?!?!?!, I did Hello World, worked fine, but am I missing step in between to be able to achieved this puzzle, or I am going too quick. What goes where?!!??!?
Nice web site, but you should add steps to know what to try first, second,...
GaaTy
Commented on 2.July 2010
Excellent Article. Got it working easily. Helped me alot!
WPFPupil
Commented on 20.September 2010
How will you stop user from going to next page till user clears all validations on this page? Just like it used to happen in aspx pages. As soon as user will click submit button, all client side validators will validate and of any of them fails then user remains on the same page.

Please help...
Vipin M
Commented on 14.October 2010
I have a little problem in this validation process. I don't wan't to show user the validation errors (red border on controls) before they try to save the form. Is there any way I can disable this validation errors on controls till user tries to submit the form?
Gorked
Commented on 18.October 2010
Using StringBuilder() is more memory-efficient than using Strings.
sudheer...
Commented on 28.October 2010
good , but why there is no simpler way as in winforms for a common thing like validation
luke
Commented on 4.November 2010
I can't get the image to show, but at lease here's the syntax for the image.... <BitmapImage x:Key="ErrorImage" UriSource="Resources\Key.png" />
venky
Commented on 22.December 2010
its nice
Raja
Commented on 13.January 2011
Thanks a lot for this series!!
Louis Nardozi
Commented on 19.January 2011
Except that the error will not necessarily be correct if there's more than one error. Where you have your (Validation.Errors)[0].ErrorContent, you can add ,BindsDirectlyToSource="True" and it will always show the first error and won't "lie to you" about errors that have already been corrected.

Alternatively, you could replace your ValidationToolTipTemplate with the following:

<ControlTemplate x:Key="ValidationToolTipTemplate" >
<Grid x:Name="root" Margin="5,0" RenderTransformOrigin="0,0" Opacity="0" >
<Grid.RenderTransform>
<TranslateTransform x:Name="xform" X="-25"/>
</Grid.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="OpenStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
<VisualTransition To="Open" GeneratedDuration="0:0:0.2">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="xform" Storyboard.TargetProperty="X" To="0" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<BackEase Amplitude=".3" EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Storyboard.TargetName="root" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Closed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="root" Storyboard.TargetProperty="Opacity" To="0" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Open">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="xform" Storyboard.TargetProperty="X" To="0" Duration="0"/>
<DoubleAnimation Storyboard.TargetName="root" Storyboard.TargetProperty="Opacity" To="1" Duration="0"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<Border Margin="4,4,-4,-4" Background="#052A2E31" CornerRadius="5"/>
<Border Margin="3,3,-3,-3" Background="#152A2E31" CornerRadius="4"/>
<Border Margin="2,2,-2,-2" Background="#252A2E31" CornerRadius="3"/>
<Border Margin="1,1,-1,-1" Background="#352A2E31" CornerRadius="2"/>

<Border Background="#FFDC000C" CornerRadius="2"/>
<Border CornerRadius="2">
<ListBox MaxHeight="450" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
UseLayoutRounding="True"
Foreground="White" Background="Transparent" Margin="8,4,8,4" ItemsSource="{Binding (Validation.Errors),BindsDirectlyToSource=True}" >

<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock IsHitTestVisible="False" MaxWidth="240" Text="{Binding ErrorContent}" TextWrapping="Wrap"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

</Border>
</Grid>
</ControlTemplate>
Priya
Commented on 8.August 2011
To learn wpf this was a very helpful site for me.
Thanks a lot
Beauty
Commented on 13.September 2011
Thanks for your nice tutorial.
The disadvantage is that you don't tell the needed namespaces. So I propose that you insert the list to the begin of the tutorial.

Here are all needed namespaces:
---------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using System.Windows.Data;
using System.Collections.ObjectModel;
using System.Windows.Markup;
---------------------
Beauty
Commented on 13.September 2011
Hidden note:
You have a mistype in the last parameter of this line:
---------------------
public override ValidationResult Validate(object value, CultureInfo ultureInfo)
---------------------

It's &quot;cultureInfo&quot; with &quot;C&quot; instead of &quot;ultureInfo&quot;.
Beauty
Commented on 14.September 2011
Problem:
I tried to define &lt;sys:String x:Key=&quot;emailRegex&quot;&gt;
but got the error that the TYPE &quot;sys:String&quot; NOT FOUND.
After a while I found the solution:
---&gt; ADD THIS to the &lt;Window&gt; tag:
xmlns:sys=&quot;clr-namespace:System;assembly=mscorlib&quot;

Name
E-Mail (optional)
Comment