利刃 MVVMLight 5:綁定在表單驗證上的應用

   表單驗證是MVVM體系中的重要一塊。而綁定除了推進 Model-View-ViewModel (MVVM) 模式鬆散耦合 邏輯、數據 和 UI定義 的關係以外,還爲業務數據驗證方案提供強大而靈活的支持。前端

WPF 中的數據綁定機制包括多個選項,可用於在建立可編輯視圖時校驗輸入數據的有效性。git

常見的表單驗證機制有以下幾種:正則表達式

驗證類型 說明
Exception 驗證 經過在某個 Binding 對象上設置 ValidatesOnExceptions 屬性,若是源對象屬性設置已修改的值的過程當中引起異常,則拋出錯誤併爲該 Binding 設置驗證錯誤。
ValidationRule 驗證

Binding 類具備一個用於提供 ValidationRule 派生類實例的集合的屬性。這些 ValidationRules 須要覆蓋某個 Validate 方法,該方法由 Binding 在每次綁定控件中的數據發生更改時進行調用。express

若是 Validate 方法返回無效的 ValidationResult 對象,則將爲該 Binding 設置驗證錯誤。app

IDataErrorInfo 驗證

經過在綁定數據源對象上實現 IDataErrorInfo 接口並在 Binding 對象上設置 ValidatesOnDataErrors 屬性,Binding 將調用從綁定數據源對象公開的 IDataErrorInfo API。框架

若是從這些屬性調用返回非 null 或非空字符串,則將爲該 Binding 設置驗證錯誤。ide

 

 

 

 

 

 

 

 

驗證交互的關係模式如圖:ui

       

   咱們在使用 WPF 中的數據綁定來呈現業務數據時,一般會使用 Binding 對象在目標控件的單個屬性與數據源對象屬性之間提供數據管道。this

若是要使得綁定驗證有效,首先須要進行 TwoWay 數據綁定。這代表,除了從源屬性流向目標屬性以進行顯示的數據以外,編輯過的數據也會從目標流向源。編碼

這就是偉大的雙向數據綁定的精髓,因此在MVVM中作數據校驗,會容易的多。

當 TwoWay 數據綁定中輸入或修改數據時,將啓動如下工做流:

一、  用戶經過鍵盤、鼠標、手寫板或者其餘輸入設備來輸入或修改數據,從而改變綁定的目標信息
二、 設置源屬性值。
三、 觸發 Binding.SourceUpdated 事件。
四、 若是數據源屬性上的 setter 引起異常,則異常會由 Binding 捕獲,並可用於指示驗證錯誤。
五、 若是實現了 IDataErrorInfo 接口,則會對數據源對象調用該接口的方法得到該屬性的錯誤信息。
六、 向用戶呈現驗證錯誤指示,並觸發 Validation.Error 附加事件。

 

 







綁定目標向綁定源發送數據更新的請求,而綁定源則對數據進行驗證,並根據不一樣的驗證機制進行反饋。 

 

    下面咱們用實例來對比下這幾種驗證機制,在此以前,咱們先作一個事情,就是寫一個錯誤觸發的樣式,來保證錯誤觸發的時候直接清晰的向用戶反饋出去。

咱們新建一個資源字典文件,命名爲TextBox.xaml,下面這個是資源字典文件的內容,目標類型是TextBoxBase基礎的控件,如TextBox和RichTextBox.

