Silverlight之Validate

其实关于验证的博文已经出现了很多,个人推荐的还是jv9的关于验证的文章.http://kb.cnblogs.com/page/74390/

当然本文是有部分和jv9文章的内容类似,不过在其基础上又多出了更多的验证方式和其他常用的技巧和功能。

首先分类下验证的方式:

一、异常方式,即在属性的Setter中throw出异常,则只要在XAML中配置相应属性即可显示

二、使用BindingValidationError事件进行验证

三、使用IDataErrorInfo(同步验证)和INotifyDataErrorInfo(异步和同步验证,异步的验证IDataErrorInfo和DataAnnotations无法实现)

四、提交验证 

五、使用DataAnnotations进行验证(包含自定义验证CustomerValidate)

 

异常方式:

   例子如下:

当属性UnitCost的值小于0时候抛出异常.

private double unitCost;
public double UnitCost
{
get { return unitCost; }
set
{
if (value < 0) throw new ArgumentException("Can't be less than 0.");
unitCost = value;
}
}


Xaml使用方式:

<TextBox Margin="5" Grid.Row="2" Grid.Column="1" x:Name="txtUnitCost"
Text
="{Binding UnitCost, Mode=TwoWay, ValidatesOnExceptions=True}"></TextBox>

除了指定Binding绑定属性之外,还设置了ValidatesOnExceptions属性为true,表示发生异常时候进行验证,正好是这种异常方式的使用方法。

效果如下:


当控件丢失光标之后,则会触发属性的PropertyChanged事件,通过验证确定是否抛出异常(当前示例是有异常的,因为数值小于了0),如果有异常则会显示红色边框,并且

在右上角有错误的图标,当点击错误图标或者选中控件都会在控件的右方显示错误信息(异常信息)。

 

 

使用BindingValidationError验证事件:

就是通过给Element或者其父容器添加BindingValidationError事件来进行数据的验证,看下例子:

<Grid Name="gridProductDetails"
BindingValidationError
="Grid_BindingValidationError">
<TextBox Margin="5" Grid.Row="2" Grid.Column="1" x:Name="txtUnitCost"
Text
="{Binding UnitCost, Mode=TwoWay, ValidatesOnExceptions=True,
NotifyOnValidationError=True}
"></TextBox>

可以看到TextBox还设置了NotifyOnValidationError,表示出现了错误则进行验证,下面的方式就是在Grid的Error事件中进行验证。

private void Grid_BindingValidationError(object sender, ValidationErrorEventArgs e)
{

lblInfo.Text = e.Error.Exception.Message;
lblInfo.Text += "\nThe stored value is still: " +
((Product)gridProductDetails.DataContext).UnitCost.ToString();

txtUnitCost.Focus();
}

可以看到在BindingValidationError事件中对数据进行了处理和验证,当出现异常,则会进入该时间中然后进行自定义的处理,在最后调用了Focus方法,使控件选中光标,显示错误信息. 这里有个需要注意的地方,其实当使用各种验证出现验证失败后(发生了异常或者使用 INotifyDataErrorInfo)则会进入到该事件,可以在该事件中进行对发生错误后进行自定义操作,当然是可以结合Validation来完成.


使用Validation验证类:

Validation.GetHasErrors(Element)方法得到当前Element是否有Error,返回值为false或者true

Validation.GetErrors()得到当前页面的错误集合。(在提交验证方式中很有用)



--小贴士

1.使对象类型实现IDataErrorInfo或INotifyDataErrorInfo接口。

2.如果实现于IDataErrorInfo接口,则设置ValidatesOnDataErrors属性为true;如果实现于INotifyDataErrorInfo接口,则设置ValidatesOnNotifyDataErrors为true。

3.设置属性NotifyOnValidationError为true,则会接收BindingValidationFailed的错误信息。

4.ValidatesOnExceptions属性设置为true,则是该文的第一种验证的方式。

 


使用INotifyDataErrorInfo验证首先需要实现该接口,同时需要三个元素,ErrorsChanged事件(当增加Error或者移除Error都会触发此事件),HasErrors属性返回true或者false标识是否有错误,GetErrors()方法返回所有的错误信息集合。

为了方便记录下每个属性对应的错误信息,我们需要一个集合,集合用来存放属性和错误信息的键对值,当有错误发生或者移除都需要来操作这个集合。

private Dictionary<string, List<string>> errors =new Dictionary<string, List<string>>();

接下来,看下一个示例类的完整代码:

