Mike Taulty's Blog
Bits and Bytes from Microsoft UK
A quick look at Silverlight 3: Data Validation

Blogs

Mike Taulty's Blog

Elsewhere

In Silverlight 2, if I’ve got a UI like this one which is just 3 TextBoxes bound to 3 data properties;

<StackPanel
    x:Name="LayoutRoot"
    Background="White">
    <TextBox
        Margin="5"
        Grid.Column="1"
        VerticalAlignment="Center"
        Width="192"
        Text="{Binding FirstName,Mode=TwoWay}" />
    <TextBox
        Margin="5"
        Grid.Column="1"
        VerticalAlignment="Center"
        Grid.Row="1"
        Width="192"
        Text="{Binding LastName,Mode=TwoWay}" />
    <TextBox
        Margin="5"
        Grid.Column="1"           
        VerticalAlignment="Center"
        Width="192"
        Text="{Binding Account,Mode=TwoWay}"
        Grid.Row="2" />
</StackPanel>

and I’ve set the DataContext for the StackPanel so that it binds to an instance of my Person class;

  public class Person 
  {
    public Person(string firstName, string lastName, int account)
    {
      this.FirstName = firstName;
      this.LastName = lastName;
      this.Account = account;
    }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Account { get; set; }
  }

then property getters/setters will get called automatically but if I wanted to do any validation around the values that come from the UI then I’ve got to take a custom approach because any exceptions that are thrown from the setters above will be swallowed and lost.

In Silverlight 3, I can have my validation in my setters (e.g.);

 public class InvalidDataException : Exception
  {
    public InvalidDataException(string msg) : base(msg)
    {
    }
  }
  public class Person 
  {
    public Person(string firstName, string lastName, int account)
    {
      this.firstName = firstName;
      this.lastName = lastName;
      this.Account = account;
    }
    public string FirstName
    {
      get
      {
        return (firstName);
      }
      set
      {
        CheckFieldLength(value, 5, 10);
        firstName = value;
      }
    }
    public string LastName
    {
      get
      {
        return (lastName);
      }
      set
      {
        CheckFieldLength(value, 5, 10);
        lastName = value;
      }
    }
    void CheckFieldLength(string value, int min, int max)
    {
      if ((value.Length < min) || (value.Length > max))
      {
        throw new InvalidDataException("Bad length on that string");
      }
    }
    public int Account
    {
      get
      {
        return (account);
      }
      set
      {
        if ((value < 12340000) || (value > 12349999))
        {
          throw new InvalidDataException("Account number is invalid");
        }
        account = value;
      }
    }
    string firstName;
    string lastName;
    int account;
  }

and then if I update my UI so that my bindings have a new ValidatesOnExceptions property then all kinds of new and exciting stuff kicks into gear;

<StackPanel
    x:Name="LayoutRoot"
    Background="White">
    <TextBox
        Margin="5"
        Grid.Column="1"
        VerticalAlignment="Center"
        Width="192"
        Text="{Binding FirstName,Mode=TwoWay,ValidatesOnExceptions=True}" />
    <TextBox
        Margin="5"
        Grid.Column="1"
        VerticalAlignment="Center"
        Grid.Row="1"
        Width="192"
        Text="{Binding LastName,Mode=TwoWay,ValidatesOnExceptions=True}" />
    <TextBox
        Margin="5"
        Grid.Column="1"           
        VerticalAlignment="Center"
        Width="192"
        Text="{Binding Account,Mode=TwoWay,ValidatesOnExceptions=True}"
        Grid.Row="2" />
</StackPanel>

image

and so we’re getting automatic (and, naturally, customisable) error display UI on controls like TextBox here. There are new attached properties on a static class called Validation called ErrorsProperty and HasErrorProperty and helper methods Validation.GetErrors() and Validation.GetHasError() which can obtain the set of errors that have occurred in pushing a UI value through into its associated property ( note – there’s a new for “errors” rather than “error” here because it’s possible to have different properties of a UI control bound to more than one property on a data class ).

