在上一篇文章中我们讨论了有关WPF绑定的知识点,现在我们可以很容易的将业务数据作为源绑定到WPF控件并可以通过创建不同的Data Template后来以特定的样式展现。而作为这个最常用的功能我们可以通过Two Way的绑定模式与界面交互,而在这个时候我们就需要类似于ASP.NET中Validator一样的东西来限制或者说校验数据有效性。ValidationRule就是为这样的应用而生的。本文将详细讨论如何应用验证规则和自定义错误模板。

我们首先来看看WPF中有关数据验证规则相关的几个常用类:

  • ValidationRule : 所有自定义验证规则的基类。提供了让用户定义验证规则的入口。
  • ExceptionValidation :表示一个规则,该规则检查在绑定源属性更新过程中引发的异常。是一个内置的规则,它检查在绑定源属性更新过程中引发的异常。
  • ValidationResult : 数据验证结果的表现方式。ValidationRule对象的Validate方法执行完毕后通过ValidationResult来表示验证的结果。这里包含了错误信息—ErrorContent,数据是否有效—IsValid。ValidResult 为 ValidationResult 的有效实例。
  • ValidationError :表示一个验证错误,该错误在 ValidationRule 报告验证错误时由绑定引擎创建。

对于WPF中绑定的验证和转换值我们需要注意:

1.       在将值从目标属性传输到源属性时,数据绑定引擎首先移除可能已添加到所绑定元素的 Validation.Errors 附加属性的任何 ValidationError。然后,数据绑定引擎检查是否为该 Binding 定义了自定义验证规则;如果定义了自定义验证规则,那么它将调用每个 ValidationRule 上的 Validate 方法,直到其中一个规则失败或者全部规则都通过为止。如果某个自定义规则未通过,则绑定引擎会创建一个 ValidationError 对象,并将该对象添加到绑定元素的 Validation.Errors 集合。如果Validation.Errors 不为空,则元素的 Validation.HasError 附加属性被设置为 true。此外,如果 Binding 的 NotifyOnValidationError 属性设置为 true,则绑定引擎将引发该元素上的 Validation.Error 附加事件。

2.       如果所有规则都通过,则绑定引擎会调用转换器(如果存在的话)。

3.       如果转换器通过,则绑定引擎会调用源属性的 setter。

4.       如果绑定具有与其关联的 ExceptionValidationRule,并且在步骤 3 或 4 中引发异常,则绑定引擎将检查是否存在 UpdateSourceExceptionFilter。使用 UpdateSourceExceptionFilter 回调可以提供用于处理异常的自定义处理程序。如果未对 Binding 指定UpdateSourceExceptionFilter,则绑定引擎将对异常创建 ValidationError 并将其添加到绑定元素的 Validation.Errors 集合中。

任何方向(目标到源或源到目标)的有效值传输操作都将清除 Validation.Errors 附加属性。

  • 简单验证:使用ExceptionValidationRule

对于大多数验证来说我们都是在验证用户输入。ExceptionValidateRule作为WPF内置的简单验证器可以捕捉在绑定上发生的任何异常。我们可以用ExceptionValidateRule来作为一个笼统的错误收集器,来暴露出内部数据验证规则的异常信息。

模拟一个Employee信息更改的窗体。假设我们定义了一个Employee实体类,在实体类中我们对数据的有效性都做了简单的验证。

public string Title

{

get { return strTitle; }

set

{

strTitle = value;

if (String.IsNullOrEmpty(strTitle))

{

throw new ApplicationException("Please input Title.");

}

}

}

public DateTime Birthday

{

get { return objBirthday; }

set

{

objBirthday =value;

if (objBirthday.Year >= DateTime.Now.Year)

{

throw new ApplicationException("Please enter a valid date.");

}

}

}

在XAML中添加对字段的绑定,并对各个单独的控件的Text属性设置Bindg.ValidationRules. 这样当这个绑定的实体对象对应的属性出现验证错误时,错误信息会被ExceptionValidationRule抛出来。其默认行为是在当前控件LostFocus时触发验证,并将出错源控件加亮。

<TextBox Grid.Column="1" Grid.Row="2" Width="200" HorizontalAlignment="Left">

<TextBox.Text>

<Binding Path="Title">

<Binding.ValidationRules>

<ExceptionValidationRule />