代碼比較簡單,注意標紅的內容,設計一個紅底白字的提示框,當源屬性觸發錯誤驗證的時候,把驗證對象集合中的錯誤內容顯示出來。

  1 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  3 
  4     <Style x:Key="{x:Type TextBoxBase}" TargetType="{x:Type TextBoxBase}" BasedOn="{x:Null}">
  5         <Setter Property="BorderThickness" Value="1"/>
  6         <Setter Property="Padding" Value="2,1,1,1"/>
  7         <Setter Property="AllowDrop" Value="true"/>
  8         <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
  9         <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
 10         <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
 11         <Setter Property="SelectionBrush" Value="{DynamicResource Accent}" />
 12         <Setter Property="Validation.ErrorTemplate">
 13             <Setter.Value>
 14                 <ControlTemplate>
 15                     <StackPanel Orientation="Horizontal">
 16                         <Border BorderThickness="1" BorderBrush="#FFdc000c" VerticalAlignment="Top">
 17                             <Grid>
 18                                 <AdornedElementPlaceholder x:Name="adorner" Margin="-1"/>
 19                             </Grid>
 20                         </Border>
 21                         <Border x:Name="errorBorder" Background="#FFdc000c" Margin="8,0,0,0"
 22                                 Opacity="0" CornerRadius="0"
 23                                 IsHitTestVisible="False"
 24                                 MinHeight="24" >
 25                             <TextBlock Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
 26                                        Foreground="White" Margin="8,2,8,3" TextWrapping="Wrap" VerticalAlignment="Center"/>
 27                         </Border>
 28                     </StackPanel>
 29                     <ControlTemplate.Triggers>
 30                         <DataTrigger Value="True">
 31                             <DataTrigger.Binding>
 32                                 <Binding ElementName="adorner" Path="AdornedElement.IsKeyboardFocused" />
 33                             </DataTrigger.Binding>
 34                             <DataTrigger.EnterActions>
 35                                 <BeginStoryboard x:Name="fadeInStoryboard">
 36                                     <Storyboard>
 37                                         <DoubleAnimation Duration="00:00:00.15"
 38                                                          Storyboard.TargetName="errorBorder"
 39                                                          Storyboard.TargetProperty="Opacity"
 40                                                          To="1"/>
 41                                     </Storyboard>
 42                                 </BeginStoryboard>
 43                             </DataTrigger.EnterActions>
 44                             <DataTrigger.ExitActions>
 45                                 <StopStoryboard BeginStoryboardName="fadeInStoryboard"/>
 46                                 <BeginStoryboard x:Name="fadeOutStoryBoard">
 47                                     <Storyboard>
 48                                         <DoubleAnimation Duration="00:00:00"
 49                                                          Storyboard.TargetName="errorBorder"
 50                                                          Storyboard.TargetProperty="Opacity"
 51                                                          To="0"/>
 52                                     </Storyboard>
 53                                 </BeginStoryboard>
 54                             </DataTrigger.ExitActions>
 55                         </DataTrigger>
 56                     </ControlTemplate.Triggers>
 57                 </ControlTemplate>
 58             </Setter.Value>
 59         </Setter>
 60         <Setter Property="Template">
 61             <Setter.Value>
 62                 <ControlTemplate TargetType="{x:Type TextBoxBase}">
 63                     <Border x:Name="Bd"
 64                             BorderThickness="{TemplateBinding BorderThickness}"
 65                             BorderBrush="{TemplateBinding BorderBrush}"
 66                             Background="{TemplateBinding Background}"
 67                             Padding="{TemplateBinding Padding}"
 68                             SnapsToDevicePixels="true">
 69                         <ScrollViewer x:Name="PART_ContentHost" RenderOptions.ClearTypeHint="Enabled"
 70                                       SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
 71                     </Border>
 72                     <ControlTemplate.Triggers>
 73                         <Trigger Property="IsEnabled" Value="false">
 74                             <Setter Property="Foreground" Value="{DynamicResource InputTextDisabled}"/>
 75                         </Trigger>
 76                         <Trigger Property="IsReadOnly" Value="true">
 77                             <Setter Property="Foreground" Value="{DynamicResource InputTextDisabled}"/>
 78                         </Trigger>
 79                         <Trigger Property="IsFocused" Value="true">
 80                             <Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource Accent}" />
 81                         </Trigger>
 82                         <MultiTrigger>
 83                             <MultiTrigger.Conditions>
 84                                 <Condition Property="IsReadOnly" Value="False"/>
 85                                 <Condition Property="IsEnabled" Value="True"/>
 86                                 <Condition Property="IsMouseOver" Value="True"/>
 87                             </MultiTrigger.Conditions>
 88                             <Setter Property="Background" Value="{DynamicResource InputBackgroundHover}"/>
 89                             <Setter Property="BorderBrush" Value="{DynamicResource InputBorderHover}"/>
 90                             <Setter Property="Foreground" Value="{DynamicResource InputTextHover}"/>
 91                         </MultiTrigger>
 92                     </ControlTemplate.Triggers>
 93                 </ControlTemplate>
 94             </Setter.Value>
 95         </Setter>
 96     </Style>
 97     <Style BasedOn="{StaticResource {x:Type TextBoxBase}}" TargetType="{x:Type TextBox}">
 98     </Style>
 99     <Style BasedOn="{StaticResource {x:Type TextBoxBase}}" TargetType="{x:Type RichTextBox}">
