WPF数据验证异常:加载含初始错误实例时错误计数异常
WPF自定义TabItem错误计数异常的原因及解决方法
问题场景
我们自定义了继承自TabItem的TabItemEx类,新增NumErrors属性统计其下控件触发的Validation Errors数量,通过监听Validation.Error路由事件的CollectError方法更新计数。验证规则要求int类型属性A、B、C必须大于0,但出现以下异常:
- 加载初始无错误的实例时功能正常
- 启动后直接加载A值不符合要求的实例时,错误数统计为0
- 将A从-2改为2时,会出现无意义的负数计数
原实现代码
internal class TabItemEx : TabItem, INotifyPropertyChanged { private int _NumErrors; public int NumErrors { get => _NumErrors; set { if (_NumErrors != value) { _NumErrors = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NumErrors))); } } } public TabItemEx() : base() { NumErrors = 0; Debug.WriteLine("TabItemEx Created"); } public event PropertyChangedEventHandler PropertyChanged; }
private void CollectError(object sender, ValidationErrorEventArgs e) { var tabItemEx= sender as TabItemEx; if (tabItemEx == null) return; if (e.Action == ValidationErrorEventAction.Added) { tabItemEx.NumErrors++; Debug.WriteLine($"{tabItemEx.Header.ToString()}:+:{tabItemEx.NumErrors} {e.Error.ErrorContent.ToString()}"); } else { tabItemEx.NumErrors--; Debug.WriteLine($"{tabItemEx.Header.ToString()}:-:{tabItemEx.NumErrors} {e.Error.ErrorContent.ToString()}"); } if (tabItemEx.NumErrors == 0) { tabItemEx.Foreground = Brushes.Black; } else { tabItemEx.Foreground = Brushes.Red; } }
异常原因
- 初始错误未触发事件:
Validation.Error路由事件仅在错误动态添加/移除时触发(比如用户输入修改后),而初始加载时绑定的错误是在控件初始化阶段就已存在的,不会触发Added事件,导致NumErrors始终停留在初始值0。 - 负数计数的由来:当把初始错误的A值从-2改为2时,会触发
Removed事件(移除原有错误),此时NumErrors从0减1,就出现了无意义的负数。
解决方法
要解决这个问题,需要主动收集初始加载时的错误数,同时在事件处理中添加边界保护防止负数:
步骤1:添加视觉树遍历工具方法
用于遍历TabItem下的所有元素,收集绑定错误:
private static IEnumerable<DependencyObject> GetVisualChildren(DependencyObject parent) { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); yield return child; foreach (var grandChild in GetVisualChildren(child)) { yield return grandChild; } } }
步骤2:修改TabItemEx,添加Loaded事件处理
在控件加载完成后主动统计初始错误:
internal class TabItemEx : TabItem, INotifyPropertyChanged { private int _NumErrors; public int NumErrors { get => _NumErrors; set { if (_NumErrors != value) { _NumErrors = value; // 确保计数不会为负 if (_NumErrors < 0) _NumErrors = 0; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NumErrors))); UpdateForeground(); } } } public TabItemEx() : base() { NumErrors = 0; Debug.WriteLine("TabItemEx Created"); Loaded += TabItemEx_Loaded; } private void TabItemEx_Loaded(object sender, RoutedEventArgs e) { // 加载完成后统计初始错误数 int initialErrors = 0; foreach (var element in GetVisualChildren(this)) { if (element is FrameworkElement fe) { initialErrors += Validation.GetErrors(fe).Count; } } NumErrors = initialErrors; } private void UpdateForeground() { Foreground = NumErrors == 0 ? Brushes.Black : Brushes.Red; } public event PropertyChangedEventHandler PropertyChanged; }
步骤3:修正CollectError方法,简化逻辑
private void CollectError(object sender, ValidationErrorEventArgs e) { var tabItemEx = sender as TabItemEx; if (tabItemEx == null) return; if (e.Action == ValidationErrorEventAction.Added) { tabItemEx.NumErrors++; Debug.WriteLine($"{tabItemEx.Header.ToString()}:+:{tabItemEx.NumErrors} {e.Error.ErrorContent.ToString()}"); } else { tabItemEx.NumErrors--; Debug.WriteLine($"{tabItemEx.Header.ToString()}:-:{tabItemEx.NumErrors} {e.Error.ErrorContent.ToString()}"); } }
内容的提问来源于stack exchange,提问作者j_zhou
相关产品推荐
相关产品推荐