</Binding.ValidationRules>

</Binding>

</TextBox.Text>

</TextBox>

上边的验证是正确了,但是我们并不知道具体的错误信息。这对于一个String类型的字段来说可能是可以理解的,我可以试出来。但对于特殊的字段来说,我们根本无法得知到底发生了什么,我怎么填写数据才能正确。那么如何去控制显示呢?

  • 自定义错误验证规则和定制显示

我们在上一篇里边提到过,所有的验证其实是有一个System.Windows.Controls.Validation类来完成的,对于这个类我们可以看到它有几个附加属性和一个附加事件Error.所谓附加事件/属性就是说可以被加载到另外的控件上来触发某个对象的事件或操作其属性,类似于我们有一个接口,通过这个接口你可以操作别的对象。在这里我们着重需要注意下边几个属性:

Errors

Gets the collection of all active ValidationError objects on the bound element.

HasError

Gets a value that indicates whether any binding on the binding target element has a ValidationError.

ErrorTemplate

Gets or sets the ControlTemplate used to generate validation error feedback on the adorner layer.

回想一下上一篇的最后部分,我们用到了触发器,用触发器来判断HasError属性,然后设置其错误模板。很容易我们可以做到这一点,这也是我们经常用到的办法。为了其复用性,我们可以将它定义为一个公用的Style。

<Style TargetType="{x:Type TextBox}">

<Style.Triggers>

<Trigger Property="Validation.HasError" Value="true">

<Setter Property="Validation.ErrorTemplate">

<Setter.Value>

<ControlTemplate>

<DockPanel LastChildFill="True">

<TextBlock DockPanel.Dock="Right" Foreground="Red" FontSize="12pt"

Text="{Binding ElementName=MyAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">

</TextBlock>

<Border BorderBrush="Red" BorderThickness="1">

<AdornedElementPlaceholder Name="MyAdorner" />

</Border>

</DockPanel>

</ControlTemplate>

</Setter.Value>

</Setter>

</Trigger>

</Style.Triggers>

</Style>

样式属于一个implicit key的样式,表明它针对的是所有的TargetType所指明的类型,这里是TextBox. 有个问题是,我们想在这里边的值改变时就需要触发验证而不是LostFocus,怎么改变呢?Binding对象有一个属性叫UpdateSourceTrigger,这是一个枚举值来指定什么时候触发数据源更新,更新的时候才会调用验证。

Members

Description

Default

The default UpdateSourceTrigger value of the binding target property. The default value for most dependency properties is PropertyChanged, while the Text property has a default value of LostFocus.

PropertyChanged

Updates the binding source immediately whenever the binding target property changes.

LostFocus

Updates the binding source whenever the binding target element loses focus.

Explicit

Updates the binding source only when you call the UpdateSource method.

<Binding Path="Title" UpdateSourceTrigger="PropertyChanged">就可以帮我们完成任务。

接下来我们可以定义一个对象化一些的错误处理类了。定义自己的规则来验证数据是ValidationRule所处理的主要任务,扩展这些规则就要使我们的类继承于这个父类:

public class EnumValidationRule : ValidationRule

{

private string _enumClass;

private string _errorMessage;

public string EnumClass

{

get { return "Allan.WPFBinding.ValidationDemo." + _enumClass; }

set { _enumClass = value; }

}

public string ErrorMessage

{

get { return _errorMessage; }

set { _errorMessage = value; }

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

ValidationResult result = new ValidationResult(true, null);

string inputString = (value ?? string.Empty).ToString();

Type type = Type.GetType(EnumClass);

if (string.IsNullOrEmpty(inputString) || !ValidateInput(type,inputString))

{

result = new ValidationResult(false, this.ErrorMessage);

}

return result;

}

}

更改绑定参数设置:将验证规则换成EnumValidationRule并设置相关参数。

<Binding Path="Title" UpdateSourceTrigger="PropertyChanged">

<Binding.ValidationRules>

<local:EnumValidationRule EnumClass="TitleEnum" ErrorMessage="输入值不存在。请重新输入。" />

</Binding.ValidationRules>

</Binding>

  • BindingGroups实现对List的验证

