常见的表单验证机制有如下几种:
验证交互的关系模式如图:
下面详细描述下这三种验证模式
1、Exception 验证:
2、ValidationRule 验证:
3、IDataErrorInfo 验证:
表单验证是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绑定在表单验证上的应用的资料请关注软件开发网其它相关文章!