译文:详谈Silverlight 3.0中的数据验证 (Silverlight Validation in Detail)

原文链接:http://silverlight.net/blogs/jesseliberty/archive/2009/06/05/silverlight-validation-in-detail.aspx

在先前的帖子里我提到了Silverlight 3增强了对数据输入验证的支持。在这次小型的专题辅导中,我会带着你详细地体验实施验证的整个过程。

理解Silverlight验证的关键是逻辑从用户界面的分离。在这种情况下,逻辑被委托给输入控件所绑定的业务对象,而用户界面是属于输入控件(以及相关的显示错误状况的控件。)

在上图中,那个让用户往里输入一个ISBN的文本框就是一个输入控件。没有被显示出来的是一个规定了什么才是有效地ISBN的业务对象(在这些情况下也叫数据对象)。 

在这个图的左边是用户界面。这是用XAML画出来的一个文本框。右边是业务逻辑。业务对象,在这个例子中,被创建成一个C#中的类,它往往代表着一个用户关注的物体——在这里是一本书。

业务对象(书)知道什么是有效的ISBN,而用户界面不知道。业务对象决定用户界面传给它的值是否有效。如果不是,它抛出一个异常,具体说明错误在哪。在这个例子中,它可能抛出三个异常:

  • 长度必须是10个整数
  • 必须是数字或字母X
  • 校验无效!

用户界面不知道这些规则是什么,而业务对象不知道用户界面如何将问题展现给用户。这样很好。


使用绑定来促成使用数据对象来对用户界面进行验证

我们已经要求绑定对象将用户控件连接到业务对象,因此很明显地,我们也要将值传递过去做验证以及对象的响应(如果有的话)。

通常,当你绑定到一个属性到文本框时,你需要提供模式和路径,在这个例子里,你添加两个属性:

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

 

如果数据是无效的,业务对象会抛出一个异常,并把原因放在异常信息里。绑定对象将异常转换成控件里的信息,并将控件的视觉状态设置成或者无效不聚焦(InvalidUnfocused)或者无效聚焦(InvalidFocused)。当控件的视觉状态从有效改变成无效时会做些什么完全由控件的设计者控制或模版的设计者来决定。

要做到这一点,控件必须使用双向绑定,并且它必须支持新的验证状态(ValidationState)组(通过开始给任何支持Out-Of-The-Box的输入控件做模版,你会非常容易地看到验证状态组,如下所示:

截至写这篇文章的时候,在RTW版本中能支持Out-Of-The-Box验证的控件有:

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

除了PasswordBox ,所有这些控件的验证功能在Beta版本的时候已经能够工作。

 

一步一步数据验证

现在,你拥有所有的要素,让我们把它们放在一起。

设计人员创建了一个输入控件和一个数据对象(Book)之间的绑定,并确保:

  • 绑定是双向的
  • 控件有可以支持验证状态的视觉状态
  • 绑定框架知道把异常转换成验证状态

用户被提示往文本框中输入一个ISBN。输入的值不会马上被评估,直到以下两件事件之一发生:或者用户焦点离开文本框(会引发文本框的TextChanged方法),或者用户点击旁边的“Validate Now!”按钮,这个按钮会调用UpdateSource在文本框的TextProperty上,而不必离开文本框。

在任何一种情况下,数据都会被传递给绑定框架(Binding Framework),然后由它再传递给数据对象做验证。

如果数据无效,DataObject会抛出一个异常给BindingFramework。该框架将异常转换成让输入控件将它的验证状态设成无效的命令。这将会启动一个storyboard,在我们的例子中是将边界变成红色,和当用户点击控件的时候将从传递给这个控件的异常里得到的错误信息展现出来。

 

创建数据验证的步骤

有些时候我发现虽然我看懂了报告里的每一个字,但我仍然不知道该如何真正地去做。因此,以下就是如何来做。

  1. 创建一个新项目
  2. 创建一个业务对象来做数据验证
  3. 在上面列出来支持数据验证的控件清单中的一个或多个控件与你的数据对象的一个或多个属性之间建立双向数据绑定
  4. 对每个你想要验证的控件,在绑定的属性上创建一个setter来测试你想要验证的条件,如果数据无效就抛出异常。在控件里,有两个Flag一定要设置(NotifyOnValidationError=True, ValidatesOnExceptions=True)

这样就大功告成了!真的。

以下是这个例子中用到的完整的MinPage.xaml以及完整的代码...

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"   
   xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"   
   xmlns:d
="http://schemas.microsoft.com/expression/blend/2008"   
   xmlns:mc
="http://schemas.openxmlformats.org/markup-compatibility/2006"   
   x:Class
="Validation_OutOfBox.MainPage"   
   Width
="600" Height="250"
   mc:Ignorable
="d">
   
<Grid x:Name="LayoutRoot" Background="White">
      
<Grid.RowDefinitions>
         
<RowDefinition Height="1*" />
         
<RowDefinition Height="1*" />
         
<RowDefinition Height="1*" />
         
<RowDefinition Height="1*" />
      
</Grid.RowDefinitions>
      
<Grid.ColumnDefinitions>
         
<ColumnDefinition Width="1*" />
         
<ColumnDefinition Width="1*" />
      
</Grid.ColumnDefinitions>
      
<TextBlock Text="{Binding Path=Title}" HorizontalAlignment="Center" VerticalAlignment="Bottom" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" FontFamily="Georgia" FontSize="24" />
      
<TextBlock x:Name="Prompt10" Text="Please enter the 10 digit ISBN" TextWrapping="Wrap" HorizontalAlignment="Right" VerticalAlignment="Bottom" FontFamily="Georgia" FontSize="18" Grid.Column="0" Grid.Row="1" Margin="5" />
      
<TextBox x:Name="TenDigits" FontFamily="Georgia" FontSize="18" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="5" Width="150" Height="40" Grid.Column="1" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Mode=TwoWay, NotifyOnValidationError=True, Path=ISBN10, ValidatesOnExceptions=True}" />
      
