You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
    }
}

异常原因

  1. 初始错误未触发事件:Validation.Error路由事件仅在错误动态添加/移除时触发(比如用户输入修改后),而初始加载时绑定的错误是在控件初始化阶段就已存在的,不会触发Added事件,导致NumErrors始终停留在初始值0。
  2. 负数计数的由来:当把初始错误的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 16:15:13