在WPF中,有许多数据校验方法,ValidationRule、IDataErrorInfo和DataAnnotations是三种最常用的方式,本文对其用法做一个简单介绍。
在实际项目开发中,推荐使用IDataErrorInfo + DataAnnotations的方案!
方法1:ValidationRule
这种方法适合在XAML中指定数据检验规则,但需要在后台代码中编写检验规则(继承自ValidationRule
的子类)。
MainWindow.xaml
如果在代码中修改属性,默认不做数据校验,添加ValidatesOnTargetUpdated="True"
可以开启。
数据非法时默认不会激发事件,给Binding添加NotifyOnValidationError="True"
可以激发事件,事件在TextBox中指定Validation.Error="TextBox_Error"
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <Window x:Class ="WpfApp1.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:WpfApp1" mc:Ignorable ="d" Title ="WPF数据校验" Width ="300" Height ="250" > <StackPanel Orientation ="Vertical" > <TextBox Margin ="5" Validation.Error ="TextBox_Error" > <TextBox.Text > <Binding Path ="Score" UpdateSourceTrigger ="PropertyChanged" NotifyOnValidationError ="True" > <Binding.ValidationRules > <local:ScoreValidateRule ValidatesOnTargetUpdated ="True" /> </Binding.ValidationRules > </Binding > </TextBox.Text > </TextBox > <Button Margin ="5" x:Name ="btnShowAge" Click ="btnShowAge_Click" > 显示分数</Button > <Button Margin ="5" x:Name ="btnChangeAge" Click ="btnChangeAge_Click" > 修改分数</Button > </StackPanel > </Window >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 using System.Windows;namespace WpfApp1 { public partial class MainWindow : Window { private ViewModel vm = null ; public MainWindow () { InitializeComponent(); vm = new ViewModel() { Name = "小明" , Score = 25 }; DataContext = vm; } private void btnShowAge_Click (object sender, RoutedEventArgs e ) { MessageBox.Show(vm.Score.ToString()); } private void btnChangeAge_Click (object sender, RoutedEventArgs e ) { vm.Score = 120 ; } private void TextBox_Error (object sender, System.Windows.Controls.ValidationErrorEventArgs e ) { MessageBox.Show("Score非法!" ); } } }
ViewModel.cs
[AddINotifyPropertyChangedInterface]
来自NuGet包PropertyChanged.Fody
,可以为属性自动添加变化通知,非常实用!
实现自定义校验规则时,需要实现其Validate()
方法,参数value
表示待校验的数据,传回是否成功的标识符和失败时的错误信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using PropertyChanged;using System;using System.Globalization;using System.Windows.Controls;namespace WpfApp1 { [AddINotifyPropertyChangedInterface ] internal class ViewModel { public String Name { get ; set ; } public int Score { get ; set ; } } public class ScoreValidateRule : ValidationRule { public override ValidationResult Validate (object value , CultureInfo cultureInfo ) { int score = 0 ; if (int .TryParse(value .ToString(), out score)) if (score < 0 || score > 100 ) return new ValidationResult(false , "分数非法" ); return new ValidationResult(true , null ); } } }
方法2:IDataErrorInfo
MainWindow.xaml
当输入数据非法时,如果要显示红色边框,需要给Binding设置ValidatesOnDataErrors=True
。
同方法1一样,设置NotifyOnValidationError=True
可以激发数据校验失败事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <Window x:Class ="WpfApp1.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:WpfApp1" mc:Ignorable ="d" Title ="WPF数据校验" Width ="300" Height ="250" > <StackPanel Orientation ="Vertical" > <TextBox Margin ="5" Validation.Error ="TextBox_Error" Text ="{Binding Score, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" > </TextBox > <Button Margin ="5" x:Name ="btnShowAge" Click ="btnShowAge_Click" > 显示分数</Button > <Button Margin ="5" x:Name ="btnChangeAge" Click ="btnChangeAge_Click" > 修改分数</Button > </StackPanel > </Window >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System.Windows;namespace WpfApp1 { public partial class MainWindow : Window { private ViewModel vm = null ; public MainWindow () { InitializeComponent(); vm = new ViewModel() { Name = "小明" , Score = 25 }; DataContext = vm; } private void btnShowAge_Click (object sender, RoutedEventArgs e ) { MessageBox.Show(vm.Score.ToString()); } private void btnChangeAge_Click (object sender, RoutedEventArgs e ) { vm.Score = 120 ; } private void TextBox_Error (object sender, System.Windows.Controls.ValidationErrorEventArgs e ) { MessageBox.Show(vm["Score" ]); } } }
ViewModel.cs
当一个ViewModel需要进行数据校验时,它必须实现IDataErrorInfo
接口,该接口包含一个属性Error
和一个[]
符号重载。
在[]
符号重载代码中,需要对各属性进行合法性校验,随着属性数量的增多,该代码将非常繁琐,这也是该方法的一个弊端,方法3可以解决该问题。
如果上述[]
符号重载代码最终返回null
,则表示该属性值校验成功;否则校验失败,会在控件边缘显示红色边框。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using PropertyChanged;using System;using System.ComponentModel;namespace WpfApp1 { [AddINotifyPropertyChangedInterface ] public class ViewModel : IDataErrorInfo { public String Name { get ; set ; } public int Score { get ; set ; } #region 数据校验 public string Error => "" ; public string this [string columnName] { get { if (columnName == "Score" ) { if (Score < 0 || Score > 100 ) { return "Score必须在1~100之间!" ; } } return string .Empty; } } #endregion } }
方法3:DataAnnotations
该方法最大的特点是:在ViewModel的各属性上通过标注添加各种校验规则。
通过Validator.TryValidateObject()
方法进行属性的数据校验,可以返回错误信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 using PropertyChanged;using System;using System.Collections.Generic;using System.ComponentModel;using System.ComponentModel.DataAnnotations;using System.Linq;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { Person person = new Person() { Name = "" , Email = "aaaa" , Age = 222 , Phone = "1111" , }; var result = ValidatetionHelper.IsValid(person); if (!result.IsVaild) { foreach (ErrorMember errormember in result.ErrorMembers) { Console.WriteLine($"{errormember.ErrorMemberName} :{errormember.ErrorMessage} " ); } } Console.ReadLine(); } } [AddINotifyPropertyChangedInterface ] public class Person { [Required(ErrorMessage = "{0}必须填写,不能为空" ) ] [DisplayName("姓名" ) ] public string Name { get ; set ; } [Required(ErrorMessage = "{0}必须填写,不能为空" ) ] [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}" , ErrorMessage = "{0}邮件格式不正确" ) ] public string Email { get ; set ; } [Required(ErrorMessage = "{0}必须填写,不能为空" ) ] [Range(18, 100, ErrorMessage = "{0}年满18岁小于100岁方可申请" ) ] public int Age { get ; set ; } [Required(ErrorMessage = "{0}手机号不能为空" ) ] [StringLength(11, MinimumLength = 11, ErrorMessage = "{0}请输入正确的手机号" ) ] public string Phone { get ; set ; } } }
以下是进行ViewModel数据校验的工具类,核心是使用Validator.TryValidateObject()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class ValidatetionHelper { public static ValidResult IsValid (object value ) { ValidResult result = new ValidResult(); try { var validationContext = new ValidationContext(value ); var results = new List<ValidationResult>(); var isValid = Validator.TryValidateObject(value , validationContext, results, true ); if (!isValid) { result.IsVaild = false ; result.ErrorMembers = new List<ErrorMember>(); foreach (var item in results) { result.ErrorMembers.Add(new ErrorMember() { ErrorMessage = item.ErrorMessage, ErrorMemberName = item.MemberNames.FirstOrDefault() }); } } else { result.IsVaild = true ; } } catch (Exception ex) { result.IsVaild = false ; result.ErrorMembers = new List<ErrorMember>(); result.ErrorMembers.Add(new ErrorMember() { ErrorMessage = ex.Message, ErrorMemberName = "Internal error" }); } return result; } } public class ValidResult { public List<ErrorMember> ErrorMembers { get ; set ; } public bool IsVaild { get ; set ; } } public class ErrorMember { public string ErrorMessage { get ; set ; } public string ErrorMemberName { get ; set ; } }
方法4:IDataErrorInfo + DataAnnotations
在本方法中,MainWindow.xaml
和MainWindow.xaml.cs
同方法3的代码。
相比方法2,本方法的唯一差别体现在ViewModel.cs
的[]
符号重载中,这里使用DataAnnotations的数据校验方式,替换了方法2中的手动编写数据校验逻辑!领会这一点很关键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 using PropertyChanged;using System;using System.ComponentModel.DataAnnotations;namespace WpfApp1 { [AddINotifyPropertyChangedInterface ] public class ViewModel : PropertyValidateModel { public String Name { get ; set ; } [Range(0, 100, ErrorMessage = "Score必须在1~100之间!" ) ] public int Score { get ; set ; } } public abstract class PropertyValidateModel : IDataErrorInfo { public string Error { get { return null ; } } public string this [string columnName] { get { var validationResults = new List<ValidationResult>(); if (Validator.TryValidateProperty( GetType().GetProperty(columnName).GetValue(this ), new ValidationContext(this ) { MemberName = columnName }, validationResults)) return null ; return validationResults.First().ErrorMessage; } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ViewModel : ObservableValidator { private string name = "小明" ; [MaxLength(10, ErrorMessage = "名字长度不能超过10" ) ] public string Name { get => name; set => SetProperty(ref name, value , true ); } private RelayCommand<object > _checkDataCommand; public RelayCommand<object > CheckDataCommand => _checkDataCommand ?? (_checkDataCommand = new RelayCommand<object >(CheckData)); public void CheckData (object parameter ) { ValidateAllProperties(); if (HasErrors) { string res = string .Join(Environment.NewLine, GetErrors()); MessageBox.Show(res); } }
ValidateAllProperties()
可以校验所有属性,如果有属性校验失败,HasErrors
会被置为false
,通过GetErrors()
方法可以拿到所有的错误信息。
如果希望属性发生变化时就校验,可以向SetProperty()
的第三个参数传true
。