100     </Style>
101     
102 </ResourceDictionary>

  而後在App.Xaml中全局註冊到整個應用中。

 1 <Application x:Class="MVVMLightDemo.App" 
 2              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
 3              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
 4              StartupUri="View/BindingFormView.xaml" 
 5              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
 6              d1p1:Ignorable="d" 
 7              xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006"
 8              xmlns:vm="clr-namespace:MVVMLightDemo.ViewModel"
 9              xmlns:Common="clr-namespace:MVVMLightDemo.Common">
10   <Application.Resources>
11     <ResourceDictionary>
12             <ResourceDictionary.MergedDictionaries>
13                 <ResourceDictionary Source="/MVVMLightDemo;component/Assets/TextBox.xaml" />
14             </ResourceDictionary.MergedDictionaries>
15             <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
16             <Common:IntegerToSex x:Key="IntegerToSex" d:IsDataSource="True" />
17         </ResourceDictionary>
18   </Application.Resources>
19 </Application>

 達到的效果以下:

 

下面詳細描述下這三種驗證模式  

一、Exception 驗證:

正如說明中描述的那樣,在具備綁定關係的源字段模型上作驗證異常的引起並拋出,在View中的Xaml對象上設置 ExceptionValidationRule 屬性,響應捕獲異常並顯示。

View代碼:

 1                 <GroupBox Header="Exception 驗證" Margin="10 10 10 10" DataContext="{Binding Source={StaticResource Locator},Path=ValidateException}" >
 2                     <StackPanel x:Name="ExceptionPanel" Orientation="Vertical" Margin="0,10,0,0" >
 3                         <StackPanel>
 4                             <Label Content="用戶名" Target="{Binding ElementName=UserNameEx}"/>
 5                             <TextBox x:Name="UserNameEx" Width="150">
 6                                 <TextBox.Text>
 7                                     <Binding Path="UserNameEx" UpdateSourceTrigger="PropertyChanged">
 8                                         <Binding.ValidationRules>
 9                                             <ExceptionValidationRule></ExceptionValidationRule>
10                                         </Binding.ValidationRules>
11                                     </Binding>
12                                 </TextBox.Text>
13                             </TextBox>
14                         </StackPanel>
15                     </StackPanel>
16                 </GroupBox>

  ViewModel代碼:

 1     /// <summary>
 2     /// Exception 驗證
 3     /// </summary>
 4     public class ValidateExceptionViewModel:ViewModelBase
 5     {
 6         public ValidateExceptionViewModel()
 7         {
 8 
 9         }
10 
11         private String userNameEx;
12         /// <summary>
13         /// 用戶名稱(不爲空)
14         /// </summary>
15         public string UserNameEx
16         {
17             get
18             {
19                 return userNameEx;
20             }
21             set
22             {
23                 userNameEx = value;
24                 RaisePropertyChanged(() => UserNameEx);
25                 if (string.IsNullOrEmpty(value))
26                 {
27                     throw new ApplicationException("該字段不能爲空!");
28                 }
29             }
30         }
31     }

  結果如圖:

 

將驗證失敗的信息直接拋出來,這無疑是最簡單粗暴的,實現也很簡單,可是隻是針對單一源屬性進行驗證, 複用性不高。

並且在組合驗證(好比同時須要驗證非空和其餘規則)狀況下,會致使Model中寫太重過臃腫的代碼。

 

二、ValidationRule 驗證:

經過繼承ValidationRule 抽象類,並重寫他的Validate方法來擴展編寫咱們須要的驗證類。該驗證類能夠直接使用在咱們須要驗證的屬性。

View代碼:

 1                 <GroupBox Header="ValidationRule 驗證"  Margin="10 20 10 10" DataContext="{Binding Source={StaticResource Locator},Path=ValidationRule}" >
 2                     <StackPanel x:Name="ValidationRulePanel" Orientation="Vertical" Margin="0,20,0,0">
 3                         <StackPanel>
 4                             <Label Content="用戶名" Target="{Binding ElementName=UserName}"/>
 5                             <TextBox Width="150" >
 6                                 <TextBox.Text>
 7                                     <Binding Path="UserName" UpdateSourceTrigger="PropertyChanged">
 8                                     <Binding.ValidationRules>
 9                                         <app:RequiredRule />