在类中给Age属性,进行了错误的验证,如果值超出了0--100则向errors集合添加错误信息,在最后还是调用了我们的OnPropertyChanged事件,进行通知。

 public class User:INotifyPropertyChanged,INotifyDataErrorInfo
{
private string name;

public string Name
{
get { return name; }
set { name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}

private int age;
public int Age
{

get { return age; }
set
{
bool validate = true;
if (value < 0 || value > 100)
{
validate = false;
}
if (!validate)
{
//调用SetError方法进行添加错误信息
List<string> errors = new List<string>();
errors.Add("Age必须在0到100之内");
SetErrors("Age", errors);
}
else
{
//清楚错误信息
ClearErrors("Age");
}
//通知改变
OnPropertyChanged(new PropertyChangedEventArgs("Age"));
}
}

public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged!=null)
{
PropertyChanged(this,e);
}

}

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private Dictionary<string, List<string>> errors=new Dictionary<string,List<string>> ();

/// <summary>
/// 添加错误信息
/// </summary>
/// <param name="propertyName">属性名称</param>
/// <param name="propertyErrors">错误信息集合</param>
private void SetErrors(string propertyName,List<string> propertyErrors)
{
//如果存在此错误信息,则先移除
errors.Remove(propertyName);
//将错误键对值添加到集合中去
errors.Add(propertyName,propertyErrors);
//如果事件不为空,则调用错误改变事件
if (ErrorsChanged != null)
ErrorsChanged(this,new DataErrorsChangedEventArgs (propertyName));
}

/// <summary>
/// 移除错误信息
/// </summary>
/// <param name="propertyName">属性名称</param>
private void ClearErrors(string propertyName)
{
//将错误从集合中移除
errors.Remove(propertyName);
//如果事件不为空,则调用错误改变事件
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}

/// <summary>
/// 根据属性名称得到错误信息
/// </summary>
/// <param name="propertyName">属性名称</param>
/// <returns></returns>
public System.Collections.IEnumerable GetErrors(string propertyName)
{
//如果属性值为空,则返回所有的错误信息
if (string.IsNullOrEmpty(propertyName))
{
return errors.Values;
}
else
{
//如果当前的错误集合中包含此属性,则返回错误信息
if (errors.ContainsKey(propertyName))
{
return errors[propertyName];
}
else
{
return null;
}
}
}

/// <summary>
/// 是否存在错误信息
/// </summary>
public bool HasErrors
{
get { return (errors.Count > 0); }
}
}


看下使用方法:

  <TextBox x:Name="txtAge" Grid.Column="1" Text="{Binding Age,Mode=TwoWay, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}"></TextBox>

 

private void LayoutRoot_BindingValidationError(object sender, ValidationErrorEventArgs e)
{
if (e.Error.Exception != null)
{

}
else
{
txtAge.Focus();
}
}

当发生了错误,直接让txtAge获得焦点(当然具体是需要使用Validation的方法来判断当前产生错误的控件,然后确定该控件要被选中光标).
在这里设置了Binding的ValidatesOnNotifyDataErrors属性为true,表示如果使用了NotifyDataErrors则进行验证,第二个NotifyOnValidationError设置为true当发生了错误则会进入BindingValidationError事件。

效果如下:

输入Age为123超出了 100范围,当离开文本框则会显示红色的错误标识

 

 

 

因为到目前为止,验证就是通过接口实现,添加特性,或者异常的抛出,所以可以归类为两类,异常和非异常,在BindingValidationError(元素的父容器的该事件)事件中

进行判断,如果出现了错误,则直接使控件选中光标,当然具体还得使用Validation.GetHasErrors(Element)和ValidationGetErrors来进行判断指定Element是否存在错误。


使用 DataAnnotations进行验证:

对于DataAnnotation的翻译,可以理解为“数据元素注释”验证法。该验证机制,使用了System.ComponentModel.DataAnnotations命名空间中的属性类,通过对DataMember数据成员设置Metadata元数据属性,对其验证值进行判断是否符合当前属性条件,以达到Validation的效果

看个例子:

        [Required(ErrorMessage="必须输入ProdctName")]
[Display(Name = "Product Name", Description = "This is the retail product name.")]
public string ProductName
{
get { return productName; }
set {
ValidationContext context = new ValidationContext(this, null, null);
context.MemberName = "ProductName";
Validator.ValidateProperty(value, context);
productName = value;
OnPropertyChanged(new PropertyChangedEventArgs("ProductName"));
}
}

该例子中的Required特性,就是其中的验证之一,表示为空验证,通过指定ErrorMessage来设置显示异常的错误。

可能有同学记得在之前的例子中的验证方式是在setter中进行throw一个异常,而DataAnotation则不需要的,只需要在setter中激活验证即可,使用ValidationContext和

