[WPF 基础知识系列] —— 绑定中的数据校验Vaildation
原文:[WPF 基础知识系列] —— 绑定中的数据校验Vaildation

前言:

只要是有表单存在,那么就有可能有对数据的校验需求。如:判断是否为整数、判断电子邮件格式等等。

WPF采用一种全新的方式 - Binding,来实现前台显示与后台数据进行交互,当然数据校验方式也不一样了。

本专题全面介绍一下WPF中4种Validate方法,帮助你了解如何在WPF中对binding的数据进行校验,并处理错误显示。

一、简介

正常情况下,只要是绑定过程中出现异常或者在converter中出现异常,都会造成绑定失败。

但是WPF不会出现任何异常,只会显示一片空白(当然有些Converter中的异常会造成程序崩溃)。

这是因为默认情况下,Binding.ValidatesOnException为false,所以WPF忽视了这些绑定错误。

但是如果我们把Binding.ValidatesOnException为true,那么WPF会对错误做出以下反应:

  1. 设置绑定元素的附加属性 Validation.HasError为true(如TextBox,如果Text被绑定,并出现错误)。
  2. 创建一个包含错误详细信息(如抛出的Exception对象)的ValidationError对象。
  3. 将上面产生的对象添加到绑定对象的Validation.Errors附加属性当中。
  4. 如果Binding.NotifyOnValidationError是true,那么绑定元素的附加属性中的Validation.Error附加事件将被触发。(这是一个冒泡事件)

我们的Binding对象,维护着一个ValidationRule的集合,当设置ValidatesOnException为true时,

默认会添加一个ExceptionValidationRule到这个集合当中。

PS:对于绑定的校验只在Binding.Mode 为TwoWay和OneWayToSource才有效,

即当需要从target控件将值传到source属性时,很容易理解,当你的值不需要被别人使用时,就很可能校验也没必要。

二、四种实现方法

1、在Setter方法中进行判断

直接在Setter方法中,对value进行校验,如果不符合规则,那么就抛出异常。然后修改XAML不忽视异常。

public class PersonValidateInSetter : ObservableObject{private string name;private int age;public string Name{get   {  return this.name;   }set{if (string.IsNullOrWhiteSpace(value)){throw new ArgumentException("Name cannot be empty!");}if (value.Length < 4){throw new ArgumentException("Name must have more than 4 char!");}this.name = value;this.OnPropertyChanged(() => this.Name);}}public int Age{get{    return this.age;  }set{if (value < 18){throw new ArgumentException("You must be an adult!");}this.age = value;this.OnPropertyChanged(() => this.Age);}}}

         <Grid DataContext="{Binding PersonValidateInSetter}"><Grid.RowDefinitions><RowDefinition /><RowDefinition /></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition /></Grid.ColumnDefinitions><TextBlock Text="Name:" /><TextBox Grid.Column="1"Margin="1"Text="{Binding Name,ValidatesOnExceptions=True,UpdateSourceTrigger=PropertyChanged}" /><TextBlock Grid.Row="1" Text="Age:" /><TextBox Grid.Row="1"Grid.Column="1"Margin="1"Text="{Binding Age,ValidatesOnExceptions=True,UpdateSourceTrigger=PropertyChanged}" /></Grid>

当输入的值,在setter方法中校验时出现错误,就会出现一个红色的错误框。

关键代码:ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged。

PS:这种方式有一个BUG,首次加载时不会对默认数据进行检验。

2、继承IDataErrorInfo接口

使Model对象继承IDataErrorInfo接口,并实现一个索引进行校验。如果索引返回空表示没有错误,如果返回不为空,

表示有错误。另外一个Erro属性,但是在WPF中没有被用到。

