Silverlight Feeds - All your Silverlight feeds in one place.

Sponsors

Friday, June 05, 2009

Silverlight Validation in Detail

by jesseliberty via Jesse Liberty - Silverlight Geek on 6/5/2009 4:21:18 AM

 

In a previous post I mentioned that Silverlight 3 has enhanced support for data entry validation. In this first of two mini-tutorials on the topic,  I will take you through the process of implementing validation in some detail.

DataValidation1

The key to understanding Silverlight validation is the division of logic from UI. In this case, the logic is delegated to the business object that the input control is bound to, and the UI is owned by the the input control (and the associated controls for displaying error conditions.) 

In the case shown above, the text box into which the user is invited to type an ISBN is the input control. Not shown is a business object (also, in these cases called a data object) that holds the rules about what makes for a valid ISBN.

UIAndLogicInValidation

On the left of this diagram is the UI. It is presented to the user as a text box, and implemented using Xaml. On the right is the business logic. A business object is created, in this case as a class in C# that represents, most often, an object in the user’s domain of concern – here a book.

The business object (the book) knows what a valid ISBN is, the UI does not.  The Business object determines if the value given to it by the UI is valid. If not, it throws an exception, specifying what is wrong. In this case, it might throw one of three exceptions:

  • "Must be exactly 10 integers long"
  • "Must be numbers or letter X"
  • "Checksum is invalid!"

 

The UI doesn’t know what the rules are, and the Business object doesn’t know how the UI will present the problem to the user (if at all!). And that is good.

Using Binding to Mediate the Validation of the UI By the Data Object

We are already asking the Binding Object to connect the User Control to the Business object, so it is the obvious choice to also pass along the value for validation and the objects response (if any).

Normally, when you bind a property to a TextBox you would provide the Mode and the Path, in this case, you add two more properties:

 

   1: Text="{Binding Mode=TwoWay, 
   2: NotifyOnValidationError=True,
   3: Path=ISBN10, 
   4: ValidatesOnExceptions=True}" 

The Business Object throws an exception if the data is not valid, and puts the reason in the Exception’s message. The Binding Object turns the exception into a message to the control, and sets the controls visual state from Valid to either InvalidUnfocused or InvalidFocused. What the control does when it changes visual state from Valid to one of the invalid states is entirely up to the designer of the control or whomever templates that control.

[We’ll look at templating error states in the second part of this mini-tutorial in a few weeks.]

