PAYATHON 2026

WinForms:根据 ValidationResult 区分单元格警告与错误

数据小许

可以保留 CustomValidationResult,在 CellFormatting 中读取它的 State。先在数据对象上添加一个返回验证结果集合的方法,再让 IDataErrorInfo 和格式化逻辑共用这个方法。

IDataErrorInfo 的属性索引器只能返回字符串。当前代码返回 results.First().ErrorMessage 后,结果对象及其严重程度信息就丢失了,因此无法从 cell.ErrorText 中取回原来的 CustomValidationResult。

先修正 CustomValidationResult

将原构造函数中的 ResultState = state; 改为 State = state;。ResultState 是枚举类型名称,不能作为属性赋值。

修改后的类如下:

using System.ComponentModel.DataAnnotations;

public class CustomValidationResult : ValidationResult
{
    public enum ResultState
    {
        Warning,
        Error
    }

    public ResultState State { get; private set; }

    public CustomValidationResult(string errorMessage, ResultState state)
        : base(errorMessage)
    {
        State = state;
    }
}

无需额外声明 Success 属性。验证通过时,直接返回 ValidationResult.Success:

public class MyAwesomeValidator
{
    public static ValidationResult ValidateIntVal(
        int value,
        ValidationContext context)
    {
        if (value < 0)
        {
            return new CustomValidationResult(
                "The value cannot be smaller than 0",
                CustomValidationResult.ResultState.Error);
        }

        if (value < 5)
        {
            return new CustomValidationResult(
                "The value is a bit small, isn't it?",
                CustomValidationResult.ResultState.Warning);
        }

        return ValidationResult.Success;
    }
}

在数据对象上公开属性验证结果

将以下方法加入 MyAwesomeClass,并用下面的索引器替换原有实现。相关代码需要 System.Collections.Generic、System.ComponentModel 和 System.ComponentModel.DataAnnotations。

public IList<ValidationResult> GetPropertyValidationResults(string propertyName)
{
    var results = new List<ValidationResult>();
    var property = TypeDescriptor.GetProperties(this)[propertyName];

    if (property == null)
    {
        return results;
    }

    var context = new ValidationContext(this, null, null)
    {
        MemberName = propertyName
    };

    Validator.TryValidateProperty(
        property.GetValue(this),
        context,
        results);

    return results;
}

[Browsable(false)]
public string this[string propertyName]
{
    get
    {
        var results = GetPropertyValidationResults(propertyName);

        return results.Count == 0
            ? string.Empty
            : results[0].ErrorMessage;
    }
}

索引器继续向 WinForms 数据绑定提供错误文本,格式化逻辑则可以取得包含 State 的结果对象。原来的 Error 属性可以保留。

这个方法每次调用时都会重新验证,不会缓存结果。如果验证频繁或开销较大,可以按属性缓存结果,并在相关属性变化时使缓存失效。

根据绑定对象和属性设置颜色

用下面的实现替换原来的 DataGridViewCellFormatter:

using System;
using System.Drawing;
using System.Windows.Forms;

class DataGridViewCellFormatter
{
    private readonly DataGridView dgv;

    public DataGridViewCellFormatter(DataGridView dgv)
    {
        if (dgv == null)
        {
            throw new ArgumentNullException(nameof(dgv));
        }

        this.dgv = dgv;
        this.dgv.CellFormatting += Dgv_CellFormatting;
    }

    private void Dgv_CellFormatting(
        object sender,
        DataGridViewCellFormattingEventArgs e)
    {
        if (e.RowIndex < 0 || e.ColumnIndex < 0)
        {
            return;
        }

        var row = dgv.Rows[e.RowIndex];
        if (row.IsNewRow)
        {
            return;
        }

        var item = row.DataBoundItem as MyAwesomeClass;
        if (item == null)
        {
            return;
        }

        var propertyName = dgv.Columns[e.ColumnIndex].DataPropertyName;
        if (string.IsNullOrWhiteSpace(propertyName))
        {
            return;
        }

        var results = item.GetPropertyValidationResults(propertyName);
        bool hasWarning = false;
        bool hasError = false;

        foreach (var result in results)
        {
            var custom = result as CustomValidationResult;

            if (custom != null &&
                custom.State == CustomValidationResult.ResultState.Warning)
            {
                hasWarning = true;
            }
            else
            {
                // Treat ordinary ValidationResult instances as errors.
                hasError = true;
            }
        }

        if (hasError)
        {
            e.CellStyle.BackColor = Color.Red;
        }
        else if (hasWarning)
        {
            e.CellStyle.BackColor = Color.Orange;
        }
    }
}

代码通过 row.DataBoundItem 找到当前行对应的对象,通过列的 DataPropertyName 找到绑定属性。不要用列标题或列的 Name 推断属性,因为它们可能与绑定属性名称不同。

同一属性可能返回多个验证结果。上述实现让错误优先于警告,并将普通 ValidationResult 视为错误。按题目中的规则,-2 对应红色,3 对应橙色,8 不覆盖默认背景。

格式化和验证需要注意的地方

在 CellFormatting 中修改 e.CellStyle,可以为本次显示设置样式。没有验证问题时,不覆盖背景,让 DataGridView 使用原有的样式继承规则,无需手动从 OwningColumn.DefaultCellStyle 恢复颜色。应移除原来的格式化处理器,避免它继续写入 cell.Style.BackColor。

单元格被选中时,显示颜色还受 SelectionBackColor 影响。如果选中后仍需要显示严重程度,可以相应设置 e.CellStyle.SelectionBackColor。

在 DataAnnotations 中,返回带有 Warning 状态的 CustomValidationResult 仍表示验证未通过。Warning 是应用自行定义的含义,IDataErrorInfo 不会理解这个级别。保留上述索引器时,警告仍可能通过错误图标显示。如果只想让真正的错误进入 IDataErrorInfo,需要在索引器中筛掉警告,格式化逻辑仍读取完整结果集合。

示例根据数据对象当前的属性值格式化单元格。编辑中的文本尚未提交到对象时,这个方法不会验证新值。如果需要在提交前校验,应另外处理 CellValidating,并明确警告是否允许继续提交。

原始节选没有展示 PropertyChanged 事件声明和 MyAwesomeClass(string, int) 构造函数。如果完整代码中也没有定义,需要补齐,否则无法编译。

备注:内容仅供参考。