返回顶部
首页 > 资讯 > 移动开发 >MVVMLight项目之绑定在表单验证上的应用示例分析
  • 536
分享到

MVVMLight项目之绑定在表单验证上的应用示例分析

2024-04-02 19:04:59 536人浏览 八月长安
摘要

目录常见的表单验证机制有如下几种:验证交互的关系模式如图:下面详细描述下这三种验证模式  1、Exception 验证:2、ValidationRule 验证:3

表单验证是MVVM体系中的重要一块。而绑定除了推动 Model-View-ViewModel (MVVM) 模式松散耦合 逻辑、数据 和 UI定义 的关系之外,还为业务数据验证方案提供强大而灵活的支持。

WPF 中的数据绑定机制包括多个选项,可用于在创建可编辑视图时校验输入数据的有效性。

常见的表单验证机制有如下几种:

验证类型说明
Exception 验证通过在某个 Binding 对象上设置 ValidatesOnExceptions 属性,如果源对象属性设置已修改的值的过程中引发异常,则抛出错误并为该 Binding 设置验证错误。
ValidationRule 验证

Binding 类具有一个用于提供 ValidationRule 派生类实例的集合的属性。这些 ValidationRules 需要覆盖某个 Validate 方法,该方法由 Binding 在每次绑定控件中的数据发生更改时进行调用。

如果 Validate 方法返回无效的 ValidationResult 对象,则将为该 Binding 设置验证错误。

IDataErrorInfo 验证

通过在绑定数据源对象上实现 IDataErrorInfo 接口并在 Binding 对象上设置 ValidatesOnDataErrors 属性,Binding 将调用从绑定数据源对象公开的 IDataErrorInfo api

如果从这些属性调用返回非 null 或非空字符串,则将为该 Binding 设置验证错误。

验证交互的关系模式如图:

我们在使用 WPF 中的数据绑定来呈现业务数据时,通常会使用 Binding 对象在目标控件的单个属性与数据源对象属性之间提供数据管道。

如果要使得绑定验证有效,首先需要进行 TwoWay 数据绑定。这表明,除了从源属性流向目标属性以进行显示的数据之外,编辑过的数据也会从目标流向源。

这就是伟大的双向数据绑定的精髓,所以在MVVM中做数据校验,会容易的多。

当 TwoWay 数据绑定中输入或修改数据时,将启动以下工作流:

1、 用户通过键盘、鼠标、手写板或者其他输入设备来输入或修改数据,从而改变绑定的目标信息
2、设置源属性值。
3、触发 Binding.SourceUpdated 事件。
4、如果数据源属性上的 setter 引发异常,则异常会由 Binding 捕获,并可用于指示验证错误。
5、如果实现了 IDataErrorInfo 接口,则会对数据源对象调用该接口的方法获得该属性的错误信息。
6、向用户呈现验证错误指示,并触发 Validation.Error 附加事件。

绑定目标向绑定源发送数据更新的请求,而绑定源则对数据进行验证,并根据不同的验证机制进行反馈。 

下面我们用实例来对比下这几种验证机制,在此之前,我们先做一个事情,就是写一个错误触发的样式,来保证错误触发的时候直接清晰的向用户反馈出去。

我们新建一个资源字典文件,命名为TextBox.xaml,下面这个是资源字典文件的内容,目标类型是TextBoxBase基础的控件,如TextBox和RichTextBox.

代码比较简单,注意标红的内容,设计一个红底白字的提示框,当源属性触发错误验证的时候,把验证对象集合中的错误内容显示出来。

<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中全局注册到整个应用中。

 <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>

 达到的效果如下:

下面详细描述下这三种验证模式  

1、Exception 验证:

正如说明中描述的那样,在具有绑定关系的源字段模型上做验证异常的引发并抛出,在View中的Xaml对象上设置 ExceptionValidationRule 属性,响应捕获异常并显示。

View代码:

                <GroupBox Header="Exception 验证" 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 验证
    /// </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("该字段不能为空!");
                }
            }
        }
    

  结果如图:

将验证失败的信息直接抛出来,这无疑是最简单粗暴的,实现也很简单,但是只是针对单一源属性进行验证, 复用性不高。

而且在组合验证(比如同时需要验证非空和其他规则)情况下,会导致Model中写过重过臃肿的代码。

2、ValidationRule 验证:

通过继承ValidationRule 抽象类,并重写他的Validate方法来扩展编写我们需要的验证类。该验证类可以直接使用在我们需要验证的属性。

View代码:

 <GroupBox Header="ValidationRule 验证"  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>

 重写两个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, "邮箱地址不准确!");
             }
         }
         return new ValidationResult(true, null);
     }

 创建了两个类,一个用于验证是否为空,一个用于验证是否符合邮箱地址标准格式。 

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

 结果如下:

 说明:相对来说,这种方式是比较不错的,独立性、复用性都很好,从松散耦合角度来说也是比较恰当的。

可以预先写好一系列的验证规则类,视图编码人员可以根据需求直接使用这些验证规则,服务端无需额外的处理。

但是仍然有缺点,扩展性差,如果需要个性化反馈消息也需要额外扩展。不符合日益丰富的前端验证需求。

3、IDataErrorInfo 验证:

3.1、在绑定数据源对象上实现 IDataErrorInfo 接口

3.2、在 Binding 对象上设置 ValidatesOnDataErrors 属性

Binding 将调用从绑定数据源对象公开的 IDataErrorInfo API。如果从这些属性调用返回非 null 或非空字符串,则将为该 Binding 设置验证错误。

View代码:

      <GroupBox Header="IDataErrorInfo 验证" 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位的数值!";
                   }
               }
               if (columnName == "UserEmail" && !String.IsNullOrEmpty(this.UserEmail))
               {
                   if (!emailReg.IsMatch(this.UserEmail.ToString()))
                   {
                       return "用户邮箱地址不正确!";
                   }
               }
               return null;
           }
       }
   }

继承IDataErrorInfo接口后,实现方法两个属性:Error 属性用于指示整个对象的错误,而索引器用于指示单个属性级别的错误。

每次的属性值发生变化,则索引器进行一次检查,看是否有验证错误的信息返回。

两者的工作原理相同:如果返回非 null 或非空字符串,则表示存在验证错误。否则,返回的字符串用于向用户显示错误。 

结果如图:

 利用 IDataErrorInfo 的好处是它可用于轻松地处理交叉耦合属性。但也具有一个很大的弊端:
索引器的实现通常会导致较大的 switch-case 语句(对象中的每个属性名称都对应于一种情况),
必须基于字符串进行切换和匹配,并返回指示错误的字符串。而且,在对象上设置属性值之前,不会调用 IDataErrorInfo 的实现。

为了避免出现大量的 switch-case,并且将校验逻辑进行分离提高代码复用,将验证规则和验证信息独立化于于每个模型对象中, 使用DataAnnotations 无疑是最好的的方案 。

所以我们进行改良一下:

View代码,跟上面那个一样:

<GroupBox Header="IDataErrorInfo+ 验证" 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>
        /// 表单验证错误集合
        /// </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位的数值.")]
        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 = "请填写正确的邮箱地址.")]
        public String UserEmail
        {
            get { return userEmail; }
            set { userEmail = value; }
        }
        #endregion
        #region 命令
        private RelayCommand validFormCommand;
        /// <summary>
        /// 验证表单
        /// </summary>
        public RelayCommand ValidFormCommand
        {
            get
            {
                if (validFormCommand == null)
                    return new RelayCommand(() => ExcuteValidForm());
                return validFormCommand;
            }
            set { validFormCommand = value; }
        }
        /// <summary>
        /// 验证表单
        /// </summary>
        private void ExcuteValidForm()
        {
            if (dataErrors.Count == 0) MessageBox.Show("验证通过!");
            else MessageBox.Show("验证失败!");
        }
        #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相信很多人很熟悉,可以使用数据批注来自定义用户的模型数据,记得引用 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 类,请注意标红部分:

 public class ValidateModelBase : ObservableObject, IDataErrorInfo
     {
         public ValidateModelBase()
         {
               
         }
         #region 属性 
         /// <summary>
         /// 表当验证错误集合
         /// </summary>
         private Dictionary<String, String> dataErrors = new Dictionary<String, String>();
 
         /// <summary>
         /// 是否验证通过
         /// </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
     }

 验证的模型类:继承 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位的数值.")]
        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 = "请填写正确的邮箱地址.")]
        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>
       /// 验证表单
       /// </summary>
       private void ExcuteValidForm()
       {
           if (ValidateUI.IsValidated) MessageBox.Show("验证通过!");
           else MessageBox.Show("验证失败!");
       }
       #endregion
   }

 结果如下:

以上就是MVVMLight项目之绑定在表单验证上的应用示例分析的详细内容,更多关于MVVMLight绑定在表单验证上的应用的资料请关注编程网其它相关文章!

--结束END--

本文标题: MVVMLight项目之绑定在表单验证上的应用示例分析

