1,前言:
默认情况下,WPF XAML 中使用的绑定并未开启绑定验证,这样导致用户在UI上对绑定的属性进行赋值时即使因不符合规范内部已抛出异常(此情况仅限WPF中的数据绑定操作),也被程序默认忽略,UI层面也无异常提示,无法确定值是否已更改。而这些问题可通过Validation提供的附加属性,附加事件,错误模板进行检测提示,从而有效的解决绑定中产生的异常问题。
例如以下情况:
<TextBox><TextBox.Text><Binding Path="UnitCost" > </Binding></TextBox.Text></TextBox>
public decimal UnitCost{get { return unitCost; }set{//测试UI属性绑定异常抛出捕捉if (value < 0){throw new ArgumentException("值不能小于0");}unitCost = value;OnPropertyChanged(nameof(UnitCost));}}
在UI层面用户通过绑定将当前的 UnitCost 值设置为小于0时(仅限通过绑定输入的值),虽在代码中已产生异常,但运行程序对该绑定中产生的异常默认进行了忽略,不提示异常,导致该值是否已更新,无法确定。
2,数据验证的应用。
数据绑定进行数据源更新时先进行验证再进行装换,所以对于文本框而言,数据在验证时都是字符串型。
2.1,开启绑定中的验证通知:NotifyOnValidationError="True"
<TextBox Grid.Row="2" Grid.Column="1"><TextBox.Text><Binding Path="UnitCost" NotifyOnValidationError="True" ValidatesOnExceptions="True" ></Binding></TextBox.Text></TextBox>
2.2,开启异常验证捕捉规则:ValidatesOnExceptions="True",用于捕捉在该绑定中产生的任何异常(可选)。
此设定与以下绑定 ExceptionValidationRule 异常验证规则等同 :
<TextBox Grid.Row="2" Grid.Column="1"><TextBox.Text><Binding Path="UnitCost" NotifyOnValidationError="True" ><Binding.ValidationRules><ExceptionValidationRule></ExceptionValidationRule></Binding.ValidationRules></Binding></TextBox.Text></TextBox>
2.3,绑定自定义验证规则。
自定义的验证规则类需要继承自System.Windows.Controls下的抽象类ValidationRule。
class RangeValidationRule : ValidationRule{/// <summary>/// 范围上限/// </summary>public decimal MaxNum { get; set; } = 10000;/// <summary>/// 范围下限/// </summary>public decimal MinNum { get; set; }public override ValidationResult Validate(object value, CultureInfo cultureInfo){string valStr = value.ToString();if (string.IsNullOrEmpty(valStr)){return new ValidationResult(false, "不能为空值");}decimal val;if(!decimal.TryParse(valStr,NumberStyles.Any,cultureInfo,out val)){return new ValidationResult(false, "输入的内容非法,请输入有效的货币值");}if(val>MaxNum || val < MinNum){return new ValidationResult(false, $"只能是:{MinNum} - {MaxNum}之间的货币值");}return new ValidationResult(true, "");}}
2.4,在绑定中添加自定义的验证规则,并设置相应属性。
<TextBox Grid.Row="2" Grid.Column="1"><TextBox.Text><Binding Path="UnitCost" NotifyOnValidationError="True" ValidatesOnExceptions="True" ><Binding.ValidationRules><local:RangeValidationRule MinNum="10" MaxNum="10000"></local:RangeValidationRule></Binding.ValidationRules></Binding></TextBox.Text></TextBox>
2.5,验证失败时WPF自动将控件使用的模板切换为由Validation.ErrorTemplate附加属性定义的模板。在文本框中新模板将文本框的轮过改成一条细的红色边框(默认错误模板)。
3,使用Validation提供的附加属性,附加事件对异常进行处理。
Validation.HasError | 附加属性,验证当前元素是否存在验证错误 |
Validation.Error | 附加事件,当前元素验证错误事件(路由事件) |
Validation.Errors | 附加属性,当前元素产生的验证错误信息集合 |
Validation.ErrorTemplate | 附加属性,当前的元素的错误模板(模板类型:ControlTemplate) |
Validation.ErrorEvent为附加事件即为路由事件,所以可在其父容器进行注册监听。
3.1,在父容器添加附加事件,监听子元素产生的验证错误。
Validation.Error="Grid_Error"
示例:
<Grid Margin="10" DataContext="{Binding ElementName=listBox01, Path=SelectedItem}" Validation.Error="Grid_Error"><Grid.RowDefinitions><RowDefinition ></RowDefinition><RowDefinition ></RowDefinition><RowDefinition ></RowDefinition><RowDefinition ></RowDefinition><RowDefinition Height="3*"></RowDefinition></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="auto"></ColumnDefinition><ColumnDefinition ></ColumnDefinition></Grid.ColumnDefinitions><TextBlock Text="Model Number"></TextBlock><TextBox Grid.Column="1" Text="{Binding ModelNumber, TargetNullValue=[Empty]}"></TextBox><TextBlock Grid.Row="1" Text="Model Name"></TextBlock><TextBox Grid.Row="1" Grid.Column="1" Text="{Binding ModelName, TargetNullValue=[Empty]}"></TextBox><TextBlock Grid.Row="2" Text="Unit Cost"></TextBlock><TextBox Grid.Row="2" Grid.Column="1"><TextBox.Text><Binding Path="UnitCost" NotifyOnValidationError="True" ValidatesOnExceptions="True" ><Binding.ValidationRules><local:RangeValidationRule MinNum="10" MaxNum="10000"></local:RangeValidationRule></Binding.ValidationRules></Binding></TextBox.Text></TextBox><TextBlock Grid.Row="3" Text="Descriptionz:"></TextBlock><TextBox Grid.Row="4" Grid.ColumnSpan="2" Style="{x:Null}" Text="{Binding Description}"></TextBox></Grid></Border></Grid>
private void Grid_Error(object sender, ValidationErrorEventArgs e){if (e.Action == ValidationErrorEventAction.Added){StringBuilder sb = new StringBuilder();sb.AppendLine($"RoutedEvent:{e.RoutedEvent.Name}");sb.AppendLine($"Source:{e.Source}");sb.AppendLine($"ErrorContent:{e.Error.ErrorContent}");sb.AppendLine($"{e.Error.RuleInError.GetType().Name}");//sb.AppendLine($"Message:{e.Error.Exception.Message}");MessageBox.Show(sb.ToString());}}
3.2,根据当前元素的是否出现验证错误进行样式设置。
<Trigger Property="Validation.HasError" Value="true">
示例:
<Trigger Property="Validation.HasError" Value="true"> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" > </Setter></Trigger>
3.3,绑定当前元素的当前验证错误信息。
Path=(Validation.Errors)[0].ErrorContent
示例:
<Style.Triggers><Trigger Property="Validation.HasError" Value="true"><Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" ></Setter></Trigger></Style.Triggers>
注意此时的附加属性样式与Path关联的附加属性样式有差异:
Property中附加属性无括号包裹:
<Trigger Property="Validation.HasError" Value="true">
Path中的附加属性需用括号包裹
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" >
4,错误模板应用。
当验证控件的Validation.HasError属性被设置为true时,WPF自动将控件使用的模板切换为由Validation.ErrorTemplate附加属性定义的模板。在文本框中新模板将文本框的轮过改成一条细的红色边框。
ErrorTemplate的类型为ControlTemplate。
4.1,自定义错误模板。
<Style TargetType="TextBox"><Setter Property="Margin" Value="0,3"></Setter><Setter Property="VerticalContentAlignment" Value="Center"></Setter><Setter Property="Validation.ErrorTemplate"><Setter.Value><ControlTemplate><Border BorderBrush="Green" BorderThickness="1"><DockPanel LastChildFill="True"><TextBlock DockPanel.Dock="Right" Background="Red" ToolTip="{Binding ElementName=adornedElement1, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" HorizontalAlignment="Center">*</TextBlock><AdornedElementPlaceholder x:Name="adornedElement1"></AdornedElementPlaceholder></DockPanel></Border></ControlTemplate></Setter.Value></Setter></Style>
错误模板是使用装饰层,装饰层位于普通窗口之上的绘图层。
<AdornedElementPlaceholder x:Name="adornedElement1"></AdornedElementPlaceholder>
AdornedElementPlaceholder,这里指代被修饰的元素即文本框(AdornedElementPlaceholder必须位于ControlTemplate中,为固定写法。)。
4.2,通过AdornedElementPlaceholder获取被修饰对象上的验证错误信息。
<TextBlock DockPanel.Dock="Right" Background="Red" ToolTip="{Binding ElementName=adornedElement1, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" HorizontalAlignment="Center">*</TextBlock>
5,效果
6,Demo链接:
https://download.csdn.net/download/lingxiao16888/89263053