如何在WPF/AvaloniaUI中为属性动态添加ValidationAttributes
动态添加ValidationAttribute并触发Avalonia/WPF验证的解决方法
问题根源
你通过TypeDescriptor.GetProperties能看到已添加的属性,但验证未触发,核心原因是:
- Avalonia/WPF的默认验证引擎只会在属性初始化或首次绑定阶段读取
ValidationAttribute,动态添加后不会自动重新扫描属性元数据; - 旧的
IDataErrorInfo验证机制不支持动态规则的自动更新,必须通过INotifyDataErrorInfo主动管理验证状态并通知变化。
具体解决方案
1. 确保绑定启用数据验证
在XAML绑定中必须开启数据验证开关,否则控件不会响应验证状态:
<TextBox Text="{Binding MyProperty, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" />
2. 实现INotifyDataErrorInfo接口(核心步骤)
让你的ViewModel实现这个接口,手动管理验证错误,并在属性变化或规则更新时触发验证刷新。示例代码:
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.ComponentModel.DataAnnotations; public class MyViewModel : INotifyPropertyChanged, INotifyDataErrorInfo { // 存储各属性的错误信息 private readonly Dictionary<string, List<string>> _propertyErrors = new(); // 存储动态添加的验证规则 private readonly Dictionary<string, List<ValidationAttribute>> _dynamicValidations = new(); private string _myProperty; public string MyProperty { get => _myProperty; set { _myProperty = value; OnPropertyChanged(); // 属性值变化时自动验证 ValidateProperty(nameof(MyProperty)); } } /// <summary> /// 动态添加验证规则的方法 /// </summary> public void AddDynamicValidation(string propertyName, ValidationAttribute attribute) { if (!_dynamicValidations.ContainsKey(propertyName)) _dynamicValidations[propertyName] = new List<ValidationAttribute>(); _dynamicValidations[propertyName].Add(attribute); // 添加规则后立即触发验证 ValidateProperty(propertyName); } /// <summary> /// 执行属性验证:合并静态+动态规则 /// </summary> private void ValidateProperty(string propertyName) { var property = GetType().GetProperty(propertyName); if (property == null) return; var propertyValue = property.GetValue(this); var allValidations = new List<ValidationAttribute>(); // 读取静态标记的ValidationAttribute allValidations.AddRange( property.GetCustomAttributes<ValidationAttribute>(inherit: true)); // 读取动态添加的ValidationAttribute if (_dynamicValidations.TryGetValue(propertyName, out var dynamicAttrs)) allValidations.AddRange(dynamicAttrs); // 执行验证并收集错误 var errors = allValidations .Where(attr => !attr.IsValid(propertyValue)) .Select(attr => attr.ErrorMessage ?? $"字段{propertyName}验证失败") .ToList(); // 更新错误字典并通知UI if (errors.Any()) { _propertyErrors[propertyName] = errors; OnErrorsChanged(propertyName); } else if (_propertyErrors.ContainsKey(propertyName)) { _propertyErrors.Remove(propertyName); OnErrorsChanged(propertyName); } } #region INotifyDataErrorInfo实现 public bool HasErrors => _propertyErrors.Any(); public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; public IEnumerable GetErrors(string propertyName) { return _propertyErrors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty<string>(); } private void OnErrorsChanged(string propertyName) { ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); } #endregion #region INotifyPropertyChanged实现 public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #endregion }
3. 动态添加验证规则并触发刷新
调用ViewModel的AddDynamicValidation方法添加规则,无需依赖TypeDescriptor(如果坚持用TypeDescriptor,添加完成后必须手动调用ValidateProperty):
// 示例:给MyProperty添加必填和长度验证 var viewModel = new MyViewModel(); viewModel.AddDynamicValidation(nameof(viewModel.MyProperty), new RequiredAttribute { ErrorMessage = "此字段不能为空" }); viewModel.AddDynamicValidation(nameof(viewModel.MyProperty), new StringLengthAttribute(10) { ErrorMessage = "长度不能超过10个字符" });
补充说明
如果你一定要沿用TypeDescriptor的方式添加属性,只需在添加完成后手动调用viewModel.ValidateProperty("MyProperty")触发验证即可,核心还是通过INotifyDataErrorInfo通知UI更新状态。
内容的提问来源于stack exchange,提问作者Htennek
相关产品推荐
相关产品推荐

