使用 WPF 数据绑定模型可以将 ValidationRules 与 Binding 对象相关联。验证在从绑定目标到绑定源的值传输过程中调用转换器之前发生。下面描述了验证过程:
-
在将值从目标属性传输到源属性时,数据绑定引擎首先移除可能已添加到所绑定元素的 Validation.Errors 附加属性的任何 ValidationError。然后,数据绑定引擎检查是否为该 Binding 定义了自定义 ValidationRule;如果定义了自定义验证规则,那么它将调用每个 ValidationRule 上的 Validate 方法,直到其中一个规则出错或者全部规则都通过为止。
-
如果某个自定义规则未通过,则绑定引擎会创建一个 ValidationError 对象,并将该对象添加到绑定元素的 Validation.Errors 集合。如果 Validation.Errors 不为空,则元素的 Validation.HasError 附加属性被设置为 true。此外,如果 Binding 的 NotifyOnValidationError 属性设置为 true,则绑定引擎将引发该元素上的 Validation.Error 附加事件。
-
如果所有规则都通过,则绑定引擎会调用转换器(如果存在的话)。
-
如果转换器通过,则绑定引擎会调用源属性的 setter。
-
如果绑定具有与其关联的 ExceptionValidationRule,并且在步骤 4 中引发异常,则绑定引擎将检查是否存在 UpdateSourceExceptionFilter。您可以选择使用 UpdateSourceExceptionFilter 回调来提供用于处理异常的自定义处理程序。如果未对 Binding 指定 UpdateSourceExceptionFilter,则绑定引擎将对异常创建 ValidationError 并将其添加到绑定元素的 Validation.Errors 集合中。
还应注意,任何方向(目标到源或源到目标)上的有效值传输操作都将清除 Validation.Errors 附加属性。
.net提供了三种ValidationRule:
1. CustomerValidationRule 用户提供的Rule
2. ExceptionValidationRule 更新源时,如果有异常(比如类型不匹配)或不满足条件,会向UI上报异常
3. DataErrorValidationRule 更新源时,如果值不满足条件(无法判断值类型异常),会向UI上报异常。要求对象继承自IDataErrorInfo,并且异常是在该对象中抛出的,下面会有示例说明
下面的示例来自msdn,显示了Binding Validation的使用:
图中第一个TextBox使用了自定义的ErrorTemplate,在TextBox左边显示一个红色的叹号
第二个TextBox使用的是系统默认的ErrorTemplate,把TextBox边框用红色表示
第三个TextBox使用的是ExceptionValidationRule,这个类在目标更新源时,发生异常(这里是输入是string,而源需要的是int,而引起的异常)时,把异常抛到UI,如果Binding有UpdateSourceExceptionFilter,会调用该方法(使用户可以更人性化的显示异常或做些自己的事),结果如图2。这个步骤解释的是上面第5点。
如果更新源时出现异常,而没有使用ExceptionValidationRule,那么即使输入的不符合条件,界面上也不会有任何提示。
另一种在UI上显示更新源异常的方案是使用Binding.ValidatesOnExceptions属性,如果设置为True,那么也会显示异常,默认是False。ValidatesOnExceptions和ExceptionValidationRule作用是相同的,是.net3.5中新增的一属性。如果两者同时存在,ValidatesOnExceptions为False,也会显示异常的。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SDKSample"
x:Class="SDKSample.Window1"
Title="Binding Validation Sample"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize">
<Window.Resources>
<c:MyDataSource x:Key="ods"/>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0" Grid.ColumnSpan="2"
FontSize="20" Margin="8"
Text="Enter a number between 21-130 or there will be a validation error:"/>
<Label Grid.Column="0" Grid.Row="1" FontSize="15" Margin="2"
Target="{Binding ElementName=textBox1}">TextBox with _custom ErrorTemplate and ToolTip:</Label>
<TextBox Name="textBox1" Width="50" FontSize="15"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}"
Grid.Row="1" Grid.Column="1" Margin="2">
<TextBox.Text>
<Binding Path="Age" Source="{StaticResource ods}"
UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<c:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Grid.Row="2" Grid.Column="0" FontSize="15" Margin="2"
Target="{Binding ElementName=textBox2}">TextBox with _default ErrorTemplate:</Label>
<TextBox Name="textBox2" Width="50" FontSize="15"
Grid.Row="2" Grid.Column="1" Margin="2">
<TextBox.Text>
<Binding Path="Age2" Source="{StaticResource ods}"
UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<c:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Grid.Row="3" Grid.ColumnSpan="3" FontSize="20" Margin="8"
Text="The following TextBox uses the ExceptionValidationRule and UpdateSourceExceptionFilter handler:"/>
<Label Grid.Row="4" Grid.Column="0" FontSize="15" Margin="2"
Target="{Binding ElementName=textBox3}">TextBox with UpdateSourceExceptionFilter _handler:</Label>
<TextBox Name="textBox3" Width="50" FontSize="15"
Grid.Row="4" Grid.Column="1" Margin="2"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding Path="Age3" Source="{StaticResource ods}"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<CheckBox Name="cb" FontSize="15" HorizontalAlignment="Left"
Grid.Row="4" Grid.Column="2" Margin="5"
Checked="UseCustomHandler" Unchecked="DisableCustomHandler">Enable Custom Handler (see ToolTip)</CheckBox>
</Grid>
</Window>
public class AgeRangeRule : ValidationRule { private int _min; private int _max; public AgeRangeRule() { } public int Min { get { return _min; } set { _min = value; } } public int Max { get { return _max; } set { _max = value; } } public override ValidationResult Validate(object value, CultureInfo cultureInfo) { int age = 0; try { if (((string)value).Length > 0) age = Int32.Parse((String)value); } catch (Exception e) { return new ValidationResult(false, "Illegal characters or " + e.Message); } if ((age < Min) || (age > Max)) { return new ValidationResult(false, "Please enter an age in the range: " + Min + " - " + Max + "."); } else { return new ValidationResult(true, null); } } }
public partial class Window1 : Window { public Window1() { InitializeComponent(); } void UseCustomHandler(object sender, RoutedEventArgs e) { BindingExpression myBindingExpression = textBox3.GetBindingExpression(TextBox.TextProperty); Binding myBinding = myBindingExpression.ParentBinding; myBinding.UpdateSourceExceptionFilter = new UpdateSourceExceptionFilterCallback(ReturnExceptionHandler); myBindingExpression.UpdateSource();//加入UpdateSourceExceptionFilter后,更新源,以触发异常 } void DisableCustomHandler(object sender, RoutedEventArgs e) { // textBox3 is an instance of a TextBox // the TextProperty is the data-bound dependency property Binding myBinding = BindingOperations.GetBinding(textBox3, TextBox.TextProperty); myBinding.UpdateSourceExceptionFilter -= new UpdateSourceExceptionFilterCallback(ReturnExceptionHandler); BindingOperations.GetBindingExpression(textBox3, TextBox.TextProperty).UpdateSource(); } object ReturnExceptionHandler(object bindingExpression, Exception exception) { return "This is from the UpdateSourceExceptionFilterCallBack."; } }
public class MyDataSource { private int _age; private int _age2; private int _age3; public MyDataSource() { Age = 0; Age2 = 0; } public int Age { get { return _age; } set { _age = value; } } public int Age2 { get { return _age2; } set { _age2 = value; } } public int Age3 { get { return _age3; } set { _age3 = value; } } }
ValidationRule.ValidationStep用来设置rule是什么时候被调用:
RawProposedValue 在进行任何转换之前运行 ValidationRule。
ConvertedProposedValue 在转换了值后运行 ValidationRule。
UpdatedValue 在更新了源后运行 ValidationRule。
CommittedValue 在将值提交到源后运行 ValidationRule。
默认值是RawProposedValue。
DataErrorValidationRule 的示例如下:
<Binding Path="Age" Source="{StaticResource data}" ValidatesOnExceptions="True" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <!--DataErrorValidationRule checks for validation errors raised by the IDataErrorInfo object.--> <!--Alternatively, you can set ValidationOnDataErrors="True" on the Binding.—> <DataErrorValidationRule/> </Binding.ValidationRules> </Binding>
public class Person : IDataErrorInfo { private int age; public int Age { get { return age; } set { age = value; } } public string Error { get { return null; } } public string this[string name] { get { string result = null; if (name == "Age") { if (this.age < 0 || this.age > 150) { result = "Age must not be less than 0 or greater than 150."; } } return result; } } }
和ExceptionValidationRule与ValidatesOnExceptions这对一样,DataErrorValidationRule 也有一个叫ValidatesOnDataErrors的属性与之配对。
关于validation,zlgcool的这篇文章可作参考
下面的文章会介绍使用BindingGroup对整个对象或表单的验证。