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.
First thing I need to do is place a regular expression pattern as string to the windows resources
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:
The converter is both, a value converter and a markup extension. This allows you to create and use it at the same time.
The following code shows how to get the binding expression from a property of a control.
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); } } }
<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 ofValidationErrors
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 theText
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 callUpdateSource()
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(); }
No comments:
Post a Comment