10                                     </Binding.ValidationRules>
11                                 </Binding>
12                                 </TextBox.Text>
13                             </TextBox>
14                         </StackPanel>
15 
16                         <StackPanel>
17                             <Label Content="用戶郵箱" Target="{Binding ElementName=UserEmail}"/>
18                             <TextBox Width="150">
19                                 <TextBox.Text>
20                                     <Binding Path="UserEmail" UpdateSourceTrigger="PropertyChanged">
21                                         <Binding.ValidationRules>
22                                             <app:EmailRule />
23                                         </Binding.ValidationRules>
24                                     </Binding>
25                                 </TextBox.Text>
26                             </TextBox>
27                         </StackPanel>
28                     </StackPanel>
29                 </GroupBox>

 重寫兩個ValidationRule,代碼以下:

 1  public class RequiredRule : ValidationRule
 2     {
 3         public override ValidationResult Validate(object value, CultureInfo cultureInfo)
 4         {
 5             if (value == null)
 6                 return new ValidationResult(false, "該字段不能爲空值!");
 7             if (string.IsNullOrEmpty(value.ToString()))
 8                 return new ValidationResult(false, "該字段不能爲空字符串!");
 9             return new ValidationResult(true, null);
10         }
11     }
12 
13     public class EmailRule : ValidationRule
14     {
15         public override ValidationResult Validate(object value, CultureInfo cultureInfo)
16         {
17             Regex emailReg = new Regex("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@(\\w+\\.)+\\w{2,5})\\s*$");
18 
19             if (!String.IsNullOrEmpty(value.ToString()))
20             {
21                 if (!emailReg.IsMatch(value.ToString()))
22                 {
23                     return new ValidationResult(false, "郵箱地址不許確!");
24                 }
25             }
26             return new ValidationResult(true, null);
27         }
28     }

 建立了兩個類,一個用於驗證是否爲空,一個用於驗證是否符合郵箱地址標準格式。 

