表單驗證是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 }
結果以下:
轉載請標明出處,謝謝