前言
WPF中的TextBox是最常使用的控件,一个友好的界面应该加入验证的功能,如下图所示

当输入年龄不合适时,及时给出友好的提醒。这种功能很常见,也很实用。实现的方式也有很多种。这篇文章介绍使用ExceptionValidationRule,即绑定的属性数据发生异常时,触发TextBox自身的Validation机制。下面先看看主要代码
实现
前台
<TextBox Name="txtAge">
<TextBox.Text>
<Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
或采用如下更简洁的方式
<TextBox Text="{Binding Age,UpdateSourceTrigger=PropertyChanged,
ValidatesOnExceptions=True}" />
说明:UpdateSourceTrigger="PropertyChanged",验证规则采用ExceptionValidationRule。
后台
class Student
{
private int age;
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
///<summary>
/// 年龄
/// </summary>
public int Age
{
get { return age; }
set
{
if (value >=0 && value <= 150)
{
age = value;
}
else
{
throw new Exception("Age should between 0 and 150");
}
}
}
}
说明:这里为了简化代码,Student类并未继承INotifyPropertyChanged接口,如果需要实现双向绑定,必须继承并实现INotifyPropertyChanged接口
运行的效果如下图所示,当输入200时,TextBox的Border为红色,但是界面中并未出现“Age should between 0 and 150”字眼。

其实,此刻已经成功了一半了,至于没有出现错误提示信息,是TextBox模板的问题,修改下TextBox模板即可
<Style TargetType="TextBox">
<Setter Property="MinHeight" Value="30"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal" >
<Border >
<Grid>
<AdornedElementPlaceholder x:Name="adorner" />
</Grid>
</Border>
<Grid Width="10"/>
<Popup Name="popup" AllowsTransparency="True" Placement="Right">
<Border x:Name="errorBorder" Background="#ffdc000c" Opacity="0" MinHeight="30" >
<TextBlock Margin="5,0" Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}" Foreground="White" TextWrapping="Wrap" VerticalAlignment="Center"/>
</Border>
</Popup>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding ElementName="adorner" Path="AdornedElement.IsKeyboardFocused" />
<span style="font-size:0px; color:#ff0000;"><a href="https://www.man-wax.com/">犀利士</a>
</span> </DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="IsOpen" TargetName="popup" Value="true"/>
</DataTrigger.Setters>
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="fadeInStoryboard">
<Storyboard>
<DoubleAnimation Duration="00:00:00.15" Storyboard.TargetName="errorBorder" Storyboard.TargetProperty="Opacity" To="1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="fadeInStoryboard"/>
<BeginStoryboard x:Name="fadeOutStoryBoard">
<Storyboard>
<DoubleAnimation Duration="00:00:00" Storyboard.TargetName="errorBorder" Storyboard.TargetProperty="Opacity" To="0"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="BorderBrush" Value="#ffdc000c"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
说明:最关键的代码是
Text="{Binding ElementName=adorner,
Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"
有些文档使用如下代码,本人不推荐这种写法,因为不是所有的TextBox绑定都有验证消息,如果缺失,可能导致index异常。
Text="{Binding ElementName=adorner,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
补充
走到这一步已经能实现输入验证功能了,但是新的需求来了,如何在“Submit”的时候判断是否所有的数据都满足要求。
方法一:判断每一个TextBox是否都满足条件
private void btnSubmit_Click(object sender, RoutedEventArgs e)
{
if(!Validation.GetHasError(txtName) &&
!Validation.GetHasError(txtAge))
{
// TODO
}
else
{
MessageBox.Show("Please input correct data in red textbox");
}
}
很明显这种方式存在一个问题,每个需要验证的TextBox都需要命名,然后在btnSubmit_Click下进行判断。对于数据项少的页面还好,数据项较多的,就比较繁琐而且容易遗漏。例如一些工控软件的数据项,可能有上百项。
方法二:
在Student类添加一个IsValidated属性,默认值为true,在每个异常项加入IsValidated = false。btnSubmit_Click只需要判断这个属性即可,主要代码如下
using System;
using System.ComponentModel;
class ViewMode: INotifyPropertyChanged
{
private bool isValidated = true; // 默认为全部通过验证
public event PropertyChangedEventHandler PropertyChanged;
protected void InvokePropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public bool IsValidated
{
get { return isValidated; }
protected set { isValidated = value; }
}
}
class Student:ViewMode
{
private string name;
private double age;
///<summary>
/// 姓名
/// </summary>
public string Name
{
get { return name; }
set
{
if (!string.IsNullOrEmpty(value))
{
name = value;
InvokePropertyChanged("Name");
}
else
{
IsValidated = false;
throw new Exception("Name cannot be empty");
}
}
}
///<summary>
/// 年龄
/// </summary>
public double Age
{
get { return age; }
set
{
if (value >=0 && value <= 150)
{
age = value;
InvokePropertyChanged("Age");
}
else
{
IsValidated = false;
throw new Exception("Age should between 0 and 150");
}
}
}
}
private void btnSubmit_Click(object sender, RoutedEventArgs e)
{
if(!stu.IsValidated)
{
MessageBox.Show("Please input correct data in red textbox");
}
}
源码下载
立即下载
文章评论