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.****.net/download/lingxiao16888/89263053