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

[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,暂时还不知道如何解决,但是这个能够灵活的实现校验,并且能传参数。

效果图:

1

 

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}" >

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

效果图:

2

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/249462.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ModuleNotFoundError: No module named 'win32api'

启动一个工程的cmd&#xff1a; scrapy crawl HI 如果 运行报 No module named “win32api” 要安装 pip install pypiwin32 这个包转载于:https://www.cnblogs.com/hailong88/p/10528618.html

powercmd注册码

用户名&#xff1a;nzone注册码&#xff1a;PCMDA-86128-PCMDA-70594 http://www.baidu.com/

Servlet其实是单例多线程

https://blog.csdn.net/xiaojiahao_kevin/article/details/51781946

解决“跨域问题”的几种方法

&#xff08;0&#xff09;使用注解方式&#xff0c;这个可能有些框架可以&#xff0c;有些不行&#xff0c;在要访问的方法前面加上此注解即可&#xff1a; CrossOrigin &#xff08;1&#xff09;使用 Access-Control-Allow-Origin 设置请求响应头&#xff0c;简洁有效。 &am…

Conda 安装本地包

有的conda或pipy源太慢&#xff0c;conda install xxx或者pip install xxx下载会中断连接导致压缩包下载不全&#xff0c;本地的安装包没法完全安装, 遇到这个问题时&#xff0c;我们可以用p2p工具-迅雷等先下载指定包再用conda或pip安装 pip 安装本地包pip install D:\XXX.w…

DESUtils 加解密时 Given final block not properly padded bug小记

事情的经过是这个样子的。。。。。。 先说说问题是怎么出现的。根据客户需求&#xff0c;需要完成一个一键登录的功能&#xff0c;于是我的项目中就诞生了DesUtil&#xff0c;但是经过上百次用户测试&#xff0c;发现有一个用户登录就一直报错&#xff01;难道又遇到神坑啦&am…

Apache

https://www.iteye.com/blog/yaodaqing-1596570

仿 腾讯新闻快讯 --无缝滚动

//无缝滚动function AutoScroll(obj) {var autoScrollTimernull,timernull;timersetTimeout(function(){move();},3000);function move(){clearTime(autoScrollTimer);var liLen $(obj).find(li).length;if(liLen 1){//此处处理只有一条数据时 跳动效果$(obj).find("ul:f…

spring3.2 @Scheduled注解 定时任务

1.首先加入 下载spring3.2 &#xff0c;http://projects.spring.io/spring-framework/ 2.加入jar包&#xff0c;在applicationContext.xml加入声明-xmlns加入[java xmlns:task"http://www.springframework.org/schema/task" -xsi加入[java] http://www.springframe…

搜索(题目)

A.POJ_1321考查DFS的一个循环中递归调用 1 #include<iostream>2 #include<cstring>3 4 using namespace std;5 char a[10][10]; //记录棋盘位置6 int book[10]; //记录一列是否已经放过棋子7 int n, k; // k 为 需要放入的棋子数8 int t…

rest_framework中的url注册器,分页器,响应器

url注册器&#xff1a; 对于authors表&#xff0c;有两个url显得麻烦&#xff1a; rest_framework将我们的url进行了处理&#xff1a; 这样写了之后&#xff0c;就可以像原来一样访问author表了。 故意写错路径&#xff0c;看看它为我们做了哪些配置&#xff1a; 在有关author的…

Alluxio学习

介绍 Alluxio&#xff08;之前名为Tachyon&#xff09;是世界上第一个以内存为中心的虚拟的分布式存储系统。它统一了数据访问的方式&#xff0c;为上层计算框架和底层存储系统构建了桥梁。应用只需要连接Alluxio即可访问存储在底层任意存储系统中的数据。此外&#xff0c;Allu…

freemarker常见语法大全

FreeMarker的插值有如下两种类型:1,通用插值${expr};2,数字格式化插值:#{expr}或#{expr;format} ${book.name?if_exists } //用于判断如果存在,就输出这个值 ${book.name?default(‘xxx’)}//默认值xxx ${book.name!"xxx"}//默认值xxx ${book.date?string(yyy…

网页排版与布局

一 网站的层次结构 制作便于浏览页面的一个大敌就是视觉干扰,它包含两类: a,混乱页面主次不清,所有东西都引人注目 b,背景干扰 1.把页面分割成清晰明确的不同区域很重要,因为可以使用户迅速判断出哪些区域应重点看,哪些可以放心地忽略. 2.创建清晰直观的页面层次结构;越重要越要…

Bash的循环结构(for和while)

在bash有三中类型的循环结构表达方法&#xff1a;for&#xff0c;while&#xff0c;until。这里介绍常用的两种&#xff1a;for和while。 for bash的for循环表达式和python的for循环表达式风格很像&#xff1a; for var in $(ls) doecho "$var"done 取值列表有很多种…

MVVM模式下实现拖拽

MVVM模式下实现拖拽 原文:MVVM模式下实现拖拽在文章开始之前先看一看效果图 我们可以拖拽一个"游戏"给ListBox,并且ListBox也能接受拖拽过来的数据&#xff0c; 但是我们不能拖拽一个"游戏类型"给它。 所以当拖拽开始发生的时候我们必须添加一些限制条件&a…

nodejs变量

https://www.cnblogs.com/vipyoumay/p/5597992.html

jenkins+Docker持续化部署(笔记)

参考资料&#xff1a;https://www.cnblogs.com/leolztang/p/6934694.html &#xff08;Jenkins&#xff08;Docker容器内&#xff09;使用宿主机的docker命令&#xff09; https://container-solutions.com/running-docker-in-jenkins-in-docker/ &#xff08;Running Docker i…

正则表达式之括号

正则表达式&#xff08;三&#xff09; 括号 分组 量词可以作用字符或者字符组后面作为限定出现次数&#xff0c;如果是限制多个字符出现次数或者限制一个表达式出现次数&#xff0c;需要使用括号()将多个字符或者表达式括起来&#xff0c;这样便称为分组。例如(ab)表示“ab”字…

免安装Mysql在Mac中的神坑之Access denied for user 'root'@'localhost' (using password: YES)

眼看马上夜深人静了&#xff0c;研究了一天的问题也尘埃落定了。 废话不多说 直接来干货&#xff01; 大家都知道免安装版本的Mysql, 在Mac中安装完成&#xff08;如何安装详见Mac OS X 下 TAR.GZ 方式安装 MySQL&#xff09;之后&#xff0c;在登录时会遇到没有访问权限的问题…