Validator即可,上边的例子很明显,设置ValidationContext.MemberName为验证的属性名称,然后调用ValidatorValidateProperty即可。

在这里需要提到一点,虽然没有使用Throw的方式,但是当验证失败之后还是会进入到调试模式,所以可以设置当发生了异常不打断操作,如下:

这种方式是很烦人的,不过可以取消对DataAnnotations的异常中止.

解决方案看该链接http://kb.cnblogs.com/page/73918/

 


看下在前台的使用方法:

  <TextBox x:Name="txtName" Grid.Column="1" Margin="0 5" Text="{Binding ProductName,Mode=TwoWay,  ValidatesOnExceptions=True, NotifyOnValidationError=True}"></TextBox>

可以看到没什么特殊的处理,只是ValidatesOnExceptions=True, NotifyOnValidationError=True这样就可以显示错误信息了。

其实上述的做法和之前的效果是一摸一样的,下面来看看DataAnnotations的其他验证(上述是非空验证):

属性名称  描述 
Required 标识该属性为必需参数,不能为空 
StringLength 标识该字符串有长度限制,可以限制最小或最大长度 
Range 标识该属性值范围,通常被用在数值型和日期型 
RegularExpression 标识该属性将根据提供的正则表达式进行对比验证 
CustomValidation 标识该属性将按照用户提供的自定义验证方法,进行数值验证 



StringLength:

这里切忌,这个仅仅是字符串的长度.

   private string password;
[StringLength(10,ErrorMessage="最大长度为10")]
public string Password
{
get { return password; }
set {
ValidationContext context = new ValidationContext(null,null,null);
context.MemberName = "Password";
Validator.ValidateProperty(value,context);
password = value;
 OnPropertyChanged(new PropertyChangedEventArgs("Password"));
}
}

可以看到,仅仅是在添加了StringLength特性就搞定饿了,其他使用方法和Required一样。

 

Range:

Range,通常是表示数字类型的一个范围之内,但是也可以去比较其他的类型(该类型实现了IComparable接口)

  private int age;
[Range(0, 100, ErrorMessage = "年龄需要在0-100岁之间")]
public int Age
{
get { return age; }
set
{
ValidationContext context = new ValidationContext(null,null,null);
context.MemberName = "Age";
Validator.ValidateProperty(value,context);
age = value;
               OnPropertyChanged(new PropertyChangedEventArgs("Age"));
}
}

 看下比较时间类型:

[Range(typeof(DateTime), "1/1/2005", "1/1/2010"]
public DateTime ExpiryDate
{ ... }




RegularExpression:

使用正则表达式验证邮箱.

private string email;
[RegularExpression(@"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)
+[a-zA-Z]{2,9})$
", ErrorMessage = "Email格式有误!")]
public string Email
{
get { return email; }
set {

ValidationContext context = new ValidationContext(null,null,null);
context.MemberName = "Email";
Validator.ValidateProperty(value,context);
email = value;
OnPropertyChanged(new PropertyChangedEventArgs("Email"));
}
}


具体正则的各个符号含义,请参见博客http://www.cnblogs.com/ListenFly/archive/2012/01/03/2310921.html
CustomValidation自定义验证:

  public class CustomizeValidation:ValidationAttribute
{
/// <summary>
/// 重写IsValid方法
/// </summary>
/// <param name="value">验证的值</param>
/// <param name="validationContext">验证上下文对象</param>
/// <returns></returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string checkName = value.ToString();
return checkName == "Fly" ? ValidationResult.Success : new ValidationResult("请输入指定的用户名!");
}
}

自定义一个类CustomizeValidation,实现ValidationAttribute类,重写IsValid方法,在方法中进行逻辑的判断,返回值为ValidationResult类型。

在类中的使用方法:

   private string name;
[CustomizeValidation]
public string Name
{
get { return name; }
set
{
ValidationContext context = new ValidationContext(null,null,null);
context.MemberName = "Name";
Validator.ValidateProperty(value,context);
name = value;
}
}

还是直接指定为其特性就好了。

除了上述的自定义方法之外,还有另外的更为通用的验证方法:

直接使用CustomValidation特性进行验证:

public class ProductCustomerValidate
{
public static ValidationResult ValidateUnitCost(double value, ValidationContext context)
{
// 得到属性的值
string valueString = value.ToString();
string cents = "";
int decimalPosition = valueString.IndexOf(".");
if (decimalPosition != -1)
{
cents = valueString.Substring(decimalPosition);
}
// 仅仅当值的小数部分为条件适合验证通过
if ((cents != ".75") && (cents != ".99") && (cents != ".95"))
{
return new ValidationResult(
"Retail prices must end with .75, .95, or .99 to be valid.");
}
else
{
return ValidationResult.Success;
}
}
}

新建一个类,在类中加入自定义的静态方法,方法大参数为两个,object(根据具体类型来定也可以) 和一个ValidationContext,方法的返回值为ValidationResult类型,

如果验证失败则返回错误信息的ValidateionResult,否则返回ValidationResult.Success.

在类中的使用方法如下:

[CustomValidation(typeof(ProductValidation), "ValidateUnitCost")]
public double UnitCost
{ ... }

CustomerValidation的第一个参数就是自定义实体类的类型,第二个就是要使用的方法.

 

上述的自定义都是进行验证字段,当然也是可以自定义验证实体类的,刚才提到了参数可以是obejct。下面来看下验证整个实体类:

同样是新建一个类,添加一个静态方法:

public static ValidationResult ValidateProduct(Product product,
ValidationContext context)
{
if (product.ModelName == product.ModelNumber)
{
return new ValidationResult(
"You can't use the same model number as the model name.");
}
else
{
return ValidationResult.Success;
}
}

可以看到参数一为Product实体类型,参数二不变,看下使用方法:

刚才是在属性上边添加特性,现在在类上边添加特性:

[CustomValidation(typeof(ProductValidation), "ValidateProduct")]
public class Product : INotifyPropertyChanged
{ ... }

同样是参数一为typeof,参数二为静态方法.
当然不管是使用属性的验证还是类的验证都需要手动去调用Validator.ValidateProperty/Validator.ValidateObject

 

提交验证数据:

上边的几种验证方式都是在当光标离开了控制之后开始进行验证,可有时候可能希望在用户点击按钮之后才进行数据的验证,这个当然是可以自己控制的。

UpdateSourceTrigger属性是Validation数据验证的重要属性之一,该属性主要表示数据源触发更新的执行时间.

 

通过MSDN,我们可以了解到UpdateSourceTrigger属性有两个值。

 

  1. Default,该值返回目标依赖属性的默认UpdateSourceTrigger值,多数控件返回的默认值为PropertyChanged,而Text属性的默认值为LostFocus。

 

  2. Explicit,如果设置UpdateSourceTrigger属性设置为显式方式,则需要开发人员手工调用UpdateSource方法,才能对数据源更新事件进行触发,否则不做任何操作。

 

  在默认情况下,UpdateSourceTrigger属性为Default。这也就是尽管在前几篇的实例中,在Binding中,我们没有设置任何PropertyChanged信息,仍旧能够触发验证。当数据绑定被修改后,绑定会自动触发UpdateSourceTrigger属性,通过该属性,对绑定数据源的数据成员进行验证,如果有异常,则返回False,反之为True。

 

 

<StackPanel x:Name="LayoutRoot" Background="White"  BindingValidationError="LayoutRoot_BindingValidationError">
<StackPanel.Resources>
<binding:PriceConverter x:Name="myPriceConvert"></binding:PriceConverter>
</StackPanel.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<sdk:Label Content="Name:" Grid.Column="0"/>
<TextBox x:Name="txtName" Grid.Column="1" Margin="0 5" Text="{Binding Name,Mode=TwoWay, ValidatesOnExceptions=True, UpdateSourceTrigger=Explicit}"></TextBox>
</Grid>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<sdk:Label Content="Age:" Grid.Column="0"/>
<TextBox x:Name="txtAge" Margin="0 5" Grid.Column="1" Text="{Binding Age,Mode=TwoWay, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True, StringFormat='C', UpdateSourceTrigger=Explicit}" ></TextBox>
</Grid>
<Grid x:Name="gridProduct">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<sdk:Label Content="Price:" Grid.Column="0"/>
<TextBox x:Name="txtPrice" Grid.Column="1" Text="{Binding Price,Mode=TwoWay, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True, Converter={StaticResource myPriceConvert}}" ></TextBox>
<TextBox Grid.Column="1" Text="{Binding Price,Mode=TwoWay, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True, Converter={StaticResource myPriceConvert}, ConverterParameter=HighLight, UpdateSourceTrigger=Explicit}" ></TextBox>
</Grid>
<Button x:Name="btnSubmit" Click="btnSubmit_Click" Content="提交" HorizontalAlignment="Left" Width="100" />
</StackPanel>

 

 

 

 

至此Silverlight关于验证要告一段落了,如果大家有更好的建议和意见多多交流。




posted @ 2012-01-03 15:03  wangyafei_it  阅读(831)  评论(0编辑  收藏  举报