There’s also a new routed event defined which is BindingValidationError and it’s defined on FrameworkElement. As far as I can tell, this event only gets fired if we switch on another flag called NotifyOnValidationError as in;

<StackPanel
        x:Name="LayoutRoot"
        Background="White"
        BindingValidationError="OnValidationError">
        <TextBox
            Margin="5"
            Grid.Column="1"
            VerticalAlignment="Center"
            Width="192"
            Text="{Binding FirstName,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}" />
        <TextBox
            Margin="5"
            Grid.Column="1"
            VerticalAlignment="Center"
            Grid.Row="1"
            Width="192"
            Text="{Binding LastName,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}" />
        <TextBox
            Margin="5"
            Grid.Column="1"           
            VerticalAlignment="Center"
            Width="192"
            Text="{Binding Account,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}"
            Grid.Row="2" />
    </StackPanel>

and then I can add a handler;

    private void OnValidationError(object sender, ValidationErrorEventArgs e)
    {
      switch (e.Action)
      {
        case ValidationErrorEventAction.Added:
          Exception ex = e.Error.Exception;
          break;
        case ValidationErrorEventAction.Removed:
          break;
        default:
          break;
      }
    }

It’s interesting in that this handler gets called as validation problems are encountered and also as they are resolved as you can see with the ValidationErrorEventAction.Added and Removed enumerations.

At the time of writing, I’m not sure if there’s an automatic way to determine all validation errors that in play for the children of a particular containing piece of UI like a Panel. That is – I could use the event handler above to build a list of errors and then data-bind to it as in;

<UserControl
    x:Class="SilverlightApplication16.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SilverlightApplication16">
    <StackPanel
        x:Name="LayoutRoot"
        Background="White"
        BindingValidationError="OnValidationError">
        <TextBox
            Margin="5"
            Grid.Column="1"
            VerticalAlignment="Center"
            Width="192"
            Text="{Binding FirstName,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}" />
        <TextBox
            Margin="5"
            Grid.Column="1"
            VerticalAlignment="Center"
            Grid.Row="1"
            Width="192"
            Text="{Binding LastName,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}" />
        <TextBox
            Margin="5"
            Grid.Column="1"           
            VerticalAlignment="Center"
            Width="192"
            Text="{Binding Account,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}"
            Grid.Row="2" />
        <ListBox
            x:Name="lstErrors"
            ItemsSource="{Binding Errors}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock
                        Text="{Binding Exception.Message}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</UserControl>

with code (which should probably also set the Handled property on the ValidationErrorEventArgs);

  public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();
      this.Errors = new ObservableCollection<ValidationError>();
      this.Loaded += OnLoaded;
    }
    void OnLoaded(object sender, RoutedEventArgs e)
    {
      this.DataContext = new Person("Fred", "Smith", 12345555);
      lstErrors.DataContext = this;
    }
    private void OnValidationError(object sender, ValidationErrorEventArgs e)
    {
      switch (e.Action)
      {
        case ValidationErrorEventAction.Added:
          Errors.Add(e.Error);
          break;
        case ValidationErrorEventAction.Removed:
          Errors.Remove(e.Error);
          break;
        default:
          break;
      }
    }
    public ObservableCollection<ValidationError> Errors { get; set; }
  }

and that gives me a UI like;

image

with my ListBox at the bottom ( note – I’m not sure that adding/removing those ValidationErrors to that ObservableCollection up above is legit as I think I’m relying on the same object being passed to my event handler each time to represent the same error and that might not be the case ).

Alternatively, rather than building up a list as the errors happen, I could gather the errors from my individual controls when (e.g.) someone clicks a “Submit” button as in;

