Second Life of a Hungarian SharePoint Geek

November 17, 2010

November 5, 2010

WPF validation without real data binding

Filed under: WPF — Tags: — Peter Holpar @ 00:50

Validation in WPF is designed for data binding so if there is no data to bind to, you should apply some tricks to utilize the built-in validation techniques.

For example, you can create a dummy data binding and apply validation for this binding. Assuming we would like to validate the content of a TextBox control, a possible candidate for the binding is the Tag property of the TextBox itself.

The following example illustrates how to do that defining the data binding in XAML. The RegExValidationRule and ValidationErrorsToStringConverter classes in this example are based on the data validation sample code found in WPF Tutorial.

The first two code snippets show these helper classes.

  1. using System;
  2. using System.Text.RegularExpressions;
  3. using System.Windows.Controls;
  4. using System.Globalization;
  5.  
  6. namespace ValidationSample
  7. {
  8.     /// <summary>
  9.     /// Validates a text against a regular expression
  10.     /// </summary>
  11.     public class RegExValidationRule : ValidationRule
  12.     {
  13.         private string pattern;
  14.         private Regex regex;
  15.  
  16.         public string Pattern
  17.         {
  18.             get { return pattern; }
  19.             set
  20.             {
  21.                 pattern = value;
  22.                 regex = new Regex(pattern, RegexOptions.IgnoreCase);
  23.             }
  24.         }
  25.  
  26.         public string ErrorMessage { get; set; }
  27.  
  28.         public RegExValidationRule()
  29.         {
  30.         }
  31.  
  32.         public override ValidationResult Validate(object value, CultureInfo cultureInfo)
  33.         {
  34.             if (value == null || !regex.Match(value.ToString()).Success)
  35.             {
  36.                 return new ValidationResult(false, ErrorMessage);
  37.             }
  38.             else
  39.             {
  40.                 return new ValidationResult(true, null);
  41.             }
  42.         }
  43.     }
  44.  
  45. }

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Windows.Data;
  5. using System.Windows.Markup;
  6. using System.Windows.Controls;
  7. using System.Collections.ObjectModel;
  8. using System.Globalization;
  9.  
  10. namespace ValidationSample
  11. {
  12.     [ValueConversion(typeof(ReadOnlyObservableCollection<ValidationError>), typeof(string))]
  13.     public class ValidationErrorsToStringConverter : MarkupExtension, IValueConverter
  14.     {
  15.         public override object ProvideValue(IServiceProvider serviceProvider)
  16.         {
  17.             return new ValidationErrorsToStringConverter();
  18.         }
  19.  
  20.         public object Convert(object value, Type targetType, object parameter,
  21.             CultureInfo culture)
  22.         {
  23.             ReadOnlyObservableCollection<ValidationError> errors =
  24.                 value as ReadOnlyObservableCollection<ValidationError>;
  25.  
  26.             if (errors == null)
  27.             {
  28.                 return string.Empty;
  29.             }
  30.  
  31.             return string.Join("\n", (from e in errors
  32.                                       select e.ErrorContent as string).ToArray());
  33.         }
  34.  
  35.         public object ConvertBack(object value, Type targetType, object parameter,
  36.             CultureInfo culture)
  37.         {
  38.             throw new NotImplementedException();
  39.         }
  40.     }
  41.  
  42. }

This is the XAML for the sample:

  1. <Window x:Class="ValidationSample.MainWindow"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
  4.         xmlns:local="clr-namespace:ValidationSample"
  5.         Title="MainWindow" Height="100" Width="200">
  6.     <Window.Resources>
  7.         <Style x:Key="validator" TargetType="{x:Type TextBox}">
  8.             <Setter Property="Validation.ErrorTemplate">
  9.                 <Setter.Value>
  10.                     <ControlTemplate>
  11.                         <Grid>
  12.                             <TextBlock Grid.Column="0" HorizontalAlignment="Right" Padding="0,2,5,0" Foreground="Red" FontSize="14" FontWeight="Bold"
  13.                                 ToolTip="{Binding ElementName=adornerPlaceholder,
  14.                                 Path=AdornedElement.(Validation.Errors),
  15.                                 Converter={local:ValidationErrorsToStringConverter}}">!</TextBlock>
  16.                             <AdornedElementPlaceholder Name="adornerPlaceholder" Grid.Column="0"/>
  17.                         </Grid>
  18.                     </ControlTemplate>
  19.                 </Setter.Value>
  20.             </Setter>
  21.         </Style>
  22.     </Window.Resources>
  23.     <StackPanel>
  24.         <TextBlock>E-mail address:</TextBlock>
  25.         <TextBox x:Name="mailAddress" Style="{StaticResource ResourceKey=validator}">
  26.             <Binding ElementName="mailAddress" Path="Tag" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged" >
  27.                 <Binding.ValidationRules>
  28.                     <local:RegExValidationRule
  29.                         Pattern="^[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]$"
  30.                         ErrorMessage="Not valid e-mail address"/>
  31.                 </Binding.ValidationRules>
  32.             </Binding>
  33.         </TextBox>
  34.         <Button Name="Button" Click="Button_Click">Has errors?</Button>
  35.     </StackPanel>
  36. </Window>