<Button x:Name="FillIt" Content="Fill With valid ISBN 10" Width="120" Height="25" Grid.Column="1" Grid.Row="2" FontSize="12" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="5" Background="#FF06F616" />
      
<Button x:Name="ValidateIt" Content="Validate Now!" Background="#FFFFFF00" Width="120" Height="25" Grid.Column="0" Grid.Row="2" FontSize="12" Margin="5" VerticalAlignment="Bottom" HorizontalAlignment="Right" />  
   
</Grid>
</UserControl>


以下是完整的后台代码, 

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Validation_OutOfBox
{
    
public partial class MainPage : UserControl
    {
        
public MainPage()
        {
            InitializeComponent();
            Book b 
= new Book();
            b.Title 
= "Data Validation for Fun and Prophet";
            LayoutRoot.DataContext 
= b;
            FillIt.Click 
+= new RoutedEventHandler( FillIt_Click );
            ValidateIt.Click 
+= new RoutedEventHandler( ValidateIt_Click );
        }
        
        
void ValidateIt_Click( object sender, RoutedEventArgs e )
        {
            BindingExpression bindingExpression 
= TenDigits.GetBindingExpression( TextBox.TextProperty );
            bindingExpression.UpdateSource();
        }
        
        
void FillIt_Click( object sender, RoutedEventArgs e )
        {
            TenDigits.Text 
= "059652756X";
        }
    }
}

 

最后,是业务类/数据对象

using System;
using System.ComponentModel;

namespace Validation_OutOfBox
{
    
public class Book : INotifyPropertyChanged
    {
        
public event PropertyChangedEventHandler PropertyChanged;
        
        
private string title;
        
public string Title
        {
            
get
            {
                
return title;
            }
            
set
            {
                title 
= value;
                NotifyPropertyChanged( 
"Title" );
            }
        }
        
        
private string isbn10; 
        
public string ISBN10
        {
            
get
            {
                
return isbn10;
            }
            
set
            {
                
if ( value.Length != 10 )
                {
                    
throw new ArgumentException( "Must be exactly 10 integers long" );
                }
                
char[] isbnAsArray = value.ToCharArray();
                
foreach ( char c in isbnAsArray )
                {
                    
if ( ( !Char.IsNumber( c ) ) && c.ToString().ToUpper() != "X" )
                    { 
                        
throw new ArgumentException( "Must be numbers or letter X" );
                    } 
                }
                
int runningTotal = 0;
                
for ( int i = 0; i < 9; i++ )
                {
                    
int val =  ( Convert.ToInt32(isbnAsArray.ToString()) * ( 10 - i ) );
                    runningTotal 
+= val;
                }
                
int mod = runningTotal % 11;
                
int checkSum = 11 - mod;
                
int isbnCheckSum = -1;
                
if ( isbnAsArray[9].ToString().ToUpper() == "X" )
                    isbnCheckSum 
= 10;
                
else
                    isbnCheckSum 
= Convert.ToInt32(isbnAsArray[9].ToString());
                
if ( isbnCheckSum != checkSum )
                {
                    
throw new ArgumentException( "Checksum is invalid!" );
                }
                isbn10 
= value;
                NotifyPropertyChanged( 
"ISBN10" );
            }
        }
        
private void NotifyPropertyChanged( String propertyName )
        {
            
if ( PropertyChanged != null )
            {
                PropertyChanged( 
thisnew PropertyChangedEventArgs( propertyName ) );
            }
        }
    }
}

 

就是这样。

posted on 2009-06-09 14:11  zzy_arthur  阅读(2510)  评论(0编辑  收藏  举报

导航