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关于验证要告一段落了,如果大家有更好的建议和意见多多交流。