前台xaml样式:
<Style TargetType="{x:Type TextBox}"> <Style.Triggers> <Trigger Property="Validation.HasError" Value="True"> <Setter Property="BorderBrush" Value="Red"/> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/> </Trigger> </Style.Triggers> <Setter Property="Validation.ErrorTemplate"> <Setter.Value> <ControlTemplate> <StackPanel> <AdornedElementPlaceholder /> <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="Width" Value="120" ></Setter> <Setter Property="Height" Value="30" ></Setter> </Style>
法一重写ValidationRule:
public class MarginValidationRule : ValidationRule { double minMargin; double maxMargin; public double MinMargin { get { return this.minMargin; } set { this.minMargin = value; } } public double MaxMargin { get { return this.maxMargin; } set { this.maxMargin = value; } } public override System.Windows.Controls.ValidationResult Validate(object value, CultureInfo cultureInfo) { double margin; // Is a number? if (!double.TryParse((string)value, out margin)) { return new System.Windows.Controls.ValidationResult(false, "Not a number."); } // Is in range? if ((margin < this.minMargin) || (margin > this.maxMargin)) { string msg = string.Format("Margin must be between {0} and {1}.", this.minMargin, this.maxMargin); return new System.Windows.Controls.ValidationResult(false, msg); } // Number is valid return new System.Windows.Controls.ValidationResult(true, null); } }
//MVVM viewModel
public class MarginViewModel : INotifyPropertyChanged { int m_left; public int Left { get { return m_left; } set { m_left = value; OnPropertyChanged("Left"); } } private ICommand _testRelayCommand; public ICommand TestRelayCommand { get { if (_testRelayCommand == null) { _testRelayCommand = new RelayCommand(new Action<object>(OnTestRelayCommandExecuted), new Predicate<object>(OnTestRelayCommandCanExecute)); } return _testRelayCommand; } } public void OnTestRelayCommandExecuted(object para) { MessageBox.Show("OK"); } public bool OnTestRelayCommandCanExecute(object para) { DependencyObject node = para as DependencyObject; return IsValid(node); } // Validate all dependency objects in a window bool IsValid(DependencyObject node) { // Check if dependency object was passed if (node != null) { // Check if dependency object is valid. // NOTE: Validation.GetHasError works for controls that have validation rules attached bool isValid = !Validation.GetHasError(node); if (!isValid) { // If the dependency object is invalid, and it can receive the focus, // set the focus if (node is IInputElement) Keyboard.Focus((IInputElement)node); return false; } } // If this dependency object is valid, check all child dependency objects foreach (object subnode in LogicalTreeHelper.GetChildren(node)) { if (subnode is DependencyObject) { // If a child dependency object is invalid, return false immediately, // otherwise keep checking if (IsValid((DependencyObject)subnode) == false) return false; } } // All dependency objects are valid return true; } public event PropertyChangedEventHandler PropertyChanged; void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
前台xaml:
<StackPanel> <GroupBox Header="自定义ValidationRules"> <StackPanel x:Name="stack1" Margin="10"> <TextBox Name="leftMarginTextBox" Grid.Column="1" Grid.Row="0"> <TextBox.Text> <Binding Path="Left" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:MarginValidationRule MinMargin="0" MaxMargin="10" /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <Button Height="20" Width="200" Margin="0,20,0,0" Command="{Binding TestRelayCommand}" CommandParameter="{Binding ElementName=stack1}" >保存</Button> </StackPanel> </GroupBox> <Button Height="30" Margin="0,10,0,0" Width="80" Content="应用" Click="Button_Click"/> </StackPanel>
法二 重写实现接口IDataError
public class Person : NotifyBase, IDataErrorInfo { private string _name; public string Name { get { return _name; } set { if (_name != value) { _name = value; RaisePropertyChanged("Name"); } } } private int _age; public int Age { get { return _age; } set { if (_age != value) { _age = value; RaisePropertyChanged("Age"); } } } public string Error { get { return ""; } } public string this[string columnName] { get { //为了避免出现大量的 switch-case,并且将校验逻辑进行分离提高代码复用,于是 DataAnnotations 华丽登场。法3 //改造下上面的 Person 类,加上 [Range] ValidationAttribute:(需要添加 System.ComponentModel.DataAnnotations.dll) switch (columnName) { case "Age": if (_age < 18) { return "年龄必须在18岁以上。"; } break; case "Name": if (_name != null && _name.Length > 10) { return "名字长度最长10"; } break; } return string.Empty; } } }
public class NotifyBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; internal virtual void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }
//ViewModel public class PersonViewModel : NotifyBase { Person m_person = new Person(); public Person Person { get { return m_person; } set { m_person = value; RaisePropertyChanged("Person"); } } private ICommand _testRelayCommand2; public ICommand TestRelayCommand2 { get { if (_testRelayCommand2 == null) { _testRelayCommand2 = new RelayCommand(new Action<object>(OnTestRelayCommandExecuted2), new Predicate<object>(OnTestRelayCommandCanExecute2)); } return _testRelayCommand2; } } public void OnTestRelayCommandExecuted2(object para) { MessageBox.Show("OK"); } public bool OnTestRelayCommandCanExecute2(object para) { DependencyObject node = para as DependencyObject; return IsValid(node); } // Validate all dependency objects in a window bool IsValid(DependencyObject node) { // Check if dependency object was passed if (node != null) { // Check if dependency object is valid. // NOTE: Validation.GetHasError works for controls that have validation rules attached bool isValid = !Validation.GetHasError(node); if (!isValid) { // If the dependency object is invalid, and it can receive the focus, // set the focus if (node is IInputElement) Keyboard.Focus((IInputElement)node); return false; } } // If this dependency object is valid, check all child dependency objects foreach (object subnode in LogicalTreeHelper.GetChildren(node)) { if (subnode is DependencyObject) { // If a child dependency object is invalid, return false immediately, // otherwise keep checking if (IsValid((DependencyObject)subnode) == false) return false; } } // All dependency objects are valid return true; } }
前台xaml
<StackPanel Grid.Column="1" Grid.Row="0"> <GroupBox Header="IDataError"> <StackPanel x:Name="stack2" Margin="10"> <StackPanel Orientation="Horizontal"> <TextBlock>Age:</TextBlock> <TextBox Text="{Binding Person.Age, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" Margin="5"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock>Name:</TextBlock> <TextBox Text="{Binding Person.Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" Margin="5"/> </StackPanel> <Button Height="20" Width="200" Margin="0,20,0,0" Command="{Binding TestRelayCommand2}" CommandParameter="{Binding ElementName=stack2}" >保存</Button> </StackPanel> </GroupBox> <Button Height="30" Margin="0,10,0,0" Width="80" Content="应用" Click="Button_Click2"/> </StackPanel>
法三 重写实现接口IDataError优化:
用 DataAnnotions 后,Model 的更加简洁,校验也更加灵活。还可以利用 CustomerValidation 或者
自定义 ValidationAttribute 来进行校验逻辑的进一步分离,错误消息格式化。并且通过反射等技术,完全可以将 IDataErrorInfo 的实现抽成一个抽象类进行封装,编程更加便利。
(1) 自定义 ValidationAttribute
添加了一个针对上面 Person 的 Name 属性是否存在的校验:
class NameExistsAttribute : ValidationAttribute { public override bool IsValid(object value) { var name = value as string; // 这里可以到数据库等存储容器中检索 if (name != "Felix") { return false; } return true; } public override string FormatErrorMessage(string name) { return "请输入存在的用户名。"; } }
(2) 利用 CustomerValidationAttribute
先实现一个 public static 的校验方法(必须返回 ValidationResult )
public class CustomerValidationUtils { public static System.ComponentModel.DataAnnotations.ValidationResult CheckName(string value) { if (value == null) { return new System.ComponentModel.DataAnnotations.ValidationResult("名字不能为空"); } if (value.Length < 8) { return new System.ComponentModel.DataAnnotations.ValidationResult("名字长度必须大于等于8位。"); } return System.ComponentModel.DataAnnotations.ValidationResult.Success; } }
下面是应用:
//定义了一个实体类的基类,具有通知、实现IDataErrorInfo接口
public class EntityBase : NotifyBase, IDataErrorInfo { public string Error { get { return ""; } } //修改 IDataErrorInfo 的索引器,让它通过 Validator 校验属性 public string this[string columnName] { get { var vc = new ValidationContext(this, null, null); vc.MemberName = columnName; var res = new List<System.ComponentModel.DataAnnotations.ValidationResult>(); var result = Validator.TryValidateProperty(this.GetType().GetProperty(columnName).GetValue(this, null), vc, res); if (res.Count > 0) { return string.Join(Environment.NewLine, res.Select(r => r.ErrorMessage).ToArray()); } return string.Empty; } } }
public class Person3 : EntityBase { private string _name; [MaxLength(10, ErrorMessage = "最长姓名10")] [NameExistsAttribute] public string Name { get { return _name; } set { if (_name != value) { _name = value; RaisePropertyChanged("Name"); } } } private string _nameSeconed; [CustomValidation(typeof(CustomerValidationUtils), "CheckName")] public string NameSeconed { get { return _nameSeconed; } set { _nameSeconed = value; RaisePropertyChanged("NameSeconed"); } } private int _age; //为了避免出现大量的 switch-case,并且将校验逻辑进行分离提高代码复用,于是 DataAnnotations 华丽登场。 //改造下上面的 Person 类,加上 [Range] ValidationAttribute:(需要添加 System.ComponentModel.DataAnnotations.dll) [Range(19, 99, ErrorMessage="年龄必须在19与99之间。")] public int Age { get { return _age; } set { if (_age != value) { _age = value; RaisePropertyChanged("Age"); } } } }
ViewModel:
public class PersonViewModel3 : NotifyBase { Person3 m_person = new Person3(); public Person3 Person { get { return m_person; } set { m_person = value; RaisePropertyChanged("Person"); } } private ICommand _testRelayCommand3; public ICommand TestRelayCommand3 { get { if (_testRelayCommand3 == null) { _testRelayCommand3 = new RelayCommand(new Action<object>(OnTestRelayCommandExecuted3), new Predicate<object>(OnTestRelayCommandCanExecute3)); } return _testRelayCommand3; } } public void OnTestRelayCommandExecuted3(object para) { MessageBox.Show("OK"); } public bool OnTestRelayCommandCanExecute3(object para) { DependencyObject node = para as DependencyObject; return IsValid(node); } // Validate all dependency objects in a window bool IsValid(DependencyObject node) { // Check if dependency object was passed if (node != null) { // Check if dependency object is valid. // NOTE: Validation.GetHasError works for controls that have validation rules attached bool isValid = !Validation.GetHasError(node); if (!isValid) { // If the dependency object is invalid, and it can receive the focus, // set the focus if (node is IInputElement) Keyboard.Focus((IInputElement)node); return false; } } // If this dependency object is valid, check all child dependency objects foreach (object subnode in LogicalTreeHelper.GetChildren(node)) { if (subnode is DependencyObject) { // If a child dependency object is invalid, return false immediately, // otherwise keep checking if (IsValid((DependencyObject)subnode) == false) return false; } } // All dependency objects are valid return true; } }
原文:https://www.cnblogs.com/ZHXI/p/13224137.html