[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.pswp.cn/news/249462.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/249462.shtml
英文地址,請注明出處:http://en.pswp.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;在登錄時會遇到沒有訪問權限的問題…