1

目前我正在使用该IDataErrorInfo接口在 WPF 应用程序中实现验证。作为该接口一部分的索引器允许验证单个属性,如下所示:

public string this[string columnName]
{
    get
    {
        switch (columnName)
        {
            case "LastName":
                if (string.IsNullOrEmpty(this.LastName))
                    return "LastName must not be empty.";
                break;
            // case, case, case, etc., etc.
        }
        return null;
    }
}

如果发生验证错误,我会在文本框旁边显示一个带有工具提示的星号。

如果我有与单个属性不严格相关的验证规则,我该怎么办?例如,如果代表订单的域实体具有发货日期和发票日期,并且我想验证发票日期必须晚于或等于发货日期的规则?

当然,我也可以通过检查关系两次,一次用于 columnName“ShippingDate”,一次用于 columnName“InvoiceDate”,然后在 UI 的两个输入字段中用星号标记错误,我也可以将这个规则强制到索引器中,如下所示:

public string this[string columnName]
{
    get
    {
        switch (columnName)
        {
            case "ShippingDate":
            case "InvoiceDate":
                if (this.ShippingDate > this.InvoiceDate)
                    return "Invoice date must not be before shipping date.";
                break;
        }
        return null;
    }
}

但我更愿意独立于索引器进行“对象级别”或“交叉属性”验证(索引器应该只报告无效的“单一属性状态”)并在 UI 上分别显示这些对象级别或关系错误。

我希望接口的Error属性IDataErrorInfo可能具有对象级别验证的目的。ValidatesOnDataErrors=True例如,当我在 TextBox 的 Binding 表达式中指定时,WPF 会调用索引器进行属性验证。Error但是每当我更改输入字段中的某些数据时,我都找不到一种方法来告诉 WPF 调用该属性。也许我对这个属性的目的的猜测是错误的?

如何在 WPF 中实现跨属性验证?

提前感谢您的建议!

4

1 回答 1

2

关于我是否可以设置绑定以便 WPF 自动测试接口的Error属性的问题,我在这里IDataErrorInfo找到了以下否定答案:

某人的问题:

基本上,我想知道一个 Binding 属性会触发 IDataErrorInfo.Error 的测试,就像 ValidatesOnDataErrors 导致 IDataErrorInfo.Item 的测试一样。

来自 Microsoft 在线社区支持的回答:

设置 Binding 类的 ValidatesOnDataErrors 属性仅测试 IDataErrorInfo.Item 而不是 IDataErrorInfo.Error。

到目前为止,Binding 类没有提供一个属性来检查 IDataErrorInfo.Error 作为 ValidatesOnDataErrors 属性来检查 IDataErrorInfo.Item。

为了得到你想要的,我们必须设置一个数据绑定到 IDataError.Error...

因此,该Error属性没有比CrossPropertyErrors在域实体中定义我自己的手工属性(如 )更有价值的了。WPF 不支持Error以简单的内置方式测试属性。

编辑:上面的报价来自 2008 年 3 月,因此很可能与 .NET 3.5 有关。但我找不到任何迹象表明这在 .NET 4.0 中确实发生了变化。

编辑:最后我必须创建自己的手写绑定到Error属性,并用适当的跨属性错误消息填充它。现在,类中任何其他属性的每次更改都会引发PropertyChanged更改的属性本身和属性的事件,Error以刷新 UI 上的错误消息。

编辑 2

它看起来大致是这样的:

模型(或 ViewModel)类:

public class SomeModel : NotificationObject, IDataErrorInfo
{
    private string _someProperty;
    public string SomeProperty
    {
        get { return _someProperty; }
        set
        {
            if (_someProperty != value)
            {
                _someProperty = value;
                RaisePropertyChanged("SomeProperty", "Error");
                // That's the key: For every changed property a change
                // notification also for the Error property is raised
            }
        }
    }
    // The above repeats for every property of the model

    #region IDataErrorInfo Member
    public string Error
    {
        get
        {
            var sb = new StringBuilder();
            // for example...
            if (InvoiceDate < ShippingDate)
                sb.AppendLine("InvoiceDate must not be before ShippingDate.");

            // more cross-property validations... We have only one Error
            // string, therefore we append the messages with
            // sb.AppendLine("Another message...") ... etc.
            // could all be moved into a separate validation class
            // to keep the model class cleaner

            return sb.ToString();
        }
    }

    public string this[string columnName]
    {
        get
        {
            switch (columnName)
            {
                case "ShippingDate":
                    // property-level validations
                case "InvoiceDate":
                    // property-level validations
                // etc.
            }
            return null;
        }
    }
    #endregion
}

NotificationObject实现RaisePropertyChanged

public abstract class NotificationObject : INotifyPropertyChanged
{
    #region INotifyPropertyChanged Member
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    protected virtual void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void RaisePropertyChanged(params string[] propertyNames)
    {
        if (propertyNames == null)
            throw new ArgumentNullException("propertyNames");

        foreach (var name in propertyNames)
            RaisePropertyChanged(name);
    }

    // ...
}

然后在一个视图中,该Error属性被绑定到 - 例如 - a TextBlock,它显示了跨属性验证错误:

<TextBlock Text="{Binding SomeModel.Error}" TextWrapping="Wrap" ... />

因此:模型上的每个更改的属性都将通知 WPF 绑定引擎有关属性的(潜在)更改,Error从而导致跨属性验证文本的更新。

于 2011-02-11T15:40:25.530 回答