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




  • 相关阅读:
    Java初学者:for循环介绍
    Java初学者:条件判断及其语句
    Java初学者:基本数据类型的强制类型转换
    eclipse+gradle+nodejs搭建web开发环境
    桑基图(sankey)
    tomcat性能优化
    数据库概览与选择
    在linux上装 postgresql 在 windows或 linux 连不上的问题的解决方法
    mosquitto的TLS功能测试,客户端使用paho.mqtt.golang(附JAVA版客户端实现)
    两步使用arm-linux-androideabi-addr2line定位JNI动态库中C代码错误位置
  • 原文地址:https://www.cnblogs.com/ListenFly/p/2293652.html
Copyright © 2020-2023  润新知