public class PersonDerivedFromIDataErrorInfo : ObservableObject, IDataErrorInfo{private string name;private int age;public string Name{get{return this.name;}set{this.name = value;this.OnPropertyChanged(() => this.Name);}}public int Age{get{return this.age;}set{this.age = value;this.OnPropertyChanged(() => this.Age);}}// never called by WPFpublic string Error{get{return null;}}public string this[string propertyName]{get{switch (propertyName){case "Name":if (string.IsNullOrWhiteSpace(this.Name)){return "Name cannot be empty!";}if (this.Name.Length < 4){return "Name must have more than 4 char!";}break;case "Age":if (this.Age < 18){return "You must be an adult!";}break;}return null;}}}

<Grid  DataContext="{Binding PersonDerivedFromIDataErrorInfo}"><Grid.RowDefinitions><RowDefinition /><RowDefinition /></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition /></Grid.ColumnDefinitions><TextBlock Text="Name:" /><TextBox Grid.Column="1"Margin="1"Text="{Binding Name,NotifyOnValidationError=True,ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}" /><TextBlock Grid.Row="1" Text="Age:" /><TextBox Grid.Row="1"Grid.Column="1"Margin="1"Text="{Binding Age,NotifyOnValidationError=True,ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}" />

PS:这种方式,没有了第一种方法的BUG,但是相对很麻烦,既需要继承接口,又需要添加一个索引,如果遗留代码,那么这种方式就不太好。

3、自定义校验规则

一个数据对象或许不能包含一个应用要求的所有不同验证规则,但是通过自定义验证规则就可以解决这个问题。

在需要的地方,添加我们创建的规则,并进行检测。

通过继承ValidationRule抽象类,并实现Validate方法,并添加到绑定元素的Binding.ValidationRules中。

public class MinAgeValidation : ValidationRule{public int MinAge { get; set; }public override ValidationResult Validate(object value, CultureInfo cultureInfo){ValidationResult result = null;if (value != null){int age;if (int.TryParse(value.ToString(), out age)){if (age < this.MinAge){result = new ValidationResult(false, "Age must large than " + this.MinAge.ToString(CultureInfo.InvariantCulture));}}else{result = new ValidationResult(false, "Age must be a number!");}}else{result = new ValidationResult(false, "Age must not be null!");}return new ValidationResult(true, null);}}

<Grid><Grid.RowDefinitions><RowDefinition /><RowDefinition /></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition /></Grid.ColumnDefinitions><TextBlock Text="Name:" /><TextBox Grid.Column="1" Margin="1" Text="{Binding Name}"></TextBox><TextBlock Grid.Row="1" Text="Age:" /><TextBox Grid.Row="1"Grid.Column="1"Margin="1"><TextBox.Text><Binding Path="Age"UpdateSourceTrigger="PropertyChanged"ValidatesOnDataErrors="True"><Binding.ValidationRules><validations:MinAgeValidation MinAge="18" /></Binding.ValidationRules></Binding></TextBox.Text></TextBox></Grid>

这种方式,也会有第一种方法的BUG,暂时还不知道如何解决,但是这个能够灵活的实现校验,并且能传参数。

效果图:

4、使用数据注解(特性方式)

在System.ComponentModel.DataAnnotaions命名空间中定义了很多特性,

它们可以被放置在属性前面,显示验证的具体需要。放置了这些特性之后,

属性中的Setter方法就可以使用Validator静态类了,来用于验证数据。

public class PersonUseDataAnnotation : ObservableObject{private int age;private string name;[Range(18, 120, ErrorMessage = "Age must be a positive integer")]public int Age{get{return this.age;}set{this.ValidateProperty(value, "Age");this.SetProperty(ref this.age, value, () => this.Age);}}[Required(ErrorMessage = "A name is required")][StringLength(100, MinimumLength = 3, ErrorMessage = "Name must have at least 3 characters")]public string Name{get{return this.name;}set{this.ValidateProperty(value, "Name");this.SetProperty(ref this.name, value, () => this.Name);}}protected void ValidateProperty<T>(T value, string propertyName){Validator.ValidateProperty(value,                 new ValidationContext(this, null, null) { MemberName = propertyName });        }    }

<Grid><Grid.RowDefinitions><RowDefinition /><RowDefinition /></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition /></Grid.ColumnDefinitions><TextBlock Text="Name:" /><TextBox Grid.Column="1"Margin="1" Text="{Binding Name,ValidatesOnExceptions=True,UpdateSourceTrigger=PropertyChanged}" /><TextBlock Grid.Row="1" Text="Age:" /><TextBox Grid.Row="1"Grid.Column="1"Margin="1"Text="{Binding Age,ValidatesOnExceptions=True,UpdateSourceTrigger=PropertyChanged}" /></Grid>

使用特性的方式,能够很自由的使用自定义的规则,而且在.Net4.5中新增了很多特性,可以很方便的对数据进行校验。

例如:EmailAddress, Phone, and Url等。

三、自定义错误显示模板

在上面的例子中,我们可以看到当出现验证不正确时,绑定控件会被一圈红色错误线包裹住。

这种方式一般不能够正确的展示出,错误的原因等信息,所以有可能需要自己的错误显示方式。

前面,我们已经讲过了。当在检测过程中,出现错误时,WPF会把错误信息封装为一个ValidationError对象,

并添加到Validation.Errors中,所以我们可以取出错误详细信息,并显示出来。

1、为控件创建ErrorTemplate

下面就是一个简单的例子,每次都把错误信息以红色展示在空间上面。这里的AdornedElementPlaceholder相当于

控件的占位符,表示控件的真实位置。这个例子是在书上直接拿过来的,只能做基本展示用。

<ControlTemplate x:Key="ErrorTemplate"><Border BorderBrush="Red" BorderThickness="2"><Grid><AdornedElementPlaceholder x:Name="_el" /><TextBlock Margin="0,0,6,0"HorizontalAlignment="Right"VerticalAlignment="Center"Foreground="Red"Text="{Binding [0].ErrorContent}" /></Grid></Border></ControlTemplate>

<TextBox x:Name="AgeTextBox"Grid.Row="1"Grid.Column="1"Margin="1" Validation.ErrorTemplate="{StaticResource ErrorTemplate}" >

使用方式非常简单,将上面的模板作为逻辑资源加入项目中,然后像上面一样引用即可。

效果图:

对知识梳理总结,希望对大家有帮助!

posted on 2018-09-21 10:24 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/9685193.html

[WPF 基础知识系列] —— 绑定中的数据校验Vaildation相关推荐