到目前为止我们所有看到的都是针对每个单个的控件输入的验证,那么有没有办法对比如某个ListView的每个item进行绑定验证呢?用上边的方法是不可以的,否则我们也没必要针对每个空间都去那么繁琐的设置各种信息啊。随同.NET Framework SP1发布了新的功能点,那就是BindingGroups。BindingGroups是对list列表中的绑定之间的关系进行了封装,使得你在获得一个上下文对象时可以和BIdingGroups关联来得到与当前上下文对象相关联的所有绑定。而这个关系是你需要显式去设定的—通过BindingGroupName。

FrameworkElement和FrameworkContentElement有一个叫做BindingGroup的依赖属性,而ItemsControl继承于FrameworkElement,所以其具有BindingGroup属性。而对于BindingBase对象添加了BindingGroupName属性。

public class BindingBase

{

    public string BindingGroupName { get; set; }

}  

我们刚才讲过,BindingGroup是需要显式的去声明,并且对于一个BindingGroup来说,拿到它就相当于拿到了当前对象相关上下文的所有与其有关系的绑定。而它依然本质上又回到了基本的ValidationRule。

在上边的类图中我们可以看到几个很有意思的东西:

  • BindingExpressions: 保存了与当前上下文有关的绑定对象。我们给DateContext中绑定了四个字段的值(对应四个控件),BindingExpressions就保存了所有归属到当前BindingGroup的所有Binding对象。例如你有四个字段,那就有四个BidingExpression,有5个字段绑定,你这里就有5个BindingExpression,即便他们很可能都是一样的。
  • Items :这个属性保存了与当前上下文对象有关的绑定。例如,我们在示例程序中只绑定了一个单独的Employee对象,那么BindingExpressions里就保留了这个上下文对象。换句话讲,当我们按照对象的方式来多个验证的时候(因为Item在ListView, DataGrid等控件里就代表一个单个对象),我们所操作的对象传递到验证规则时是以BindingExpression的target来表示的。这时,你得到了行的源,就可以做你想做的事情了。
  • ValidationRules : 关联到我们给bindingGroup加载的验证规则。
  • BeginEdit,  CancelEdit, CommitEdit : 实际上这些是和IEditableCollectionView关联的一些事件,表明对数据视图的操作。这里我们可以简单认为对数据进行更新和提交。
  • UpdateSources : 这是一个方法,表明我们要调用ValidationRule来验证数据。
  • ValidateWithoutUpdate : 方法:验证数据而不更新数据。

现在我们来应用BindingGroup给基于一个Form的绑定来做验证吧。首先是定义BindingGroup.

<Grid.BindingGroup>

<BindingGroup>

<BindingGroup.ValidationRules>

<local:EmployeeValidationRule ValidationStep="ConvertedProposedValue" />

</BindingGroup.ValidationRules>

</BindingGroup>

</Grid.BindingGroup>

注意这里应用了ValidationStep,和前边的UpdateSourceTrigger很相似,它是用来表示在什么时候来应用验证规则的。是个枚举值:

  • RawProposedValue = 0,   在没有变化的原数据上验证
  • ConvertedProposedValue = 1,用户更改过数据后验证 default
  • UpdatedValue = 2, 在将数据提交给Source时验证
  • CommittedValue = 3 在将数据提交到数据库后验证

接下来定义我们的规则。现在的规则是针对每个对象而不是单个属性的,所以与前边的有些许不同,但很容易,因为你可以拿到绑定到当前项的对象。

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

BindingGroup bindingGroup = (BindingGroup)value;

Employee emp = (Employee)bindingGroup.Items[0];

object startDateObj = bindingGroup.GetValue(emp, "StartDate");

DateTime? startDate = (DateTime)startDateObj;

object endDateObj = bindingGroup.GetValue(emp, "EndDate");

DateTime? endDate = (DateTime)endDateObj;

// check start and end date together

if (startDate.Value > endDate.Value)

{

return new ValidationResult(false, string.Format("StartDate: {0}, cannot be greater than EndDate: {1}",startDate,endDate));

}

else

{

return new ValidationResult(true, null);

}

}

Ok了,你可以再去定义一下出错时的ErrorTempalte,然后去享受这样的成果了:)

点击这里获取DEMO:)

