只要是有表单存在,那么就有可能有对数据的校验需求。如:判断是否为整数、判断电子邮件格式等等。ide
WPF采用一种全新的方式 - Binding,来实现前台显示与后台数据进行交互,固然数据校验方式也不同了。ui
本专题全面介绍一下WPF中4种Validate方法,帮助你了解如何在WPF中对binding的数据进行校验,并处理错误显示。this
正常状况下,只要是绑定过程当中出现异常或者在converter中出现异常,都会形成绑定失败。spa
可是WPF不会出现任何异常,只会显示一片空白(固然有些Converter中的异常会形成程序崩溃)。3d
这是由于默认状况下,Binding.ValidatesOnException为false,因此WPF忽视了这些绑定错误。code
可是若是咱们把Binding.ValidatesOnException为true,那么WPF会对错误作出如下反应:对象
咱们的Binding对象,维护着一个ValidationRule的集合,当设置ValidatesOnException为true时,blog
默认会添加一个ExceptionValidationRule到这个集合当中。继承
PS:对于绑定的校验只在Binding.Mode 为TwoWay和OneWayToSource才有效,索引
即当须要从target控件将值传到source属性时,很容易理解,当你的值不须要被别人使用时,就极可能校验也不必。
直接在Setter方法中,对value进行校验,若是不符合规则,那么就抛出异常。而后修改XAML不忽视异常。
public class PersonValidateInSetter : ObservableObject { private string name; private int age; public string Name { get { return this.name; } set { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("Name cannot be empty!"); } if (value.Length < 4) { throw new ArgumentException("Name must have more than 4 char!"); } this.name = value; this.OnPropertyChanged(() => this.Name); } } public int Age { get { return this.age; } set { if (value < 18) { throw new ArgumentException("You must be an adult!"); } this.age = value; this.OnPropertyChanged(() => this.Age); } } }
<Grid DataContext="{Binding PersonValidateInSetter}"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="Name:" /> <TextBox Grid.Column="1" Margin="1" Text="{Binding Name, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Grid.Row="1" Text="Age:" /> <TextBox Grid.Row="1" Grid.Column="1" Margin="1" Text="{Binding Age, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" /> </Grid>
当输入的值,在setter方法中校验时出现错误,就会出现一个红色的错误框。
关键代码:ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged。
PS:这种方式有一个BUG,首次加载时不会对默认数据进行检验。
使Model对象继承IDataErrorInfo接口,并实现一个索引进行校验。若是索引返回空表示没有错误,若是返回不为空,
表示有错误。另一个Erro属性,可是在WPF中没有被用到。
public class PersonDerivedFromIDataErrorInfo : ObservableObject, IDataErrorInfo { private string name; private int age; public string Name { get { return this.name; } set { this.name = value; this.OnPropertyChanged(() => this.Name); } } public int Age { get { return this.age; } set { this.age = value; this.OnPropertyChanged(() => this.Age); } } // never called by WPF public string Error { get { return null; } } public string this[string propertyName] { get { switch (propertyName) { case "Name": if (string.IsNullOrWhiteSpace(this.Name)) { return "Name cannot be empty!"; } if (this.Name.Length < 4) { return "Name must have more than 4 char!"; } break; case "Age": if (this.Age < 18) { return "You must be an adult!"; } break; } return null; } } }
<Grid DataContext="{Binding PersonDerivedFromIDataErrorInfo}"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="Name:" /> <TextBox Grid.Column="1" Margin="1" Text="{Binding Name, NotifyOnValidationError=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Grid.Row="1" Text="Age:" /> <TextBox Grid.Row="1" Grid.Column="1" Margin="1" Text="{Binding Age, NotifyOnValidationError=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
PS:这种方式,没有了第一种方法的BUG,可是相对很麻烦,既须要继承接口,又须要添加一个索引,若是遗留代码,那么这种方式就不太好。
一个数据对象或许不能包含一个应用要求的全部不一样验证规则,可是经过自定义验证规则就能够解决这个问题。
在须要的地方,添加咱们建立的规则,并进行检测。
经过继承ValidationRule抽象类,并实现Validate方法,并添加到绑定元素的Binding.ValidationRules中。
public class MinAgeValidation : ValidationRule { public int MinAge { get; set; } public override ValidationResult Validate(object value, CultureInfo cultureInfo) { ValidationResult result = null; if (value != null) { int age; if (int.TryParse(value.ToString(), out age)) { if (age < this.MinAge) { result = new ValidationResult(false, "Age must large than " + this.MinAge.ToString(CultureInfo.InvariantCulture)); } } else { result = new ValidationResult(false, "Age must be a number!"); } } else { result = new ValidationResult(false, "Age must not be null!"); } return new ValidationResult(true, null); } }
<Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="Name:" /> <TextBox Grid.Column="1" Margin="1" Text="{Binding Name}"> </TextBox> <TextBlock Grid.Row="1" Text="Age:" /> <TextBox Grid.Row="1" Grid.Column="1" Margin="1"> <TextBox.Text> <Binding Path="Age" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True"> <Binding.ValidationRules> <validations:MinAgeValidation MinAge="18" /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> </Grid>
这种方式,也会有第一种方法的BUG,暂时还不知道如何解决,可是这个可以灵活的实现校验,而且能传参数。
效果图:
在System.ComponentModel.DataAnnotaions命名空间中定义了不少特性,
它们能够被放置在属性前面,显示验证的具体须要。放置了这些特性以后,
属性中的Setter方法就可使用Validator静态类了,来用于验证数据。
public class PersonUseDataAnnotation : ObservableObject { private int age; private string name; [Range(18, 120, ErrorMessage = "Age must be a positive integer")] public int Age { get { return this.age; } set { this.ValidateProperty(value, "Age"); this.SetProperty(ref this.age, value, () => this.Age); } } [Required(ErrorMessage = "A name is required")] [StringLength(100, MinimumLength = 3, ErrorMessage = "Name must have at least 3 characters")] public string Name { get { return this.name; } set { this.ValidateProperty(value, "Name"); this.SetProperty(ref this.name, value, () => this.Name); } } protected void ValidateProperty<T>(T value, string propertyName) { Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = propertyName });
}
}
<Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="Name:" /> <TextBox Grid.Column="1" Margin="1" Text="{Binding Name, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Grid.Row="1" Text="Age:" /> <TextBox Grid.Row="1" Grid.Column="1" Margin="1" Text="{Binding Age, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" /> </Grid>
使用特性的方式,可以很自由的使用自定义的规则,并且在.Net4.5中新增了不少特性,能够很方便的对数据进行校验。
例如:EmailAddress, Phone, and Url等。
在上面的例子中,咱们能够看到当出现验证不正确时,绑定控件会被一圈红色错误线包裹住。
这种方式通常不可以正确的展现出,错误的缘由等信息,因此有可能须要本身的错误显示方式。
前面,咱们已经讲过了。当在检测过程当中,出现错误时,WPF会把错误信息封装为一个ValidationError对象,
并添加到Validation.Errors中,因此咱们能够取出错误详细信息,并显示出来。
下面就是一个简单的例子,每次都把错误信息以红色展现在空间上面。这里的AdornedElementPlaceholder至关于
控件的占位符,表示控件的真实位置。这个例子是在书上直接拿过来的,只能作基本展现用。
<ControlTemplate x:Key="ErrorTemplate"> <Border BorderBrush="Red" BorderThickness="2"> <Grid> <AdornedElementPlaceholder x:Name="_el" /> <TextBlock Margin="0,0,6,0" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="Red" Text="{Binding [0].ErrorContent}" /> </Grid> </Border> </ControlTemplate>
<TextBox x:Name="AgeTextBox" Grid.Row="1" Grid.Column="1" Margin="1" Validation.ErrorTemplate="{StaticResource ErrorTemplate}" >
使用方式很是简单,将上面的模板做为逻辑资源加入项目中,而后像上面同样引用便可。
效果图:
对知识梳理总结,但愿对你们有帮助!