And the corresponding code-behind:

  1. using System;
  2. using System.Windows;
  3. using System.Windows.Controls;
  4.  
  5. namespace ValidationSample
  6. {
  7.     public partial class MainWindow : Window
  8.     {
  9.         public MainWindow()
  10.         {
  11.             InitializeComponent();
  12.         }
  13.  
  14.         private void Button_Click(object sender, RoutedEventArgs e)
  15.         {
  16.             MessageBox.Show(Validation.GetHasError(mailAddress).ToString());
  17.         }
  18.     }
  19. }

You can create the binding from code as well. In this case, your XAML looks like this:

  1. <Window x:Class="ValidationSample.MainWindow"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
  4.         xmlns:local="clr-namespace:ValidationSample"
  5.         Title="MainWindow" Height="100" Width="200">
  6.     <Window.Resources>
  7.         <Style x:Key="validator" TargetType="{x:Type TextBox}">
  8.             <Setter Property="Validation.ErrorTemplate">
  9.                 <Setter.Value>
  10.                     <ControlTemplate>
  11.                         <Grid>
  12.                             <TextBlock Grid.Column="0" HorizontalAlignment="Right" Padding="0,2,5,0" Foreground="Red" FontSize="14" FontWeight="Bold"
  13.                                 ToolTip="{Binding ElementName=adornerPlaceholder,
  14.                                 Path=AdornedElement.(Validation.Errors),
  15.                                 Converter={local:ValidationErrorsToStringConverter}}">!</TextBlock>
  16.                             <AdornedElementPlaceholder Name="adornerPlaceholder" Grid.Column="0"/>
  17.                         </Grid>
  18.                     </ControlTemplate>
  19.                 </Setter.Value>
  20.             </Setter>
  21.         </Style>
  22.     </Window.Resources>
  23.     <StackPanel>
  24.         <TextBlock>E-mail address:</TextBlock>
  25.         <TextBox x:Name="mailAddress" />
  26.         <Button Name="Button" Click="Button_Click">Has errors?</Button>
  27.     </StackPanel>
  28. </Window>

And its code-behind:

  1. using System;
  2. using System.Windows;
  3. using System.Windows.Controls;
  4. using System.Windows.Data;
  5.  
  6. namespace ValidationSample
  7. {
  8.     public partial class MainWindow : Window
  9.     {
  10.         public MainWindow()
  11.         {
  12.             InitializeComponent();
  13.  
  14.             Style validationStyle = (Style)FindResource("validator");
  15.             mailAddress.Style = validationStyle;
  16.  
  17.             RegExValidationRule regExValidRule = new RegExValidationRule
  18.             {
  19.                 Pattern = @"^[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]$",
  20.                 ErrorMessage = "Not valid e-mail address"
  21.             };
  22.  
  23.             Binding validationBinding = new Binding { Source = mailAddress, Path = new PropertyPath("Tag") };
  24.             validationBinding.NotifyOnValidationError = true;
  25.             validationBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
  26.             validationBinding.ValidationRules.Add(regExValidRule);
  27.             BindingOperations.SetBinding(mailAddress, TextBox.TextProperty, validationBinding);
  28.         }
  29.  
  30.         private void Button_Click(object sender, RoutedEventArgs e)
  31.         {
  32.             MessageBox.Show(Validation.GetHasError(mailAddress).ToString());
  33.         }
  34.     }
  35. }

Let’s see our sample in action.

The image below illustrates an invalid e-mail address. The red exclamation mark signs the validation error. The tool tip of the exclamation mark shows the reason for the error.

image

Clicking the Has errors? button returns True, as there is a validation error.

After correcting the e-maill address, the exclamation mark disappears and Has errors? button returns False.

image

Theme: Shocking Blue Green. Get a free blog at WordPress.com

Follow

Get every new post delivered to your Inbox.

Join 54 other followers