不管在什么公司做什么工作,对自己的工作感到自豪,每天奋斗,收获成就感,我认为这种人配称真正的人才!–《半泽直树2》
一、前言
WPF
对比 Winform
界面框架的优势之一就在于支持数据绑定(Binding)。在数据绑定的加持下,WPF 轻易地实现了界面交互与数据分离,也就是说 UI
设计与交互功能实现
这两项工作不仅可以拆分开来进行分工协作 ,而且最后还能够轻松地整合在一起。
后端数据与前端数据绑定后可以实现数据的双向传递,同时在传递过程中,也能对数据进行一些操作,例如数据验证和数据转换,本篇文章重点介绍数据验证。
图1. 数据传递流程
那么,数据验证的目的是啥❓其实就是对数据类型或格式进行约束,过滤掉不合规的数据,以免影响程序的正常运行。举个栗子🌰,假设我们的程序是实现x + y
的数值相加,其中x
和y
值由用户在文本框中输入,如果用户误输了字母或汉字,必然导致类型转换失败,继而运算无法进行,在没有catch
异常的情况下,程序便会报错甚至崩溃。倘若我们在用户输入的时候,能够先对输入值进行验证,及时提醒用户输入值是否满足要求,这样一来很大程度上减轻了后端数据处理的负担。
不多说了,上干货😂!
二、方法及步骤
2.1 自定义验证规则
正如 图1 所示,要想实现自定义的验证规则,必须要继承 ValidationRule 抽象类,并实现它的抽象方法Validate
。以下 PositiveValidationRule
类能够对 正整数 进行验证。
正整数验证规则
public class PositiveValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value != null)
{
if (Regex.IsMatch(value.ToString(), "^[1-9]\\d*$"))
{
return new ValidationResult(true, null);
}
else
{
return new ValidationResult(false, "错误输入");
}
}
else
{
return new ValidationResult(true, null);
}
}
}
ValidationRule
抽象类有两个重要属性,分别为ValidatesOnTargetUpdated
和ValidationStep
,前者指明验证的方向(单向 or 双向);后者指明验证的时机。
ValidatesOnTargetUpdated:从源到目标时是否进行验证
当该属性为 false
时,默认只对从目标到数据源这条传递线进行验证,即排除后端输出错误的可能性;反之,为 true
时,会进行双向验证。而通常情况下,我们采用默认值即可,因为当我们的界面启动时,数据源一般为空,处于待用户输入的状态,如果此时属性为 true
,那么就会出现验证错误的提示,也就说每次用户启动界面就会收到输入错误的信息(大红框),用户心里肯定 **MMP 😂**呀,所以这个属性大家慎改。
ValidationStep:指定 ValidationRule 何时运行
ValidationStep | 说明 | 是否先转换再验证 | 验证失败时,源是否改变 |
---|---|---|---|
RawProposedValue* | 在进行任何转换之前运行 ValidationRule | ✖ | ✖ |
ConvertedProposedValue | 在转换了值后运行 ValidationRule | ✔ | ✖ |
UpdatedValue | 在更新了源后运行 ValidationRule | ✔ | ✔ |
CommittedValue | 在将值提交到源后运行 ValidationRule | ✔ | ✔ |
注:*为默认值
🔔 这里需要注意的是当ValidationStep
属性值为UpdatedValue
和CommittedValue
时,验证规则中的 Value 变为了System.Windows.Data.BindingExpression
,而非目标值,此时需要通过获取窗口对象来拿到目标值进行验证!
public class PositiveValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
// MainWindow为当前窗口类
MainWindow mainwin = Application.Current.MainWindow as MainWindow;
string realValue = mainwin.SearchKeyForBinding.Id;
if (realValue != null)
{
if (Regex.IsMatch(realValue.ToString(), "^[1-9]\\d*$"))
{
return new ValidationResult(true, null);
}
else
{
return new ValidationResult(false, "错误输入");
}
}
else
{
return new ValidationResult(true, null);
}
}
}
那么 UpdatedValue
所谓的更新和CommittedValue
的提交又有什么区别。两者都是用更改后的数据源值做验证,而CommittedValue
的验证发生在调用BindingGroup.CommitEdit
之后,这个很少使用,不用纠结😉,我自己也没看懂,理解前三个即可!
2.2 数据绑定
要实现验证除了验证规则外,还依赖于数据绑定。也就是说,我们首先需要搭建起目标至数据源的桥梁,才能在他们之间安插门卫 😂!以最常见的TextBox
为例,我们需要将Text
属性绑定到数据源,然后继续往下设置验证规则,具体代码如下,代码中的bll
代表验证规则类的命名空间。
<TextBox.Text>
<Binding Path="Id" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<bll:DataValidation ValidationStep="RawProposedValue" ValidatesOnTargetUpdated="False"></bll:DataValidation>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
验证规则+数据绑定,两步走就可以完成数据验证啦,当然网上还有添加ToolTip
、弹窗提示、新建控件模板等更深入的文章,这里不多介绍了,想了解的话可以去打开文末的参考文献找一找!
三、应用实例
这里以我之前开源的CATSearch 为例,实现对 ID
输入的验证功能( 项目已公开,可供下载 学习 )。如 图2 所示,CATIA V6中的所有模型都具有一个唯一标识符,该标识符组成结构包括:3个字母-8位数字-8位数字。程序需要对这种格式进行验证,那么就需要借助正则表达式
了。正则表达式就不解释了,有时间的话写一篇专门的文章进行介绍,这里直接贴代码。
图2. ID输入格式分析
External_ID验证规则
public class DataValidation : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value != null)
{
// if (Regex.IsMatch(value.ToString(), "^[a-z]{3}-[0-9]{8}-[0-9]{8}$")) 也可替换为这一句
if (Regex.IsMatch(value.ToString(), "^[a-z]{3}-\\d{8}-\\d{8}$"))
{
return new ValidationResult(true, null);
}
else
{
return new ValidationResult(false, "当前输入无效");
}
}
else
{
return new ValidationResult(true, null);
}
}
}