您好,登錄后才能下訂單哦!
本文小編為大家詳細(xì)介紹“MVVMLight怎么綁定在表單驗(yàn)證上”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“MVVMLight怎么綁定在表單驗(yàn)證上”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。
表單驗(yàn)證是MVVM體系中的重要一塊。而綁定除了推動(dòng) Model-View-ViewModel (MVVM) 模式松散耦合 邏輯、數(shù)據(jù) 和 UI定義 的關(guān)系之外,還為業(yè)務(wù)數(shù)據(jù)驗(yàn)證方案提供強(qiáng)大而靈活的支持。
WPF 中的數(shù)據(jù)綁定機(jī)制包括多個(gè)選項(xiàng),可用于在創(chuàng)建可編輯視圖時(shí)校驗(yàn)輸入數(shù)據(jù)的有效性。
驗(yàn)證類型 | 說明 |
Exception 驗(yàn)證 | 通過在某個(gè) Binding 對(duì)象上設(shè)置 ValidatesOnExceptions 屬性,如果源對(duì)象屬性設(shè)置已修改的值的過程中引發(fā)異常,則拋出錯(cuò)誤并為該 Binding 設(shè)置驗(yàn)證錯(cuò)誤。 |
ValidationRule 驗(yàn)證 | Binding 類具有一個(gè)用于提供 ValidationRule 派生類實(shí)例的集合的屬性。這些 ValidationRules 需要覆蓋某個(gè) Validate 方法,該方法由 Binding 在每次綁定控件中的數(shù)據(jù)發(fā)生更改時(shí)進(jìn)行調(diào)用。 如果 Validate 方法返回?zé)o效的 ValidationResult 對(duì)象,則將為該 Binding 設(shè)置驗(yàn)證錯(cuò)誤。 |
IDataErrorInfo 驗(yàn)證 | 通過在綁定數(shù)據(jù)源對(duì)象上實(shí)現(xiàn) IDataErrorInfo 接口并在 Binding 對(duì)象上設(shè)置 ValidatesOnDataErrors 屬性,Binding 將調(diào)用從綁定數(shù)據(jù)源對(duì)象公開的 IDataErrorInfo API。 如果從這些屬性調(diào)用返回非 null 或非空字符串,則將為該 Binding 設(shè)置驗(yàn)證錯(cuò)誤。 |
我們?cè)谑褂?WPF 中的數(shù)據(jù)綁定來呈現(xiàn)業(yè)務(wù)數(shù)據(jù)時(shí),通常會(huì)使用 Binding 對(duì)象在目標(biāo)控件的單個(gè)屬性與數(shù)據(jù)源對(duì)象屬性之間提供數(shù)據(jù)管道。
如果要使得綁定驗(yàn)證有效,首先需要進(jìn)行 TwoWay 數(shù)據(jù)綁定。這表明,除了從源屬性流向目標(biāo)屬性以進(jìn)行顯示的數(shù)據(jù)之外,編輯過的數(shù)據(jù)也會(huì)從目標(biāo)流向源。
這就是偉大的雙向數(shù)據(jù)綁定的精髓,所以在MVVM中做數(shù)據(jù)校驗(yàn),會(huì)容易的多。
當(dāng) TwoWay 數(shù)據(jù)綁定中輸入或修改數(shù)據(jù)時(shí),將啟動(dòng)以下工作流:
1、 | 用戶通過鍵盤、鼠標(biāo)、手寫板或者其他輸入設(shè)備來輸入或修改數(shù)據(jù),從而改變綁定的目標(biāo)信息 |
2、 | 設(shè)置源屬性值。 |
3、 | 觸發(fā) Binding.SourceUpdated 事件。 |
4、 | 如果數(shù)據(jù)源屬性上的 setter 引發(fā)異常,則異常會(huì)由 Binding 捕獲,并可用于指示驗(yàn)證錯(cuò)誤。 |
5、 | 如果實(shí)現(xiàn)了 IDataErrorInfo 接口,則會(huì)對(duì)數(shù)據(jù)源對(duì)象調(diào)用該接口的方法獲得該屬性的錯(cuò)誤信息。 |
6、 | 向用戶呈現(xiàn)驗(yàn)證錯(cuò)誤指示,并觸發(fā) Validation.Error 附加事件。 |
綁定目標(biāo)向綁定源發(fā)送數(shù)據(jù)更新的請(qǐng)求,而綁定源則對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證,并根據(jù)不同的驗(yàn)證機(jī)制進(jìn)行反饋。
下面我們用實(shí)例來對(duì)比下這幾種驗(yàn)證機(jī)制,在此之前,我們先做一個(gè)事情,就是寫一個(gè)錯(cuò)誤觸發(fā)的樣式,來保證錯(cuò)誤觸發(fā)的時(shí)候直接清晰的向用戶反饋出去。
我們新建一個(gè)資源字典文件,命名為TextBox.xaml,下面這個(gè)是資源字典文件的內(nèi)容,目標(biāo)類型是TextBoxBase基礎(chǔ)的控件,如TextBox和RichTextBox.
代碼比較簡單,注意標(biāo)紅的內(nèi)容,設(shè)計(jì)一個(gè)紅底白字的提示框,當(dāng)源屬性觸發(fā)錯(cuò)誤驗(yàn)證的時(shí)候,把驗(yàn)證對(duì)象集合中的錯(cuò)誤內(nèi)容顯示出來。
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style x:Key="{x:Type TextBoxBase}" TargetType="{x:Type TextBoxBase}" BasedOn="{x:Null}"> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Padding" Value="2,1,1,1"/> <Setter Property="AllowDrop" Value="true"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="SelectionBrush" Value="{DynamicResource Accent}" /> <Setter Property="Validation.ErrorTemplate"> <Setter.Value> <ControlTemplate> <StackPanel Orientation="Horizontal"> <Border BorderThickness="1" BorderBrush="#FFdc000c" VerticalAlignment="Top"> <Grid> <AdornedElementPlaceholder x:Name="adorner" Margin="-1"/> </Grid> </Border> <Border x:Name="errorBorder" Background="#FFdc000c" Margin="8,0,0,0" Opacity="0" CornerRadius="0" IsHitTestVisible="False" MinHeight="24" > <TextBlock Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" Foreground="White" Margin="8,2,8,3" TextWrapping="Wrap" VerticalAlignment="Center"/> </Border> </StackPanel> <ControlTemplate.Triggers> <DataTrigger Value="True"> <DataTrigger.Binding> <Binding ElementName="adorner" Path="AdornedElement.IsKeyboardFocused" /> </DataTrigger.Binding> <DataTrigger.EnterActions> <BeginStoryboard x:Name="fadeInStoryboard"> <Storyboard> <DoubleAnimation Duration="00:00:00.15" Storyboard.TargetName="errorBorder" Storyboard.TargetProperty="Opacity" To="1"/> </Storyboard> </BeginStoryboard> </DataTrigger.EnterActions> <DataTrigger.ExitActions> <StopStoryboard BeginStoryboardName="fadeInStoryboard"/> <BeginStoryboard x:Name="fadeOutStoryBoard"> <Storyboard> <DoubleAnimation Duration="00:00:00" Storyboard.TargetName="errorBorder" Storyboard.TargetProperty="Opacity" To="0"/> </Storyboard> </BeginStoryboard> </DataTrigger.ExitActions> </DataTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBoxBase}"> <Border x:Name="Bd" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"> <ScrollViewer x:Name="PART_ContentHost" RenderOptions.ClearTypeHint="Enabled" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Foreground" Value="{DynamicResource InputTextDisabled}"/> </Trigger> <Trigger Property="IsReadOnly" Value="true"> <Setter Property="Foreground" Value="{DynamicResource InputTextDisabled}"/> </Trigger> <Trigger Property="IsFocused" Value="true"> <Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource Accent}" /> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsReadOnly" Value="False"/> <Condition Property="IsEnabled" Value="True"/> <Condition Property="IsMouseOver" Value="True"/> </MultiTrigger.Conditions> <Setter Property="Background" Value="{DynamicResource InputBackgroundHover}"/> <Setter Property="BorderBrush" Value="{DynamicResource InputBorderHover}"/> <Setter Property="Foreground" Value="{DynamicResource InputTextHover}"/> </MultiTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style BasedOn="{StaticResource {x:Type TextBoxBase}}" TargetType="{x:Type TextBox}"> </Style> <Style BasedOn="{StaticResource {x:Type TextBoxBase}}" TargetType="{x:Type RichTextBox}"> </Style> </ResourceDictionary>
然后在App.Xaml中全局注冊(cè)到整個(gè)應(yīng)用中。
<Application x:Class="MVVMLightDemo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="View/BindingFormView.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:MVVMLightDemo.ViewModel" xmlns:Common="clr-namespace:MVVMLightDemo.Common"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/MVVMLightDemo;component/Assets/TextBox.xaml" /> </ResourceDictionary.MergedDictionaries> <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" /> <Common:IntegerToSex x:Key="IntegerToSex" d:IsDataSource="True" /> </ResourceDictionary> </Application.Resources> </Application>
達(dá)到的效果如下:
正如說明中描述的那樣,在具有綁定關(guān)系的源字段模型上做驗(yàn)證異常的引發(fā)并拋出,在View中的Xaml對(duì)象上設(shè)置 ExceptionValidationRule 屬性,響應(yīng)捕獲異常并顯示。
View代碼:
<GroupBox Header="Exception 驗(yàn)證" Margin="10 10 10 10" DataContext="{Binding Source={StaticResource Locator},Path=ValidateException}" > <StackPanel x:Name="ExceptionPanel" Orientation="Vertical" Margin="0,10,0,0" > <StackPanel> <Label Content="用戶名" Target="{Binding ElementName=UserNameEx}"/> <TextBox x:Name="UserNameEx" Width="150"> <TextBox.Text> <Binding Path="UserNameEx" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <ExceptionValidationRule></ExceptionValidationRule> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> </StackPanel> </StackPanel> </GroupBox>
ViewModel代碼:
/// <summary> /// Exception 驗(yàn)證 /// </summary> public class ValidateExceptionViewModel:ViewModelBase { public ValidateExceptionViewModel() { } private String userNameEx; /// <summary> /// 用戶名稱(不為空) /// </summary> public string UserNameEx { get { return userNameEx; } set { userNameEx = value; RaisePropertyChanged(() => UserNameEx); if (string.IsNullOrEmpty(value)) { throw new ApplicationException("該字段不能為空!"); } } }
結(jié)果如圖:
將驗(yàn)證失敗的信息直接拋出來,這無疑是最簡單粗暴的,實(shí)現(xiàn)也很簡單,但是只是針對(duì)單一源屬性進(jìn)行驗(yàn)證, 復(fù)用性不高。
而且在組合驗(yàn)證(比如同時(shí)需要驗(yàn)證非空和其他規(guī)則)情況下,會(huì)導(dǎo)致Model中寫過重過臃腫的代碼。
通過繼承ValidationRule 抽象類,并重寫他的Validate方法來擴(kuò)展編寫我們需要的驗(yàn)證類。該驗(yàn)證類可以直接使用在我們需要驗(yàn)證的屬性。
View代碼:
<GroupBox Header="ValidationRule 驗(yàn)證" Margin="10 20 10 10" DataContext="{Binding Source={StaticResource Locator},Path=ValidationRule}" > <StackPanel x:Name="ValidationRulePanel" Orientation="Vertical" Margin="0,20,0,0"> <StackPanel> <Label Content="用戶名" Target="{Binding ElementName=UserName}"/> <TextBox Width="150" > <TextBox.Text> <Binding Path="UserName" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <app:RequiredRule /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> </StackPanel> <StackPanel> <Label Content="用戶郵箱" Target="{Binding ElementName=UserEmail}"/> <TextBox Width="150"> <TextBox.Text> <Binding Path="UserEmail" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <app:EmailRule /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> </StackPanel> </StackPanel> </GroupBox>
重寫兩個(gè)ValidationRule,代碼如下:
public class RequiredRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { if (value == null) return new ValidationResult(false, "該字段不能為空值!"); if (string.IsNullOrEmpty(value.ToString())) return new ValidationResult(false, "該字段不能為空字符串!"); return new ValidationResult(true, null); } } public class EmailRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { Regex emailReg = new Regex("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@(\\w+\\.)+\\w{2,5})\\s*$"); if (!String.IsNullOrEmpty(value.ToString())) { if (!emailReg.IsMatch(value.ToString())) { return new ValidationResult(false, "郵箱地址不準(zhǔn)確!"); } } return new ValidationResult(true, null); }
創(chuàng)建了兩個(gè)類,一個(gè)用于驗(yàn)證是否為空,一個(gè)用于驗(yàn)證是否符合郵箱地址標(biāo)準(zhǔn)格式。
ViewModel代碼:
public class ValidationRuleViewModel:ViewModelBase { public ValidationRuleViewModel() { } #region 屬性 private String userName; /// <summary> /// 用戶名 /// </summary> public String UserName { get { return userName; } set { userName = value; RaisePropertyChanged(()=>UserName); } } private String userEmail; /// <summary> /// 用戶郵件 /// </summary> public String UserEmail { get { return userEmail; } set { userEmail = value;RaisePropertyChanged(()=>UserName); } } #endregion
結(jié)果如下:
說明:相對(duì)來說,這種方式是比較不錯(cuò)的,獨(dú)立性、復(fù)用性都很好,從松散耦合角度來說也是比較恰當(dāng)?shù)摹?/p>
可以預(yù)先寫好一系列的驗(yàn)證規(guī)則類,視圖編碼人員可以根據(jù)需求直接使用這些驗(yàn)證規(guī)則,服務(wù)端無需額外的處理。
但是仍然有缺點(diǎn),擴(kuò)展性差,如果需要個(gè)性化反饋消息也需要額外擴(kuò)展。不符合日益豐富的前端驗(yàn)證需求。
3.1、在綁定數(shù)據(jù)源對(duì)象上實(shí)現(xiàn) IDataErrorInfo 接口
3.2、在 Binding 對(duì)象上設(shè)置 ValidatesOnDataErrors 屬性
Binding 將調(diào)用從綁定數(shù)據(jù)源對(duì)象公開的 IDataErrorInfo API。如果從這些屬性調(diào)用返回非 null 或非空字符串,則將為該 Binding 設(shè)置驗(yàn)證錯(cuò)誤。
View代碼:
<GroupBox Header="IDataErrorInfo 驗(yàn)證" Margin="10 20 10 10" DataContext="{Binding Source={StaticResource Locator},Path=BindingForm}" > <StackPanel x:Name="Form" Orientation="Vertical" Margin="0,20,0,0"> <StackPanel> <Label Content="用戶名" Target="{Binding ElementName=UserName}"/> <TextBox Width="150" Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" > </TextBox> </StackPanel> <StackPanel> <Label Content="性別" Target="{Binding ElementName=RadioGendeMale}"/> <RadioButton Content="男" /> <RadioButton Content="女" Margin="8,0,0,0" /> </StackPanel> <StackPanel> <Label Content="生日" Target="{Binding ElementName=DateBirth}" /> <DatePicker x:Name="DateBirth" /> </StackPanel> <StackPanel> <Label Content="用戶郵箱" Target="{Binding ElementName=UserEmail}"/> <TextBox Width="150" Text="{Binding UserEmail, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" /> </StackPanel> <StackPanel> <Label Content="用戶電話" Target="{Binding ElementName=UserPhone}"/> <TextBox Width="150" Text="{Binding UserPhone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" /> </StackPanel> </StackPanel> </GroupBox>
ViewModel代碼:
public class BindingFormViewModel :ViewModelBase, IDataErrorInfo { public BindingFormViewModel() { } #region 屬性 private String userName; /// <summary> /// 用戶名 /// </summary> public String UserName { get { return userName; } set { userName = value; } } private String userPhone; /// <summary> /// 用戶電話 /// </summary> public String UserPhone { get { return userPhone; } set { userPhone = value; } } private String userEmail; /// <summary> /// 用戶郵件 /// </summary> public String UserEmail { get { return userEmail; } set { userEmail = value; } } #endregion public String Error { get { return null; } } public String this[string columnName] { get { Regex digitalReg = new Regex(@"^[-]?[1-9]{8,11}\d*$|^[0]{1}$"); Regex emailReg = new Regex("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@(\\w+\\.)+\\w{2,5})\\s*$"); if (columnName == "UserName" && String.IsNullOrEmpty(this.UserName)) { return "用戶名不能為空"; } if (columnName == "UserPhone" && !String.IsNullOrEmpty(this.UserPhone)) { if (!digitalReg.IsMatch(this.UserPhone.ToString())) { return "用戶電話必須為8-11位的數(shù)值!"; } } if (columnName == "UserEmail" && !String.IsNullOrEmpty(this.UserEmail)) { if (!emailReg.IsMatch(this.UserEmail.ToString())) { return "用戶郵箱地址不正確!"; } } return null; } } }
繼承IDataErrorInfo接口后,實(shí)現(xiàn)方法兩個(gè)屬性:Error 屬性用于指示整個(gè)對(duì)象的錯(cuò)誤,而索引器用于指示單個(gè)屬性級(jí)別的錯(cuò)誤。
每次的屬性值發(fā)生變化,則索引器進(jìn)行一次檢查,看是否有驗(yàn)證錯(cuò)誤的信息返回。
兩者的工作原理相同:如果返回非 null 或非空字符串,則表示存在驗(yàn)證錯(cuò)誤。否則,返回的字符串用于向用戶顯示錯(cuò)誤。
結(jié)果如圖:
利用 IDataErrorInfo 的好處是它可用于輕松地處理交叉耦合屬性。但也具有一個(gè)很大的弊端:
索引器的實(shí)現(xiàn)通常會(huì)導(dǎo)致較大的 switch-case 語句(對(duì)象中的每個(gè)屬性名稱都對(duì)應(yīng)于一種情況),
必須基于字符串進(jìn)行切換和匹配,并返回指示錯(cuò)誤的字符串。而且,在對(duì)象上設(shè)置屬性值之前,不會(huì)調(diào)用 IDataErrorInfo 的實(shí)現(xiàn)。
為了避免出現(xiàn)大量的 switch-case,并且將校驗(yàn)邏輯進(jìn)行分離提高代碼復(fù)用,將驗(yàn)證規(guī)則和驗(yàn)證信息獨(dú)立化于于每個(gè)模型對(duì)象中, 使用DataAnnotations 無疑是最好的的方案 。
所以我們進(jìn)行改良一下:
View代碼,跟上面那個(gè)一樣:
<GroupBox Header="IDataErrorInfo+ 驗(yàn)證" Margin="10 20 10 10" DataContext="{Binding Source={StaticResource Locator},Path=BindDataAnnotations}" > <StackPanel Orientation="Vertical" Margin="0,20,0,0"> <StackPanel> <Label Content="用戶名" Target="{Binding ElementName=UserName}"/> <TextBox Width="150" Text="{Binding UserName,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}" > </TextBox> </StackPanel> <StackPanel> <Label Content="性別" Target="{Binding ElementName=RadioGendeMale}"/> <RadioButton Content="男" /> <RadioButton Content="女" Margin="8,0,0,0" /> </StackPanel> <StackPanel> <Label Content="生日" Target="{Binding ElementName=DateBirth}" /> <DatePicker /> </StackPanel> <StackPanel> <Label Content="用戶郵箱" Target="{Binding ElementName=UserEmail}"/> <TextBox Width="150" Text="{Binding UserEmail, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" /> </StackPanel> <StackPanel> <Label Content="用戶電話" Target="{Binding ElementName=UserPhone}"/> <TextBox Width="150" Text="{Binding UserPhone,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" /> </StackPanel> <Button Content="提交" Margin="100,16,0,0" HorizontalAlignment="Left" Command="{Binding ValidFormCommand}" /> </StackPanel> </GroupBox>
VideModel代碼:
using GalaSoft.MvvmLight; using System; using System.Collections.Generic; using System.Linq; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using GalaSoft.MvvmLight.Command; using System.Windows; namespace MVVMLightDemo.ViewModel { [MetadataType(typeof(BindDataAnnotationsViewModel))] public class BindDataAnnotationsViewModel : ViewModelBase, IDataErrorInfo { public BindDataAnnotationsViewModel() { } #region 屬性 /// <summary> /// 表單驗(yàn)證錯(cuò)誤集合 /// </summary> private Dictionary<String, String> dataErrors = new Dictionary<String, String>(); private String userName; /// <summary> /// 用戶名 /// </summary> [Required] public String UserName { get { return userName; } set { userName = value; } } private String userPhone; /// <summary> /// 用戶電話 /// </summary> [Required] [RegularExpression(@"^[-]?[1-9]{8,11}\d*$|^[0]{1}$", ErrorMessage = "用戶電話必須為8-11位的數(shù)值.")] public String UserPhone { get { return userPhone; } set { userPhone = value; } } private String userEmail; /// <summary> /// 用戶郵件 /// </summary> [Required] [StringLength(100,MinimumLength=2)] [RegularExpression("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@(\\w+\\.)+\\w{2,5})\\s*$", ErrorMessage = "請(qǐng)?zhí)顚懻_的郵箱地址.")] public String UserEmail { get { return userEmail; } set { userEmail = value; } } #endregion #region 命令 private RelayCommand validFormCommand; /// <summary> /// 驗(yàn)證表單 /// </summary> public RelayCommand ValidFormCommand { get { if (validFormCommand == null) return new RelayCommand(() => ExcuteValidForm()); return validFormCommand; } set { validFormCommand = value; } } /// <summary> /// 驗(yàn)證表單 /// </summary> private void ExcuteValidForm() { if (dataErrors.Count == 0) MessageBox.Show("驗(yàn)證通過!"); else MessageBox.Show("驗(yàn)證失?。?quot;); } #endregion public string this[string columnName] { get { ValidationContext vc = new ValidationContext(this, null, null); vc.MemberName = columnName; var res = new List<ValidationResult>(); var result = Validator.TryValidateProperty(this.GetType().GetProperty(columnName).GetValue(this, null), vc, res); if (res.Count > 0) { AddDic(dataErrors,vc.MemberName); return string.Join(Environment.NewLine, res.Select(r => r.ErrorMessage).ToArray()); } RemoveDic(dataErrors,vc.MemberName); return null; } } public string Error { get { return null; } } #region 附屬方法 /// <summary> /// 移除字典 /// </summary> /// <param name="dics"></param> /// <param name="dicKey"></param> private void RemoveDic(Dictionary<String, String> dics, String dicKey) { dics.Remove(dicKey); } /// <summary> /// 添加字典 /// </summary> /// <param name="dics"></param> /// <param name="dicKey"></param> private void AddDic(Dictionary<String, String> dics, String dicKey) { if (!dics.ContainsKey(dicKey)) dics.Add(dicKey, ""); } #endregion } }
DataAnnotations相信很多人很熟悉,可以使用數(shù)據(jù)批注來自定義用戶的模型數(shù)據(jù),記得引用 System.ComponentModel.DataAnnotations。
他包含如下幾個(gè)驗(yàn)證類型:
驗(yàn)證屬性 | 說明 |
CustomValidationAttribute | 使用自定義方法進(jìn)行驗(yàn)證。 |
DataTypeAttribute | 指定特定類型的數(shù)據(jù),如電子郵件地址或電話號(hào)碼。 |
EnumDataTypeAttribute | 確保值存在于枚舉中。 |
RangeAttribute | 指定最小和最大約束。 |
RegularExpressionAttribute | 使用正則表達(dá)式來確定有效的值。 |
RequiredAttribute | 指定必須提供一個(gè)值。 |
StringLengthAttribute | 指定最大和最小字符數(shù)。 |
ValidationAttribute | 用作驗(yàn)證屬性的基類。 |
這邊我們使用到了RequiredAttribute、StringLengthAttribute、RegularExpressionAttribute 三項(xiàng),如果有需要進(jìn)一步了解 DataAnnotations 的可以參考微軟官網(wǎng):
https://msdn.microsoft.com/en-us/library/dd901590(VS.95).aspx
用 DataAnnotions 后,Model 的更加簡潔,校驗(yàn)也更加靈活??梢辕B加組合驗(yàn)證 , 面對(duì)復(fù)雜驗(yàn)證模式的時(shí)候,可以自由的使用正則來驗(yàn)證。
默認(rèn)情況下,框架會(huì)提供相應(yīng)需要反饋的消息內(nèi)容,當(dāng)然也可以自定義錯(cuò)誤消息內(nèi)容:ErrorMessage 。
這邊我們還加了個(gè)全局的錯(cuò)誤集合收集器 :dataErrors,在提交判斷時(shí)候判斷是否驗(yàn)證通過。
這邊我們進(jìn)一步封裝索引器,并且通過反射技術(shù)讀取當(dāng)前字段下的屬性進(jìn)行驗(yàn)證。
結(jié)果如下:
封裝ValidateModelBase類:
上面的驗(yàn)證比較合理了,不過相對(duì)于開發(fā)人員還是太累贅了,開發(fā)人員關(guān)心的是Model的DataAnnotations的配置,而不是關(guān)心在這個(gè)ViewModel要如何做驗(yàn)證處理,所以我們進(jìn)一步抽象。
編寫一個(gè)ValidateModelBase,把需要處理的工作都放在里面。需要驗(yàn)證屬性的Model去繼承這個(gè)基類。如下:
ValidateModelBase 類,請(qǐng)注意標(biāo)紅部分:
public class ValidateModelBase : ObservableObject, IDataErrorInfo { public ValidateModelBase() { } #region 屬性 /// <summary> /// 表當(dāng)驗(yàn)證錯(cuò)誤集合 /// </summary> private Dictionary<String, String> dataErrors = new Dictionary<String, String>(); /// <summary> /// 是否驗(yàn)證通過 /// </summary> public Boolean IsValidated { get { if (dataErrors != null && dataErrors.Count > 0) { return false; } return true; } } #endregion public string this[string columnName] { get { ValidationContext vc = new ValidationContext(this, null, null); vc.MemberName = columnName; var res = new List<ValidationResult>(); var result = Validator.TryValidateProperty(this.GetType().GetProperty(columnName).GetValue(this, null), vc, res); if (res.Count > 0) { AddDic(dataErrors, vc.MemberName); return string.Join(Environment.NewLine, res.Select(r => r.ErrorMessage).ToArray()); } RemoveDic(dataErrors, vc.MemberName); return null; } } public string Error { get { return null; } } #region 附屬方法 /// <summary> /// 移除字典 /// </summary> /// <param name="dics"></param> /// <param name="dicKey"></param> private void RemoveDic(Dictionary<String, String> dics, String dicKey) { dics.Remove(dicKey); } /// <summary> /// 添加字典 /// </summary> /// <param name="dics"></param> /// <param name="dicKey"></param> private void AddDic(Dictionary<String, String> dics, String dicKey) { if (!dics.ContainsKey(dicKey)) dics.Add(dicKey, ""); } #endregion }
驗(yàn)證的模型類:繼承 ValidateModelBase
[MetadataType(typeof(BindDataAnnotationsViewModel))] public class ValidateUserInfo : ValidateModelBase { #region 屬性 private String userName; /// <summary> /// 用戶名 /// </summary> [Required] public String UserName { get { return userName; } set { userName = value; RaisePropertyChanged(() => UserName); } } private String userPhone; /// <summary> /// 用戶電話 /// </summary> [Required] [RegularExpression(@"^[-]?[1-9]{8,11}\d*$|^[0]{1}$", ErrorMessage = "用戶電話必須為8-11位的數(shù)值.")] public String UserPhone { get { return userPhone; } set { userPhone = value; RaisePropertyChanged(() => UserPhone); } } private String userEmail; /// <summary> /// 用戶郵件 /// </summary> [Required] [StringLength(100, MinimumLength = 2)] [RegularExpression("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@(\\w+\\.)+\\w{2,5})\\s*$", ErrorMessage = "請(qǐng)?zhí)顚懻_的郵箱地址.")] public String UserEmail { get { return userEmail; } set { userEmail = value; RaisePropertyChanged(() => UserEmail); } } #endregion }
ViewModel代碼如下:
public class PackagedValidateViewModel:ViewModelBase { public PackagedValidateViewModel() { ValidateUI = new Model.ValidateUserInfo(); } #region 全局屬性 private ValidateUserInfo validateUI; /// <summary> /// 用戶信息 /// </summary> public ValidateUserInfo ValidateUI { get { return validateUI; } set { validateUI = value; RaisePropertyChanged(()=>ValidateUI); } } #endregion #region 全局命令 private RelayCommand submitCmd; public RelayCommand SubmitCmd { get { if(submitCmd == null) return new RelayCommand(() => ExcuteValidForm()); return submitCmd; } set { submitCmd = value; } } #endregion #region 附屬方法 /// <summary> /// 驗(yàn)證表單 /// </summary> private void ExcuteValidForm() { if (ValidateUI.IsValidated) MessageBox.Show("驗(yàn)證通過!"); else MessageBox.Show("驗(yàn)證失敗!"); } #endregion }
結(jié)果如下:
讀到這里,這篇“MVVMLight怎么綁定在表單驗(yàn)證上”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。