<UserControl
    x:Class="SilverlightApplication16.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SilverlightApplication16">
    <StackPanel
        x:Name="LayoutRoot"
        Background="White">
        <TextBox
            Margin="5"
            Grid.Column="1"
            VerticalAlignment="Center"
            Width="192"
            Text="{Binding FirstName,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}" />
        <TextBox
            Margin="5"
            Grid.Column="1"
            VerticalAlignment="Center"
            Grid.Row="1"
            Width="192"
            Text="{Binding LastName,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}" />
        <TextBox
            Margin="5"
            Grid.Column="1"           
            VerticalAlignment="Center"
            Width="192"
            Text="{Binding Account,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}"
            Grid.Row="2" />
        <StackPanel>
        <ListBox
            x:Name="lstErrors"
            ItemsSource="{Binding}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock
                        Text="{Binding Exception.Message}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
            <Button
                Content="Submit"
                Click="OnSubmit" />
        </StackPanel>
    </StackPanel>
</UserControl>

with code;

public partial class MainPage : UserControl
{
  public MainPage()
  {
    InitializeComponent();
    this.Loaded += OnLoaded;
  }
  void OnLoaded(object sender, RoutedEventArgs e)
  {
    this.DataContext = new Person("Fred", "Smith", 12345555);
    lstErrors.DataContext = this;
  }
  void OnSubmit(object sender, RoutedEventArgs args)
  {
    List<ValidationError> errors = new List<ValidationError>();

    foreach (UIElement ui in LayoutRoot.Children)
    {
      FrameworkElement fe = ui as FrameworkElement;

      if (fe != null)
      {
        foreach (ValidationError ve in Validation.GetErrors(fe))
        {
          errors.Add(ve);
        }
      }
    }
    lstErrors.DataContext = errors;
  }
}

and that builds up my error list ( in addition to the standard UI ) at the point where I click the Submit button;

image

I’ll update the post if I learn how to get a list of errors for all children from a containing Panel.

In terms of customising the UI for the error display – the UI is built into the Template of controls such as TextBox, CheckBox, RadioButton, ListBox, ComboBox and so on so you’d want to use Blend to alter the template and customise the error display.

You can download the code for this post from here.

This is one of a series of posts taking a quick look at Silverlight 3 – expect them to be a little “rough and ready” and that they’ll get expanded on as I’ve had more time to work with Silverlight 3.


Posted Wed, Mar 18 2009 11:40 AM by mtaulty
Filed under: ,

Comments

Mike Taulty's Blog wrote Silverlight 3: More Posts?
on Wed, Mar 18 2009 1:43 PM

Just a quick note – I didn’t want anyone to think that I’d given up on Silverlight 3 or that these posts;

DotNetShoutout wrote A quick look at Silverlight 3: Data Validation - Mike Taulty's Blog
on Wed, Mar 18 2009 3:24 PM

Thank you for submitting this cool story - Trackback from DotNetShoutout

どっとねっとふぁんBlog wrote Silverlight 3 の技術に関するサンプルコード
on Wed, Mar 18 2009 8:02 PM

Silverlight 3: More Posts? Silverlight 3 で追加される技術についてサンプルコードを記述した13のエントリがまとめられています。 この中で個人的に一番気になっているのは...

b a r s » Blog Archive » Mike Taulty's Blog : A quick look at Silverlight 3: Data Validation wrote b a r s &raquo; Blog Archive &raquo; Mike Taulty&#39;s Blog : A quick look at Silverlight 3: Data Validation
on Thu, Mar 19 2009 2:54 AM
A quick look at Silverlight 3 - by Mike Taulty | DavideZordan.net wrote A quick look at Silverlight 3 - by Mike Taulty | DavideZordan.net
on Thu, Mar 19 2009 5:12 AM
A quick look at Silverlight 3 – By Mike Taulty « vincenthome’s Tech Clips wrote A quick look at Silverlight 3 &ndash; By Mike Taulty &laquo; vincenthome&#8217;s Tech Clips
on Thu, Mar 19 2009 9:33 PM
代震军 wrote 快速浏览Silverlight3 Beta:数据检验
on Thu, Apr 2 2009 12:05 AM

在Silverlight3中对数据进行校验不再像Silverlight2中那样麻烦了,下面就简要演示一下。首先,我们创建一个Silverlight3应用,名为:

Silverlight 3 Validation and Dataform control | ThumNet wrote Silverlight 3 Validation and Dataform control | ThumNet
on Thu, May 28 2009 2:48 PM