接上一篇文章《WPF的TextBox输入验证之Exception验证》,该文章继续讲述TextBox输入验证的方法,利用ValidationRule验证。
效果

初步方案
// 第一步,修改TextBox的Validation.ErrorTemplate
// 修改方法参考文章《WPF的TextBox输入验证之Exception验证》或下载源码
// 第二步,写验证规则
using System.Windows.Controls;
class AgeRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
int age;
if (!int.TryParse(value.ToString(), out age))
{
return new ValidationResult(false, "Invalid number format");
}
if (age > 150 || age < 0)
{
return new ValidationResult(false, "Age should between 0 and 150");
}
return ValidationResult.ValidResult;
}
}
// 第三步,写XAML布局
<TextBox Grid.Row="1" Grid.Column="1" Name="txtAge" >
<TextBox.Text>
<Binding Path="Age" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:NumberRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
// 第四步,提交时验证所有控件是否满足要求
private void btnSubmit_Click(object sender, RoutedEventArgs e)
{
if (!Validation.GetHasError(txtName) && !Validation.GetHasError(txtAge))
{
MessageBox.Show("Verificate successfully.");
}
else
{
MessageBox.Show("Please input correct data in red textbox","Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
该方案有两个的缺点:一、提交表单时需要验证所有控件是否满足要求,所以所有TextBox都需要命名;二、每个属性都需要写一个验证类,工作量较大。针对第一点我们查找出页面中所有的TextBox控件,然后进行遍历判断,代码如下:
private void Submit()
{
List<TextBox> texboxs = new List<TextBox>();
GetChildrenObject(this, ref texboxs, true);
foreach (var texbox in texboxs)
{
if (Validation.GetHasError(texbox))
{
MessageBox.Show("Please input correct data in red textbox", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
}
MessageBox.Show("Verificate successfully.");
}
private void GetChildrenObject<T>(DependencyObject obj, ref List<T> list, bool isDeepSearch) where T : FrameworkElement
{
DependencyObject child = null;
for (var i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
{
child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
{
list.Add((T)child);
}
else
{
if (isDeepSearch)
{
GetChildrenObject(child, ref list, true);
}
}
}
}
针对第二点,我们可以抽象出一个通用的验证类,例如年龄的范围是0-150岁,身高的的范围是40-300cm,那么我们抽象出一个数据范围验证类,在Xaml中设置最大值和最小值,改进方案如下。
改进方案
<TextBox Grid.Row="1" Grid.Column="1" Name="txtAge" >
<TextBox.Text>
<Binding Path="Age" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:NumberRule Minimum="0" Maximum="150"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox Grid.Row="1" Grid.Column="1" Name="txtHeight" >
<TextBox.Text>
<Binding Path="Height" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:NumberRule Minimum="40" Maximum="300" ErrorMessage="身高范围[40,300]cm"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
// 数字验证类
class NumberRule : ValidationRule
{
// 添加ErrorMessage字段,用于自定义错误提示
public string ErrorMessage { get; set; }
public double Minimum { get; set; }
public double Maximum { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
double number;
if (!double.TryParse(value.ToString(), out number))
{
return new ValidationResult(false, "Invalid number format");
}
if (number > Maximum || number < Minimum)
{
if(!string.IsNullOrEmpty(ErrorMessage))
{
return new ValidationResult(false, ErrorMessage);
}
else
{
return new ValidationResult(false, string.Format("Value should between {0} and {1}", Minimum, Maximum));
}
}
return ValidationResult.ValidResult;
}
}
完美主义者
经过改进后的方案可以说是相当完美了,但是还是发现几个奇怪的现象:
1、TextBox代码比较臃肿,每个TextBox至少需要9行代码,而且还无法使用模板解决
2、属性的范围及错误提示都在xaml中
3、Submit时,是对Texbox进行验证,而非属性本身
针对第一点了其实还能接受,至于第二点,如果数据的验证范规则或者范围改变,需要修改前端代码,更多人倾向于在后端写验证规则,前端调用。所以有没有一种方式是后端验证,将验证的结果通知到UI呢?这个就是我们下一篇文章要介绍的。《WPF的TextBox输入验证之IDataErrorInfo验证》。
源码下载
文章评论