For this to work, the control must bind using two way binding and it must support the new ValidationState group (you can see the validation states group very readily by beginning the templating process of any of the input controls that support validation out of the box as shown here:

ValidationStates2

 

As of this writing, the controls that will support validation out of the box at RTW are

  • TextBox
  • PasswordBox
  • CheckBox
  • RadioButton
  • ListBox
  • ComboBox

With the exception of PasswordBox, all of these already work in the Beta version.

Data Validation Step By Step

You now have all the pieces, let’s put them together step by step. 

The designer sets up a binding between an input control (text box) and a data object (the Book object) ensuring that

  • The binding is 2 way
  • The Control has Visual state to support Validation states
  • The BindingFramework knows to turn exceptions into validation state (flags)

The user is prompted to enter an ISBN into the text box. The value entered is not evaluated until one of two events; either the user leaves the text box (causing the text box to fire its TextChanged method, or the user clicks the handy Validate Now! button which invokes UpdateSource on the textBox’s TextProperty without having to actually leave the textBox.

In either case,  the data is given to the Binding Framework which passes it to the Data Object for validation,

ValidationStage1

If the data is not valid, the DataObject throws an exception to the BindingFramework. The Framework turns the exception into an instruction to the input control to set its validation state to Invalid. This will kick off a storyboard, in our case turning the border red, and when the user clicks in the control bringing up the error message from the exception which is passed to the control in its error message.

Datavalidation2

[This drawing based on an original image by Karen Corby]

Steps To Creating Data Validation

There are times when I find that I follow every word of a presentation but I still have no idea how to actually do it.  So here’s how.

1. Create a  new project

2. Createi a business object that is going to own data validation

3. Create two way data binding between one or more input controls from the supported list above to one or more properties on your data object

4. For each control you want to validate, create a setter on the bound property that tests for the conditions you want to validate, and throws an exception if the data is not valid. In the control, be sure to set the two flags (NotifyOnValidationError=True, ValidatesOnExceptions=True).

That’s it!  Honest.

Here is the complete MinPage.xaml for this example followed by the complete code behind…

   1: <UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   2:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   3:              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   4:              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   5:              x:Class="Validation_OutOfBox.MainPage"
   6:              Width="600"
   7:              Height="250"
   8:              mc:Ignorable="d">
   9:     <Grid x:Name="LayoutRoot"
  10:           Background="White">
  11:         <Grid.RowDefinitions>
  12:             <RowDefinition Height="1*" />
  13:             <RowDefinition Height="1*" />
  14:             <RowDefinition Height="1*" />
  15:             <RowDefinition Height="1*" />
  16:         </Grid.RowDefinitions>
  17:         <Grid.ColumnDefinitions>
  18:             <ColumnDefinition Width="1*" />
  19:             <ColumnDefinition Width="1*" />
  20:         </Grid.ColumnDefinitions>
  21:         <TextBlock Text="{Binding Path=Title}"
  22:                    HorizontalAlignment="Center"
  23:                    VerticalAlignment="Bottom"
  24:                    Grid.Row="0"
  25:                    Grid.Column="0"
  26:                    Grid.ColumnSpan="2"
  27:                    FontFamily="Georgia"
  28:                    FontSize="24" />
  29:         <TextBlock x:Name="Prompt10"
  30:                    Text="Please enter the 10 digit ISBN"
  31:                    TextWrapping="Wrap"
  32:                    HorizontalAlignment="Right"
  33:                    VerticalAlignment="Bottom"
  34:                    FontFamily="Georgia"
  35:                    FontSize="18"
  36:                    Grid.Column="0"
  37:                    Grid.Row="1"
  38:                    Margin="5" />
  39:         <TextBox x:Name="TenDigits"
  40:                  FontFamily="Georgia"
  41:                  FontSize="18"
  42:                  HorizontalAlignment="Left"
  43:                  VerticalAlignment="Bottom"
  44:                  Margin="5"
  45:                  Width="150"
  46:                  Height="40"
  47:                  Grid.Column="1"
  48:                  Grid.Row="1"
  49:                  TextWrapping="Wrap"
  50:                  Text="{Binding Mode=TwoWay, 
  51:                  NotifyOnValidationError=True, 
  52:                  Path=ISBN10, 
  53:                  ValidatesOnExceptions=True}"   />
  54:         
  55:         <Button x:Name="FillIt"
  56:                 Content="Fill With valid ISBN 10"
  57:                 Width="120"
  58:                 Height="25"
  59:                 Grid.Column="1"
  60:                 Grid.Row="2"
  61:                 FontSize="12"
  62:                 HorizontalAlignment="Left"
  63:                 VerticalAlignment="Bottom"
  64:                 Margin="5"
  65:                 Background="#FF06F616" />
  66:         <Button x:Name="ValidateIt"
  67:                 Content="Validate Now!"
  68:                 Background="#FFFFFF00"
  69:                 Width="120"
  70:                 Height="25"
  71:                 Grid.Column="0"
  72:                 Grid.Row="2"
  73:                 FontSize="12"
  74:                 Margin="5"
  75:                 VerticalAlignment="Bottom"
  76:                 HorizontalAlignment="Right" />
  77:      </Grid>
  78: </UserControl>

Here’s the complete code behind,

   1: using System.Windows;
   2: using System.Windows.Controls;
   3: using System.Windows.Data;
   4:  
   5: namespace Validation_OutOfBox
   6: {
   7:   public partial class MainPage : UserControl
   8:   {
   9:     public MainPage()
  10:     {
  11:       InitializeComponent();
  12:       Book b = new Book();
  13:       b.Title = "Data Validation for Fun and Prophet";
  14:       LayoutRoot.DataContext = b;
  15:       FillIt.Click += new RoutedEventHandler( FillIt_Click );
  16:       ValidateIt.Click += new RoutedEventHandler( ValidateIt_Click );
  17:     }
  18:  
  19:     void ValidateIt_Click( object sender, RoutedEventArgs e )
  20:     {
  21:       BindingExpression bindingExpression = TenDigits.GetBindingExpression( TextBox.TextProperty );
  22:       bindingExpression.UpdateSource();
  23:     }
  24:  
  25:     void FillIt_Click( object sender, RoutedEventArgs e )
  26:     {
  27:       TenDigits.Text = "059652756X"; 
  28:     }
  29:   }
  30: }

 

Finally, here is the business class/ data object

 

   1: using System;
   2: using System.ComponentModel;
   3:  
   4: namespace Validation_OutOfBox
   5: {
   6:   public class Book : INotifyPropertyChanged
   7:   {
   8:     public event PropertyChangedEventHandler PropertyChanged;
   9:  
  10:     private string title;
  11:     public string Title
  12:     {
  13:       get
  14:       {
  15:         return title;
  16:       }
  17:       set
  18:       {
  19:         title = value;
  20:         NotifyPropertyChanged( "Title" );
  21:       }
  22:     }
  23:  
  24:     private string isbn10;
  25:     public string ISBN10
  26:     {
  27:       get
  28:       {
  29:         return isbn10;
  30:       }
  31:       set
  32:       {
  33:          
  34:         if ( value.Length != 10 )
  35:         {
  36:           throw new ArgumentException( "Must be exactly 10 integers long" );
  37:         }
  38:  
  39:         char[] isbnAsArray = value.ToCharArray();
  40:  
  41:         foreach ( char c in isbnAsArray )
  42:         {
  43:           if ( ( !Char.IsNumber( c ) ) && c.ToString().ToUpper() != "X" )
  44:           {
  45:             throw new ArgumentException( "Must be numbers or letter X" );
  46:           }
  47:         }
  48:  
  49:         int runningTotal = 0;
  50:         for ( int i = 0; i < 9; i++ )
  51:         {
  52:           int val =  ( Convert.ToInt32(isbnAsArrayIdea.ToString()) 
  53:                  * ( 10 - i ) );
  54:           runningTotal += val;
  55:         }
  56:         int mod = runningTotal % 11;
  57:         int checkSum = 11 - mod;
  58:         
  59:         int isbnCheckSum = -1;
  60:         if ( isbnAsArray[9].ToString().ToUpper() == "X" )
  61:           isbnCheckSum = 10;
  62:         else
  63:           isbnCheckSum = Convert.ToInt32(isbnAsArray[9].ToString());
  64:  
  65:         if ( isbnCheckSum != checkSum )
  66:         {
  67:           throw new ArgumentException( "Checksum is invalid!" );
  68:         }
  69:  
  70:         isbn10 = value;
  71:         NotifyPropertyChanged( "ISBN10" );
  72:       }
  73:     }
  74:  
  75:     private void NotifyPropertyChanged( String propertyName )
  76:     {
  77:       if ( PropertyChanged != null )
  78:       {
  79:         PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
  80:       }
  81:     }
  82:   }
  83: }

 

That’s it!

 

ecq100Example-Code Quality, GuaranteedThis code was compiled with Silverlight 3 (Beta) using Visual Studio 8 SP1 on Windows 7 (RC) and also on Silverlight 3 (Beta) using Visual Studio 10 (Beta) on Windows 7 (RC).   For more on this guarantee, please see this page.

email it!bookmark it!digg it!

Original Post: Silverlight Validation in Detail

Subscribe

New Feed

Product Spotlight

Recently Updated Sources

Legal Note

The content of the postings is owned by the respective author. Silverlight Feeds is not responsible for the contents of the postings. This site is automatically generated and cannot be reviewed for abusive content. If you find abusive content on Silverlight Feeds, please contact us. Designated trademarks and brands are the property of their respective owners. All rights reserved.

Advertise with us