溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

MVVMLight怎么綁定在表單驗(yàn)證上

發(fā)布時(shí)間:2022-02-07 10:43:46 來源:億速云 閱讀:146 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(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)證機(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ò)誤。

驗(yàn)證交互的關(guān)系模式如圖:

MVVMLight怎么綁定在表單驗(yàn)證上

我們?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á)到的效果如下:

MVVMLight怎么綁定在表單驗(yàn)證上

下面詳細(xì)描述下這三種驗(yàn)證模式  

1、Exception 驗(yàn)證:

正如說明中描述的那樣,在具有綁定關(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é)果如圖:

MVVMLight怎么綁定在表單驗(yàn)證上

將驗(yàn)證失敗的信息直接拋出來,這無疑是最簡單粗暴的,實(shí)現(xiàn)也很簡單,但是只是針對(duì)單一源屬性進(jìn)行驗(yàn)證, 復(fù)用性不高。

而且在組合驗(yàn)證(比如同時(shí)需要驗(yàn)證非空和其他規(guī)則)情況下,會(huì)導(dǎo)致Model中寫過重過臃腫的代碼。

2、ValidationRule 驗(yàn)證:

通過繼承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é)果如下:

MVVMLight怎么綁定在表單驗(yàn)證上

 說明:相對(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、IDataErrorInfo 驗(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é)果如圖:

MVVMLight怎么綁定在表單驗(yàn)證上

 利用 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é)果如下:

MVVMLight怎么綁定在表單驗(yàn)證上

封裝ValidateModelBase類:

上面的驗(yàn)證比較合理了,不過相對(duì)于開發(fā)人員還是太累贅了,開發(fā)人員關(guān)心的是Model的DataAnnotations的配置,而不是關(guān)心在這個(gè)ViewModel要如何做驗(yàn)證處理,所以我們進(jìn)一步抽象。

編寫一個(gè)ValidateModelBase,把需要處理的工作都放在里面。需要驗(yàn)證屬性的Model去繼承這個(gè)基類。如下:

MVVMLight怎么綁定在表單驗(yàn)證上

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)證上

讀到這里,這篇“MVVMLight怎么綁定在表單驗(yàn)證上”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(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)容。

AI