Validation Rule和Binding Group的更多相关文章

  1. 在Salesforce中对某一个Object添加 Validation Rule

    在Salesforce中可以对某一个Object添加相应的 Validation Rule 来进行一个全局的条件判断,比如满足什么样的条件的修改允许提交,不满足的要提示相应的错误信息. 要创建一个Va ...

  2. Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)

    题外话:本篇是对之前那篇的重排版.并拆分成两篇,免得没了看的兴趣. 前言 在Spring Framework官方文档中,这三者是放到一起讲的,但没有解释为什么放到一起.大概是默认了读者都是有相关经验的 ...

  3. Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(二)

    接前一篇 Spring Framework 官方文档学习(四)之Validation.Data Binding.Type Conversion(一) 本篇主要内容:Spring Type Conver ...

  4. Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion

    本篇太乱,请移步: Spring Framework 官方文档学习(四)之Validation.Data Binding.Type Conversion(一) 写了删删了写,反复几次,对自己的描述很不 ...

  5. Learning note for Binding and validation

    Summary of my learning note for WPF Binding Binding to DataSet. when we want to add new record, we s ...

  6. MVVM架构~knockoutjs系列之从Knockout.Validation.js源码中学习它的用法

    返回目录 说在前 有时,我们在使用一个插件时,在网上即找不到它的相关API,这时,我们会很抓狂的,与其抓狂,还不如踏下心来,分析一下它的源码,事实上,对于JS这种开发语言来说,它开发的插件的使用方法都 ...

  7. Injecting and Binding Objects to Spring MVC Controllers--转

    I have written two previous posts on how to inject custom, non Spring specific objects into the requ ...

  8. Fluent Validation + NInject3 + MVC5

    Fluent Validation + NInject + MVC - Why & How : Part 1 http://fluentvalidation.codeplex.com/ htt ...

  9. Simple Validation in WPF

    A very simple example of displaying validation error next to controls in WPF Introduction This is a ...

随机推荐

  1. Django自定义上传目录

    由于数据库的upload_to功能,有时不能满足每次上传灵活自定义的需求, 基于DEF的上传,有时不能满足基于CLASS的视图要求, 于是,只好慢慢用土法实现. 当然,首先,要使用上传功能时,form ...

  2. jsp EL表达式 字符串的比较

    jsp EL表达式 字符串的比较 跟JavaScript一样,直接使用两个等于号即可:== 代码如下: <c:if test="${highLight == 'visa'}" ...

  3. block的是发送信号的线程,又不是处理槽函数的线程

    请问UI线程给子线程发信号,应该用哪种连接方式? 如果子线程正在执行一个函数,我发射信号去执行子线程的另一个函数,那么此时子线程到底会执行什么呢? 用信号量做的同步.第一把信号槽的事件丢到线程的事件队 ...

  4. python3使用requests爬取新浪热门微博

    微博登录的实现代码来源:https://gist.github.com/mrluanma/3621775 相关环境 使用的python3.4,发现配置好环境后可以直接使用pip easy_instal ...

  5. MySQL源码 解析器

    sql请求发送到server端,需要经过解析器生成内部的数据结构对象,以方便进行优化和生成执行计划.解析器主要做了两件事情,词法分析和语法分析. 词法和语法分析:mysql使用lex词法分析器,yac ...

  6. BZOJ3188: [Coci 2011]Upit

    3188: [Coci 2011]Upit Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 72  Solved: 24[Submit][Status] ...

  7. Http 状态码完整说明

    在网站建设的实际应用中,容易出现很多小小的失误,就像mysql当初优化不到位,影响整体网站的浏览效果一样,其实,网站的常规http状态码的表现也是一样, 一些常见的状态码为: 200 - 服务器成功返 ...

  8. MVC批量导出数据方法

    近段时间做了个数据平台,其中涉及到批量导出CSV格式数据的业务,主要使用了部分视图和视图之间传值等知识点,今天做了下整理,特此分享下: 主要分为四步: 1:要打印的数据格式陈列View: 2:自定义导 ...

  9. Can't initialize OCI. Error -1

    今天使用Toad连接Oracle时出现"Can't initialize OCI. Error -1" 解决方法 因为是刚做的windows 7系统,所以没有设置更改通知的时间 把 ...

  10. devi into python 笔记(二)元组 变量声明 和列表解析

    元组tuple: 类似list,只是tuple是不可变的list.类似java的String都是不可改变的.注意:tuple没有方法(有待考证),不可以像list那样那个list.pop 或者list ...