ViewModel代碼:

 1   public class ValidationRuleViewModel:ViewModelBase
 2     {
 3         public ValidationRuleViewModel()
 4         {
 5 
 6         }
 7 
 8         #region 屬性
 9 
10         private String userName;
11         /// <summary>
12         /// 用戶名
13         /// </summary>
14         public String UserName
15         {
16             get { return userName; }
17             set { userName = value; RaisePropertyChanged(()=>UserName); }
18         }
19 
20 
21 
22         private String userEmail;
23         /// <summary>
24         /// 用戶郵件
25         /// </summary>
26         public String UserEmail
27         {
28             get { return userEmail; }
29             set { userEmail = value;RaisePropertyChanged(()=>UserName);  }
30         }
31 
32         #endregion

 結果以下:

 說明:相對來講,這種方式是比較不錯的,獨立性、複用性都很好,從鬆散耦合角度來講也是比較恰當的。

能夠預先寫好一系列的驗證規則類,視圖編碼人員能夠根據需求直接使用這些驗證規則,服務端無需額外的處理。

可是仍然有缺點,擴展性差,若是須要個性化反饋消息也須要額外擴展。不符合日益豐富的前端驗證需求。

 

三、IDataErrorInfo 驗證:

3.一、在綁定數據源對象上實現 IDataErrorInfo 接口

3.二、在 Binding 對象上設置 ValidatesOnDataErrors 屬性

Binding 將調用從綁定數據源對象公開的 IDataErrorInfo API。若是從這些屬性調用返回非 null 或非空字符串,則將爲該 Binding 設置驗證錯誤。

View代碼:

 1                 <GroupBox Header="IDataErrorInfo 驗證" Margin="10 20 10 10" DataContext="{Binding Source={StaticResource Locator},Path=BindingForm}" >
 2                     <StackPanel x:Name="Form" Orientation="Vertical" Margin="0,20,0,0">
 3                         <StackPanel>
 4                             <Label Content="用戶名" Target="{Binding ElementName=UserName}"/>
 5                             <TextBox Width="150" 
 6                                  Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" >
 7                             </TextBox>
 8                         </StackPanel>
 9 
10                         <StackPanel>
11                             <Label Content="性別" Target="{Binding ElementName=RadioGendeMale}"/>
12                             <RadioButton Content="" />
13                             <RadioButton Content="" Margin="8,0,0,0" />
14                         </StackPanel>
15                         <StackPanel>
16                             <Label Content="生日" Target="{Binding ElementName=DateBirth}" />
17                             <DatePicker x:Name="DateBirth" />
18                         </StackPanel>
19                         <StackPanel>
20                             <Label Content="用戶郵箱" Target="{Binding ElementName=UserEmail}"/>
21                             <TextBox Width="150" Text="{Binding UserEmail, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
22                         </StackPanel>
23                         <StackPanel>
24                             <Label Content="用戶電話" Target="{Binding ElementName=UserPhone}"/>
25                             <TextBox Width="150" Text="{Binding UserPhone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
26                         </StackPanel>
27                     </StackPanel>
28                 </GroupBox>

 ViewModel代碼:

 1  public class BindingFormViewModel :ViewModelBase, IDataErrorInfo  2     {
 3         public BindingFormViewModel()
 4         {
 5 
 6         }
 7 
 8         #region 屬性
 9              
10         private String userName;
11         /// <summary>
12         /// 用戶名
13         /// </summary>
14         public String UserName
15         {
16             get { return userName; }
17             set { userName = value; }
18         }
19 
20 
21 
22         private String userPhone;
23         /// <summary>
24         /// 用戶電話
25         /// </summary>
26         public String UserPhone
27         {
28             get { return userPhone; }
29             set { userPhone = value; }
30         }
31 
32 
33 
34         private String userEmail;
35         /// <summary>
36         /// 用戶郵件
37         /// </summary>
38         public String UserEmail
39         {
40             get { return userEmail; }
41             set { userEmail = value; }
42         }
43         #endregion
44 
45         public String Error
46         {
47             get { return null; }
48         }
49                 
50         public String this[string columnName]
51         {
52             get
53             {
54                 Regex digitalReg = new Regex(@"^[-]?[1-9]{8,11}\d*$|^[0]{1}$");
55                 Regex emailReg = new Regex("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@(\\w+\\.)+\\w{2,5})\\s*$");
56 
57 
58                 if (columnName == "UserName" && String.IsNullOrEmpty(this.UserName))
59                 {
60                     return "用戶名不能爲空";
61                 }
62                 
63                 if (columnName == "UserPhone" && !String.IsNullOrEmpty(this.UserPhone))
64                 {
65                     if (!digitalReg.IsMatch(this.UserPhone.ToString()))
66                     {
67                         return "用戶電話必須爲8-11位的數值!";
68                     }
69                 }
70                 
71                 if (columnName == "UserEmail" && !String.IsNullOrEmpty(this.UserEmail))
72                 {
73                     if (!emailReg.IsMatch(this.UserEmail.ToString()))
74                     {
75                         return "用戶郵箱地址不正確!";
76                     }
77                 }
78 
79                 return null;
80             }
81         }
82 
83     }

繼承IDataErrorInfo接口後,實現方法兩個屬性:Error 屬性用於指示整個對象的錯誤,而索引器用於指示單個屬性級別的錯誤。

每次的屬性值發生變化,則索引器進行一次檢查,看是否有驗證錯誤的信息返回。

二者的工做原理相同:若是返回非 null 或非空字符串,則表示存在驗證錯誤。不然,返回的字符串用於向用戶顯示錯誤。 

結果如圖:

 

 利用 IDataErrorInfo 的好處是它可用於輕鬆地處理交叉耦合屬性。但也具備一個很大的弊端:
索引器的實現一般會致使較大的 switch-case 語句(對象中的每一個屬性名稱都對應於一種狀況),
必須基於字符串進行切換和匹配,並返回指示錯誤的字符串。並且,在對象上設置屬性值以前,不會調用 IDataErrorInfo 的實現。

爲了不出現大量的 switch-case,而且將校驗邏輯進行分離提升代碼複用,將驗證規則和驗證信息獨立化于于每一個模型對象中, 使用DataAnnotations 無疑是最好的的方案 。

因此咱們進行改良一下:

View代碼,跟上面那個同樣:

 1 <GroupBox Header="IDataErrorInfo+ 驗證" Margin="10 20 10 10" DataContext="{Binding Source={StaticResource Locator},Path=BindDataAnnotations}" >
 2                     <StackPanel Orientation="Vertical" Margin="0,20,0,0">
 3                         <StackPanel>
 4                             <Label Content="用戶名" Target="{Binding ElementName=UserName}"/>
 5                             <TextBox Width="150" 
 6                                  Text="{Binding UserName,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}" >
 7                             </TextBox>
 8                         </StackPanel>
 9 
10                         <StackPanel>
11                             <Label Content="性別" Target="{Binding ElementName=RadioGendeMale}"/>
12                             <RadioButton Content="" />
13                             <RadioButton Content="" Margin="8,0,0,0" />
14                         </StackPanel>
15                         <StackPanel>
16                             <Label Content="生日" Target="{Binding ElementName=DateBirth}" />
17                             <DatePicker />
18                         </StackPanel>
19                         <StackPanel>
20                             <Label Content="用戶郵箱" Target="{Binding ElementName=UserEmail}"/>
21                             <TextBox Width="150" Text="{Binding UserEmail, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
22                         </StackPanel>
23                         <StackPanel>
24                             <Label Content="用戶電話" Target="{Binding ElementName=UserPhone}"/>
25                             <TextBox Width="150" Text="{Binding UserPhone,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
26                         </StackPanel>
27 
28                         <Button Content="提交" Margin="100,16,0,0" HorizontalAlignment="Left" Command="{Binding ValidFormCommand}" />
29                     </StackPanel>
30 
31                 </GroupBox>

 VideModel代碼:

  1 using GalaSoft.MvvmLight;
  2 using System;
  3 using System.Collections.Generic;
  4 using System.Linq;
  5 using System.ComponentModel;
  6 using System.ComponentModel.DataAnnotations;
  7 using GalaSoft.MvvmLight.Command;
  8 using System.Windows;
  9 
 10 namespace MVVMLightDemo.ViewModel
 11 {
 12     [MetadataType(typeof(BindDataAnnotationsViewModel))]
 13     public class BindDataAnnotationsViewModel : ViewModelBase, IDataErrorInfo
 14     {
 15 
 16         public BindDataAnnotationsViewModel()
 17         {    
 18 
 19         }
 20 
 21         #region 屬性 
 22         /// <summary>
 23         /// 表單驗證錯誤集合
 24         /// </summary>
 25         private Dictionary<String, String> dataErrors = new Dictionary<String, String>();
 26 
 27 
 28         private String userName;
 29         /// <summary>
 30         /// 用戶名
 31         /// </summary>
 32         [Required]
 33         public String UserName
 34         {
 35             get { return userName; }
 36             set { userName = value; }
 37         }
 38 
 39 
 40 
 41         private String userPhone;
 42         /// <summary>
 43         /// 用戶電話
 44         /// </summary>
 45         [Required]
 46         [RegularExpression(@"^[-]?[1-9]{8,11}\d*$|^[0]{1}$", ErrorMessage = "用戶電話必須爲8-11位的數值.")]
 47         public String UserPhone
 48         {
 49             get { return userPhone; }
 50             set { userPhone = value; }
 51         }
 52 
 53 
 54 
 55         private String userEmail;
 56         /// <summary>
 57         /// 用戶郵件
 58         /// </summary>
 59         [Required]
 60         [StringLength(100,MinimumLength=2)]
 61         [RegularExpression("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@(\\w+\\.)+\\w{2,5})\\s*$", ErrorMessage = "請填寫正確的郵箱地址.")]
 62         public String UserEmail
 63         {
 64             get { return userEmail; }
 65             set { userEmail = value; }
 66         }
 67         #endregion
 68 
 69 
 70         #region 命令
 71 
 72         private RelayCommand validFormCommand;
 73         /// <summary>
 74         /// 驗證表單
 75         /// </summary>
 76         public RelayCommand ValidFormCommand
 77         {
 78             get
 79             {
 80                 if (validFormCommand == null)
 81                     return new RelayCommand(() => ExcuteValidForm());
 82                 return validFormCommand;
 83             }
 84             set { validFormCommand = value; }
 85         }
 86         /// <summary>
 87         /// 驗證表單
 88         /// </summary>
 89         private void ExcuteValidForm()
 90         {
 91             if (dataErrors.Count == 0) MessageBox.Show("驗證經過!");
 92             else MessageBox.Show("驗證失敗!");
 93         }
 94 
 95         #endregion
 96 
 97 
 98         public string this[string columnName]
 99         {
100             get
101             {
102                 ValidationContext vc = new ValidationContext(this, null, null);
103                 vc.MemberName = columnName;
104                 var res = new List<ValidationResult>();
105                 var result = Validator.TryValidateProperty(this.GetType().GetProperty(columnName).GetValue(this, null), vc, res);
106                 if (res.Count > 0)
107                 {
108                     AddDic(dataErrors,vc.MemberName);
109                     return string.Join(Environment.NewLine, res.Select(r => r.ErrorMessage).ToArray());
110                 }
111                 RemoveDic(dataErrors,vc.MemberName);
112                 return null;
113             }
114         }
115 
116         public string Error
117         {
118             get
119             {
120                 return null;
121             }
122         }
123 
124 
125         #region 附屬方法
126 
127         /// <summary>
128         /// 移除字典
129         /// </summary>
130         /// <param name="dics"></param>
131         /// <param name="dicKey"></param>
132         private void RemoveDic(Dictionary<String, String> dics, String dicKey)
133         {
134             dics.Remove(dicKey);
135         }
136 
137         /// <summary>
138         /// 添加字典
139         /// </summary>
140         /// <param name="dics"></param>
141         /// <param name="dicKey"></param>
142         private void AddDic(Dictionary<String, String> dics, String dicKey)
143         {
144             if (!dics.ContainsKey(dicKey)) dics.Add(dicKey, "");
145         }
146         #endregion
147 
148     }
149 }

  DataAnnotations相信不少人很熟悉,可使用數據批註來自定義用戶的模型數據,記得引用 System.ComponentModel.DataAnnotations。

他包含以下幾個驗證類型: 

驗證屬性 說明 
CustomValidationAttribute 使用自定義方法進行驗證。
DataTypeAttribute 指定特定類型的數據,如電子郵件地址或電話號碼。
EnumDataTypeAttribute 確保值存在於枚舉中。
RangeAttribute 指定最小和最大約束。
RegularExpressionAttribute 使用正則表達式來肯定有效的值。
RequiredAttribute 指定必須提供一個值。
StringLengthAttribute 指定最大和最小字符數。
ValidationAttribute 用做驗證屬性的基類。

 

 

 

 

 

 

 

 

 

    這邊咱們使用到了RequiredAttribute、StringLengthAttribute、RegularExpressionAttribute 三項,若是有須要進一步瞭解 DataAnnotations 的能夠參考微軟官網:

https://msdn.microsoft.com/en-us/library/dd901590(VS.95).aspx

用 DataAnnotions 後,Model 的更加簡潔,校驗也更加靈活。能夠疊加組合驗證 , 面對複雜驗證模式的時候,能夠自由的使用正則來驗證。

默認狀況下,框架會提供相應須要反饋的消息內容,固然也能夠自定義錯誤消息內容:ErrorMessage 。

這邊咱們還加了個全局的錯誤集合收集器 :dataErrors,在提交判斷時候判斷是否驗證經過。

這邊咱們進一步封裝索引器,而且經過反射技術讀取當前字段下的屬性進行驗證。

 結果以下:

 

=====================================================================================================================================

===================================================================================================================================== 

封裝ValidateModelBase類:

上面的驗證比較合理了,不過相對於開發人員仍是太累贅了,開發人員關心的是Model的DataAnnotations的配置,而不是關心在這個ViewModel要如何作驗證處理,因此咱們進一步抽象。

編寫一個ValidateModelBase,把須要處理的工做都放在裏面。須要驗證屬性的Model去繼承這個基類。以下:

 

ValidateModelBase 類,請注意標紅部分:

 1  public class ValidateModelBase : ObservableObject, IDataErrorInfo
 2     {
 3         public ValidateModelBase()
 4         {
 5               
 6         }
 7 
 8         #region 屬性 
 9         /// <summary>
10         /// 表當驗證錯誤集合
11         /// </summary>
12         private Dictionary<String, String> dataErrors = new Dictionary<String, String>();
13 
14         /// <summary>
15         /// 是否驗證經過
16         /// </summary>
17         public Boolean IsValidated
18         {
19             get
20             {
21                 if (dataErrors != null && dataErrors.Count > 0)
22                 {
23                     return false;
24                 }
25                 return true;
26             }
27         }
28         #endregion
29 
30         public string this[string columnName]
31         {
32             get
33             {
34                 ValidationContext vc = new ValidationContext(this, null, null);
35                 vc.MemberName = columnName;
36                 var res = new List<ValidationResult>();
37                 var result = Validator.TryValidateProperty(this.GetType().GetProperty(columnName).GetValue(this, null), vc, res);
38                 if (res.Count > 0)
39                 {
40                     AddDic(dataErrors, vc.MemberName);
41                     return string.Join(Environment.NewLine, res.Select(r => r.ErrorMessage).ToArray());
42                 }
43                 RemoveDic(dataErrors, vc.MemberName);
44                 return null;
45             }
46         }
47 
48         public string Error
49         {
50             get
51             {
52                 return null;
53             }
54         }
55 
56 
57         #region 附屬方法
58 
59         /// <summary>
60         /// 移除字典
61         /// </summary>
62         /// <param name="dics"></param>
63         /// <param name="dicKey"></param>
64         private void RemoveDic(Dictionary<String, String> dics, String dicKey)
65         {
66             dics.Remove(dicKey);
67         }
68 
69         /// <summary>
70         /// 添加字典
71         /// </summary>
72         /// <param name="dics"></param>
73         /// <param name="dicKey"></param>
74         private void AddDic(Dictionary<String, String> dics, String dicKey)
75         {
76             if (!dics.ContainsKey(dicKey)) dics.Add(dicKey, "");
77         }
78         #endregion
79     }

 驗證的模型類:繼承 ValidateModelBase

 1 [MetadataType(typeof(BindDataAnnotationsViewModel))]
 2     public class ValidateUserInfo : ValidateModelBase
 3     {
 4         #region 屬性 
 5         private String userName;
 6         /// <summary>
 7         /// 用戶名
 8         /// </summary>
 9         [Required]
10         public String UserName
11         {
12             get { return userName; }
13             set { userName = value; RaisePropertyChanged(() => UserName); }
14         }
15 
16 
17 
18         private String userPhone;
19         /// <summary>
20         /// 用戶電話
21         /// </summary>
22         [Required]
23         [RegularExpression(@"^[-]?[1-9]{8,11}\d*$|^[0]{1}$", ErrorMessage = "用戶電話必須爲8-11位的數值.")]
24         public String UserPhone
25         {
26             get { return userPhone; }
27             set { userPhone = value; RaisePropertyChanged(() => UserPhone); }
28         }
29 
30 
31 
32         private String userEmail;
33         /// <summary>
34         /// 用戶郵件
35         /// </summary>
36         [Required]
37         [StringLength(100, MinimumLength = 2)]
38         [RegularExpression("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@(\\w+\\.)+\\w{2,5})\\s*$", ErrorMessage = "請填寫正確的郵箱地址.")]
39         public String UserEmail
40         {
41             get { return userEmail; }
42             set { userEmail = value; RaisePropertyChanged(() => UserEmail);  }
43         }
44         #endregion
45     }

  ViewModel代碼以下:

 1   public class PackagedValidateViewModel:ViewModelBase
 2     {
 3         public PackagedValidateViewModel()
 4         {
 5             ValidateUI = new Model.ValidateUserInfo();
 6         }
 7 
 8         #region 全局屬性
 9         private ValidateUserInfo validateUI;
10         /// <summary>
11         /// 用戶信息
12         /// </summary>
13         public ValidateUserInfo ValidateUI
14         {
15             get
16             {
17                 return validateUI;
18             }
19 
20             set
21             {
22                 validateUI = value;
23                 RaisePropertyChanged(()=>ValidateUI);
24             }
25         }             
26         #endregion
27 
28         #region 全局命令
29         private RelayCommand submitCmd;
30         public RelayCommand SubmitCmd
31         {
32             get
33             {
34                 if(submitCmd == null) return new RelayCommand(() => ExcuteValidForm());
35                 return submitCmd;
36             }
37 
38             set
39             {
40                 submitCmd = value;
41             }
42         }
43         #endregion
44 
45         #region 附屬方法
46         /// <summary>
47         /// 驗證表單
48         /// </summary>
49         private void ExcuteValidForm()
50         {
51             if (ValidateUI.IsValidated) MessageBox.Show("驗證經過!");
52             else MessageBox.Show("驗證失敗!");
53         }
54         #endregion
55     }

 結果以下:

 

 示例代碼下載

 

轉載請標明出處,謝謝

相關文章
相關標籤/搜索