本文链接: https://lsjlt.com/news/138128.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • MVVMLight项目之绑定在表单验证上的应用示例分析
    目录常见的表单验证机制有如下几种:验证交互的关系模式如图:下面详细描述下这三种验证模式  1、Exception 验证:2、ValidationRule 验证:3...
    99+
    2024-04-02
  • MVVMLight怎么绑定在表单验证上
    本文小编为大家详细介绍“MVVMLight怎么绑定在表单验证上”,内容详细,步骤清晰,细节处理妥当,希望这篇“MVVMLight怎么绑定在表单验证上”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。表单验证是MVVM...
    99+
    2023-06-29
  • MVVMLight项目的绑定及各种使用场景示例分析
    目录一、绑定:1、元素绑定:2、非元素类型绑定: 2.1 Source属性:2.2 RelativeSource 属性:2.3 DataContext 属性:二、绑定的各种...
    99+
    2024-04-02
  • MVC遇上bootstrap后ajax表单验证的示例分析
    这篇文章主要介绍了MVC遇上bootstrap后ajax表单验证的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。使用bootstra...
    99+
    2024-04-02
  • jQuery表单验证之密码确认的示例分析
    这篇文章给大家分享的是有关jQuery表单验证之密码确认的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。代码:<!DOCTYPE html> <...
    99+
    2024-04-02
  • JS中表单提交验证的示例分析
    这篇文章将为大家详细讲解有关JS中表单提交验证的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。在进行表单提交时,需要对输入框和文本域等的value的合理性进行验证...
    99+
    2024-04-02
  • HTML5表单属性和验证方式的示例分析
    这篇文章主要介绍了HTML5表单属性和验证方式的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。 在HTML5的众多变化中,表单数据...
    99+
    2024-04-02
  • layui中lay-verify form表单自定义验证规则的示例分析
    这篇文章主要介绍layui中lay-verify form表单自定义验证规则的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!虽然layui的官方文档已经是写的比较详细,但是初...
    99+
    2024-04-02
  • VB.NET数据绑定应用技巧的示例分析
    这篇文章主要介绍了VB.NET数据绑定应用技巧的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。VB.NET编程语言的推出为开发者又增加了一种语言的选择。他们可以利用这...
    99+
    2023-06-17
  • vue自动路由之单页面项目的示例分析
    这篇文章给大家分享的是有关vue自动路由之单页面项目的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。这是一个什么项目?答:这是一个单页面的vue.js项目,主要为了实现在...
    99+
    2024-04-02
  • vue组件表单数据回显验证及提交的示例分析
    这篇文章主要介绍vue组件表单数据回显验证及提交的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!最近项目需要到vue开发单页面,所以就研究一下表单数据的回显,验证及提交如何用...
    99+
    2024-04-02
  • javascript中表单正则应用的示例分析
    这篇文章主要为大家展示了“javascript中表单正则应用的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“javascript中表单正则应用的示例分析...
    99+
    2024-04-02
  • Java 自定义注解在登录验证的应用示例
    目录java注解@Retention@Target登录注解 @Logined注解需求在拦截器上获取 @Logined 注解总结java注解 从 JDK 5开始,Java 增加了注解...
    99+
    2024-04-02
  • HTML5应用之文件上传的示例分析
    这篇文章主要介绍HTML5应用之文件上传的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!用HTML5上传文件在HTML5标准中,XMLHttpRequest对象被重新定义,被...
    99+
    2024-04-02
  • JQuery中form表单提交前验证单选框是否选中、删除记录时验证的示例分析
    这篇文章主要为大家展示了“JQuery中form表单提交前验证单选框是否选中、删除记录时验证的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“JQuery...
    99+
    2024-04-02
  • javascript之分片上传,断点续传的实际项目的示例分析
    小编给大家分享一下javascript之分片上传,断点续传的实际项目的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!总所...
    99+
    2024-04-02
  • ng-alain动态表单SF表单项设置必填和正则校验的示例分析
    这篇文章将为大家详细讲解有关ng-alain动态表单SF表单项设置必填和正则校验的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。在使用动态表单时对表单项进行非空校...
    99+
    2024-04-02
  • vue列表单项展开收缩功能之this.$refs的示例分析
    这篇文章主要为大家展示了“vue列表单项展开收缩功能之this.$refs的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“vue列表单项展开收缩功能之t...
    99+
    2024-04-02
  • Oracle中EBS工具选项之关闭其他表单修改的示例分析
    这篇文章主要介绍了Oracle中EBS工具选项之关闭其他表单修改的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。Oracle EBS...
    99+
    2024-04-02
  • css栅格系统在项目中灵活运用的示例分析
    这篇文章主要介绍了css栅格系统在项目中灵活运用的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。前言css栅格通常捆绑在各种框架中,但有时你需要自己去定制一个css栅...
    99+
    2023-06-08
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作