  1. [C#基础知识系列]专题十七:深入理解动态类型

    本专题概要: 动态类型介绍 为什么需要动态类型 动态类型的使用 动态类型背后的故事 动态类型的约束 实现动态行为 总结 引言: 终于迎来了我们C# 4中特性了,C# 4主要有两方面的改善--Com 互 ...

  2. c# getresponsestream返回byte[]_C# 基础知识系列-13 常见类库(三)

    0. 前言 在<C# 基础知识系列- 13 常见类库(二)>中,我们介绍了一下DateTime和TimeSpan这两个结构体的内容,也就是C#中日期时间的简单操作.本篇将介绍Guid和Nu ...

  3. c# string 占位符_C# 基础知识系列- 9 字符串的更多用法(一)

    0. 前言 在前面的文章里简单介绍了一下字符串的相关内容,并没有涉及到更多的相关内容,这一篇将尝试讲解一下在实际开发工作中会遇到的字符串的很多操作. 1. 创建一个字符串 这部分介绍一下如何创建一个字 ...

  4. 线程基础知识系列(三)线程的同步

    本文是系列的第三篇,前面2篇,主要是针对单个线程如何管理,启动等,没有过多涉及多个线程是如何协同工作的. 线程基础知识系列(二)线程的管理 :线程的状态,控制,休眠,Interrupt,yield等 ...

  5. c#物联网_C# 基础知识系列- 16 开发工具篇

    0. 前言 这是C# 基础知识系列的最后一个内容讲解篇,下一篇是基础知识-实战篇.这一篇主要讲解一下C#程序的结构和主要编程工具. 1. 工具 工欲善其事必先利其器,在实际动手之前我们先来看看想要编写 ...

  6. 技术思考:也谈知识图谱平台中的数据流程与构建范式思考

    笔者之前写过一篇文章<关于知识图谱标准化构建平台的思考:知识图谱只能做项目,不能做平台?>,地址:https://blog.csdn.net/lhy2014/article/details ...

  7. c# contains方法_C# 基础知识系列- 3 集合数组

    简单的介绍一下集合,通俗来讲就是用来保管多个数据的方案.比如说我们是一个公司的仓库管理,公司有一堆货物需要管理,有同类的,有不同类的,总而言之就是很多.很乱.我们对照集合的概念对仓库进行管理的话,那么 ...

  8. 图像处理基础知识系列之五:贝叶斯方法简单梳理

    图像处理基础知识系列之五:贝叶斯方法简单梳理             文章来源:                  刘未鹏        数学之美番外篇:平凡而又神奇的贝叶斯方法(作者是个技术兼心理学 ...

  9. SAP SD基础知识之订单中装运相关的功能 I

    SAP SD基础知识之订单中装运相关的功能 I 一,装运点决定Shipping Point Determination 每个订单行项目都会决定一个shipping point:系统会自动带出一个shi ...

最新文章

  1. python if语句多个条件-Python中if有多个条件处理方法
  2. 建库、建表、建约束、插入测试数据
  3. C语言结构体和结构体数组示例 - Win32窗口程序演示
  4. 树状数组(单点+区间的所有操作)
  5. C++ sort函数的用法
  6. HihoCoder - 1829 Tomb Raider(暴力)
  7. 阿里云服务器重启后无法访问的解决
  8. Redis 高可用性实践
  9. 定时器的实现原理 不消耗cpu_一直在使用JDK动态代理, 不明白原理如何实现?
  10. 谈谈自我介绍与第一印象
  11. 致信息安全专业同学的一封信
  12. 妄想性仮想人格障害 汉化补丁(BUG修正)
  13. html5 canvas 绘制、移动方块及撤销操作
  14. 智能网联「中国百强供应商」都有谁?8月30-31日现场揭晓
  15. SQL面试必考——计算留存率
  16. 炎黄盈动易鲸云低代码平台,全面助推企业数字化转型升级
  17. 极具挑战性的工作:升级模式库从CQ2002到CQ7
  18. 《微机原理与接口技术》简答题总结
  19. 基于 Java 的短视频实战项目
  20. mysql docker还是rds_rds · 输出自己/docker-mysql - Gitee.com

热门文章

  1. WSDM 2022 | 一种用于在线广告自动竞价的协作竞争多智能体框架
  2. ROS2学习(十一).ROS概念 - 命令行工具的使用
  3. java web后端技能树_后端技能树修炼:CAP 定理
  4. pcs7更改项目计算机名时出错,PCS7 C/S报警问题-工业支持中心-西门子中国
  5. android 图片缓存工具类,Android工具类系列-Glide图片缓存与圆角
  6. nodejs mysql 注入_node.js sql 注入攻击防御方法 (sql Injection)
  7. 链接ftp命令行_windows下最轻便的FTP/SCP文件管理器
  8. java 泰勒级数_鸡群优化算法(CSO)、蜻蜓算法(DA)、乌鸦搜索算法(CSA)、泰勒级数(Taylor series)...
  9. 图论--一般带花树匹配
  10. python——input() 的用法及扩展