WPF通过接口IDataErrorInfo和INotifyDataErrorInfo,允许构建报告错误的对象而不是直接抛出异常。两个接口具有相同的目标,即用更加人性化的错误通知系统来替代未处理的异常。IDataErrorInfo是初始的错误跟踪接口,为了是WPF向下兼容。INotifyDataErrorInfo接口具有同样的功能,但界面更加丰富。
public class MainWindowViewModel : NotifyPropertyBase, INotifyDataErrorInfo { private decimal? _productPrice; public decimal? ProductPrice { get => _productPrice; set { if (value.HasValue && value.Value < 0) { //throw new Exception("ProductPrice 不能小于0"); SetError("ProductPrice", new List<string> { "ProductPrice 不能小于0" }); } else { ClearErrors("ProductPrice"); } _productPrice = value; OnPropertyChanged("ProductPrice"); } } private Dictionary<string, List<string>> DictError = new Dictionary<string, List<string>>(); private void SetError(string propertyName, List<string> propertyErrors) { DictError.Remove(propertyName); DictError.Add(propertyName, propertyErrors); if (ErrorsChanged != null) { ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); } } private void ClearErrors(string propertyName) { DictError.Remove(propertyName); if (ErrorsChanged != null) { ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); } } /// <summary> /// 用于指示数据对象是否包含错误信息 /// </summary> public bool HasErrors => DictError.Keys.Count > 0; /// <summary> /// 在添加和删除错误时发生 /// </summary> public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; /// <summary> /// 提供完整的错误信息内容 /// </summary> /// <param></param> /// <returns></returns> public IEnumerable GetErrors(string propertyName) { if (string.IsNullOrEmpty(propertyName)) { return DictError.Values; } if (DictError.ContainsKey(propertyName)) { return DictError[propertyName]; } return null; } } <TextBox HorizontalAlignment="Left" VerticalAlignment="Top" Margin="200,200,0,0"> <TextBox.Text> <Binding Path="ProductPrice" ValidatesOnNotifyDataErrors="True" NotifyOnValidationError="True"/> </TextBox.Text> </TextBox>通过上述两种形式实现异常通知,但是他们之间有本质的区别:触发异常时,不会在数据对象中更新属性。但是使用INotifyDataErrorInfo接口时,允许使用非法值,但是会标记出来,数据对象会被更新,可以使用通知或者事件告知用户。
自定义验证规则
应用自定义规则的方法和应用自定义转化器的方法类似。继承自ValidationRule,并重写Validate()方法。
public class CustomValidation : ValidationRule { private decimal? _minValue = 0; private decimal? _maxValue = int.MaxValue; public decimal? MinValue { get => _minValue; set => _minValue = value; } public decimal? MaxValue { get => _maxValue; set => _maxValue = value; } public override ValidationResult Validate(object value, CultureInfo cultureInfo) { decimal price = 0; try { string strValue = value.ToString(); if (!string.IsNullOrEmpty(strValue)) { price = decimal.Parse(strValue, cultureInfo); } } catch (Exception) { return new ValidationResult(false, "非法数据"); } if (price < _minValue || price > _maxValue) { return new ValidationResult(false, "非法数据"); } return new ValidationResult(true, null); } } <TextBox HorizontalAlignment="Left" VerticalAlignment="Top" Margin="200,300,0,0"> <TextBox.Text> <Binding Path="ProductPrice"> <Binding.ValidationRules> <local:CustomValidation MaxValue="99.99"/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox>