- A+
WPF中使用ValidationRule自定义验证规则
本文主要是展示在 WPF 中使用 ValidationRule 自定义验证规则,同时展示两种错误响应方式。一种是通过 Behavior 传递到 ViewModel 中,然后进行错误信息响应;一种是直接在 View 中遍历当前也的所有错误元素,在页面中通过事件统一响应。
1、自定义验证规则类
这里自定义两个验证规则类,分别用于验证 “用户名”输入不可为空、“邮箱”输入值需满足格式要求。
两个类需要继承 ValidationRule 类。ValidationRule 是抽象类,需要具体实现 Validate 方法。代码如下:
NotEmptyValidationRule.cs
using System.Globalization; using System.Windows.Controls; namespace MyValidationRuleDemo.MyValidationRules { public class NotEmptyValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { return string.IsNullOrWhiteSpace((value ?? "").ToString()) ? new ValidationResult(false, "不能为空") : new ValidationResult(true, null); } } }
EmailValidationRule.cs
using System.Globalization; using System.Text.RegularExpressions; using System.Windows.Controls; namespace MyValidationRuleDemo.MyValidationRules { public class EmailValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { Regex emailRegex = new Regex("^\s*([A-Za-z0-9_-]+(\.\w+)*@(\w+\.)+\w{2,5})\s*$"); string str = (value ?? "").ToString(); if (!string.IsNullOrWhiteSpace(str)) { if (!emailRegex.IsMatch(str)) { return new ValidationResult(false, "邮箱地址错误!"); } } return new ValidationResult(true, null); } } }
2、在前端页面中添加验证
在前端页面中需要进行以下操作:
-
添加自定义的 ValidationRule 所有的命名空间;
xmlns:valRules="clr-namespace:MyValidationRuleDemo.MyValidationRules"
-
在需要验证的控件上的 Binding 上对应的自定义验证规则类;
<Binding.ValidationRules> <valRules:EmailValidationRule /> </Binding.ValidationRules>
具体代码如下:
<Window x:Class="MyValidationRuleDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:MyValidationRuleDemo" xmlns:valRules="clr-namespace:MyValidationRuleDemo.MyValidationRules" Title="MainWindow" Width="800" Height="400" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal"> <Label Content="用户名:" Margin="0,0,10,0" FontSize="20" /> <TextBox Width="200" Height="30"> <TextBox.Text> <Binding Path="UserName" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <valRules:NotEmptyValidationRule /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> </StackPanel> <StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal"> <Label Content="邮箱:" Margin="0,0,10,0" FontSize="20" /> <TextBox Width="200" Height="30"> <TextBox.Text> <Binding Path="UserEmail" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <valRules:EmailValidationRule /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> </StackPanel> <Button Grid.Row="2" Content="提交" Width="200" Height="30" Margin="0,20,0,0" /> </Grid> </Window>
前端页面绑定的验证参数具体如下:
using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; namespace MyValidationRuleDemo.ViewModel { public class MainViewModel : ViewModelBase { public MainViewModel() { } private string userName; /// <summary> /// 用户名 /// </summary> public string UserName { get { return userName; } set { userName = value; RaisePropertyChanged(); } } private string userEmail; /// <summary> /// 用户邮件 /// </summary> public string UserEmail { get { return userEmail; } set { userEmail = value; RaisePropertyChanged(); } } } }
此时,自定义的验证规则已经生效。当页面输入不符合规则时,会默认的红框进行标记。这是 WPF 中默认的效果。效果如下图:
3、使用 Behavior 自定义响应效果
上面虽然已经在页面上有了基本的错误响应效果,但是效果过于单一。这里我们在这里使用 Behavior 监听 Validation.Error 事件,并将错误信息传递到 ViewModel 中进行统一进行错误提醒。同时,在 MVMM 架构中将错误信息传递到 ViewModel 中进行统一处理,在有需要的时候也有利于业务逻辑处理。
3.1、实现步骤
进行以上操作需要进行以下步骤:
-
开启验证错误的通知属性 NotifyOnValidationError="True" 。这样就可以产生 Validation.Error 事件。
<TextBox Width="200" Height="30"> <TextBox.Text> <Binding Path="UserEmail" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <valRules:EmailValidationRule /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox>
-
通过自定义的 ValidationExceptionBehavior 继承于 Behavior,用于监听 Validation.Error 的错误事件。
protected override void OnAttached() { //添加 Validation.Error 事件监听 this.AssociatedObject.AddHandler(Validation.ErrorEvent, new EventHandler<ValidationErrorEventArgs>(OnValidationError)); }
-
View 中添加 Behavior;
<i:Interaction.Behaviors> <local:ValidationExceptionBehavior /> </i:Interaction.Behaviors>
-
在 ValidationExceptionBehavior 中通过 AssociatedObject 的DataContext 获取到关联当前View的ViewModel。并将错误提示统一收集到 ViewModel 的 ErrorList 。
private void OnValidationError(Object sender, ValidationErrorEventArgs e) { MainViewModel mainModel = null; if (AssociatedObject.DataContext is MainViewModel) { mainModel = this.AssociatedObject.DataContext as MainViewModel; } if (mainModel == null) return; //OriginalSource 触发事件的元素 var element = e.OriginalSource as UIElement; if (element == null) return; //ValidationErrorEventAction.Added 表示新产生的行为 if (e.Action == ValidationErrorEventAction.Added) { mainModel.ErrorList.Add(e.Error.ErrorContent.ToString()); } else if (e.Action == ValidationErrorEventAction.Removed) //ValidationErrorEventAction.Removed 该行为被移除,即代表验证通过 { mainModel.ErrorList.Remove(e.Error.ErrorContent.ToString()); } }
-
在 View 中按钮绑定的 Command 中统一处理 ErrorList;
public RelayCommand SaveCommand { get; set; } public MainViewModel() { SaveCommand = new RelayCommand(() => { StringBuilder sb = new StringBuilder(); foreach (var error in ErrorList) { sb.Append(error + "rn"); } MessageBox.Show(sb.ToString()); }); }
-
开启 ValidationRule 的属性 ValidatesOnTargetUpdated="True",否则在加载页面后,文本框中未输入值则不会进行验证。
<Binding.ValidationRules> <valRules:EmailValidationRule ValidatesOnTargetUpdated="True" /> </Binding.ValidationRules>
3.2、具体代码
完整代码如下:
ValidationExceptionBehavior.cs
using MyValidationRuleDemo.ViewModel; using System; using System.Windows; using System.Windows.Controls; using System.Windows.Interactivity; namespace MyValidationRuleDemo { public class ValidationExceptionBehavior : Behavior<FrameworkElement> { protected override void OnAttached() { //添加 Validation.Error 事件监听 this.AssociatedObject.AddHandler(Validation.ErrorEvent, new EventHandler<ValidationErrorEventArgs>(OnValidationError)); } private void OnValidationError(Object sender, ValidationErrorEventArgs e) { MainViewModel mainModel = null; if (AssociatedObject.DataContext is MainViewModel) { mainModel = this.AssociatedObject.DataContext as MainViewModel; } if (mainModel == null) return; //OriginalSource 触发事件的元素 var element = e.OriginalSource as UIElement; if (element == null) return; //ValidationErrorEventAction.Added 表示新产生的行为 if (e.Action == ValidationErrorEventAction.Added) { mainModel.ErrorList.Add(e.Error.ErrorContent.ToString()); } else if (e.Action == ValidationErrorEventAction.Removed) //ValidationErrorEventAction.Removed 该行为被移除,即代表验证通过 { mainModel.ErrorList.Remove(e.Error.ErrorContent.ToString()); } } protected override void OnDetaching() { //移除 Validation.Error 事件监听 this.AssociatedObject.RemoveHandler(Validation.ErrorEvent, new EventHandler<ValidationErrorEventArgs>(OnValidationError)); } } }
MainView.xaml
<Window x:Class="MyValidationRuleDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:MyValidationRuleDemo" xmlns:valRules="clr-namespace:MyValidationRuleDemo.MyValidationRules" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" Title="MainWindow" Width="800" Height="400" mc:Ignorable="d"> <i:Interaction.Behaviors> <local:ValidationExceptionBehavior /> </i:Interaction.Behaviors> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal"> <Label Content="用户名:" Margin="0,0,10,0" FontSize="20" /> <TextBox Width="200" Height="30"> <TextBox.Text> <Binding Path="UserName" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <valRules:NotEmptyValidationRule ValidatesOnTargetUpdated="True" /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> </StackPanel> <StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal"> <Label Content="邮箱:" Margin="0,0,10,0" FontSize="20" /> <TextBox Width="200" Height="30"> <TextBox.Text> <Binding Path="UserEmail" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <valRules:EmailValidationRule ValidatesOnTargetUpdated="True" /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> </StackPanel> <Button Grid.Row="2" Content="提交" Command="{Binding SaveCommand}" Width="200" Height="30" Margin="0,20,0,0" /> </Grid> </Window>
MainViewModel.cs
using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; using System.Collections.ObjectModel; using System.Text; using System.Windows; namespace MyValidationRuleDemo.ViewModel { public class MainViewModel : ViewModelBase { public MainViewModel() { SaveCommand = new RelayCommand(() => { StringBuilder sb = new StringBuilder(); foreach (var error in ErrorList) { sb.Append(error + "rn"); } MessageBox.Show(sb.ToString()); }); } public RelayCommand SaveCommand { get; set; } private string userName; /// <summary> /// 用户名 /// </summary> public string UserName { get { return userName; } set { userName = value; RaisePropertyChanged(); } } private string userEmail; /// <summary> /// 用户邮件 /// </summary> public string UserEmail { get { return userEmail; } set { userEmail = value; RaisePropertyChanged(); } } private ObservableCollection<string> errorList = new ObservableCollection<string>(); /// <summary> /// 错误提示 /// </summary> public ObservableCollection<string> ErrorList { get { return errorList; } set { errorList = value; RaisePropertyChanged(); } } } }
3.3、效果展示
4、直接在页面中进行错误响应
错误响应也可以直接在 View 中通过事件直接处理。但是这样处理就比较麻烦,在获取错误列表时,我们可能需要编写代码,从而在 View 中所有控件中查询有错误的控件。
4.1、单个错误响应
Error 事件是使用冒泡策略的路出事件,我们在 Binding 了 ValidationRule 的控件的父容器上关联处理程序,即可为多个控件处理 Error 事件。
在 View.xaml 中添加如下代码:
<Grid Validation.Error="Grid_Error"> <!--这里面是具体的页面内容--> </Grid>
关联的事件处理程序如下:
private void Grid_Error(object sender, ValidationErrorEventArgs e) { if (e.Action == ValidationErrorEventAction.Added) { MessageBox.Show(e.Error.ErrorContent.ToString()); } }
4.2、获取错误列表
循环遍历整个 View 中的元素,并将存在的错误信息收集到一个字符串中,然后点击按钮时将该字符串中的信息展示出来。程序代码如下:
private void Button_Click(object sender, RoutedEventArgs e) { string message; if (HasErrors(out message)) { MessageBox.Show(message); } } private bool HasErrors(out string message) { StringBuilder sb = new StringBuilder(); GetError(sb, this.grid); message = sb.ToString(); return !string.IsNullOrWhiteSpace(message); } private void GetError(StringBuilder sb, DependencyObject obj) { foreach (object child in LogicalTreeHelper.GetChildren(obj)) { StackPanel element = child as StackPanel; if (element == null) continue; foreach (var ele in element.Children) { TextBox textBox = ele as TextBox; if (textBox == null) continue; if (Validation.GetHasError(textBox)) { foreach (ValidationError error in Validation.GetErrors(textBox)) { sb.Append(error.ErrorContent.ToString() + "rn"); } GetError(sb, element); } } } }