|
|
|
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 "cultureInfo" with "C" instead of "ultureInfo".
|
|
|
|
Beauty | |
|
Commented on 14.September 2011 |
Problem:
I tried to define <sys:String x:Key="emailRegex">
but got the error that the TYPE "sys:String" NOT FOUND.
After a while I found the solution:
---> ADD THIS to the <Window> tag:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
|
|
|
|
|