IDataErrorInfo:如何知道所有属性是否有效?

7
我有一个使用IDataErrorInfo在ViewModel上验证输入的WPF应用程序(.Net 3.5)。
它运行得很好,用户控件可以得到正确的UI反馈。
问题是用户仍然可以更改所选元素或保存此元素。
所以我的问题是:如何知道所有属性都是有效的?或者至少知道所有显示的值都是有效的。目标是将一些IsActive绑定到此结果。

如果错误属性不为空,则会出现错误。 - Bob Vale
你想知道它们是否都有效的地方在哪里?在视图(View)还是视图模型(ViewModel)? - Blachshma
我认为这个答案可能会对你有所帮助:https://dev59.com/KnVD5IYBdhLWcg3wDXJ3 - Stephen Holt
@BobVale 是的,我知道,但是我想一次验证所有属性。 - J4N
@Blachshma 在视图模型或视图中,但我需要有一个属性来绑定整个数据模型在视图上,而不仅仅是一个字段。 - J4N
显示剩余2条评论
3个回答

18
从您对实现IDataErrorInfo的评论中,将您的实现更改为以下样式...
#region IDataErrorInfo Members

public string Error
{
    get { return this[null] }
}

public string this[string columnName]
{
    get
    {
        StringBuilder result = new StringBuilder();
        if (string.IsNullOrEmpty(columnName) || columnName == "FirstName")
        {
            if (string.IsNullOrEmpty(FirstName))
                result.Append("Please enter a First Name\n");
        }
        if (string.IsNullOrEmpty(columnName) || columnName == "LastName")
        {
            if (string.IsNullOrEmpty(LastName))
                result.Append("Please enter a Last Name\n");
        }
       if (string.IsNullOrEmpty(columnName) || columnName == "Age")
        {
            if (Age <= 0 || Age >= 99)
                result.Append("Please enter a valid age\n");
        }
        return (result.Length==0) ? null : result.Remove(result.Length-1,1).ToString();
    }
}

#endregion


public bool IsValid {
   get { return string.IsNullOrEmpty(this.Error); }
}

然后在您的属性更改事件中
if (e.PropertyName == "Error") {
   OnPropertyChanged(this,new PropertyChangedEventArgs("IsValid"));
}
if (e.PropertyName != "Error" && e.PropertyName != "IsValid") {
   OnPropertyChanged(this,new PropertyChangedEventArgs("Error"));
}

1
@J4N 是的,错误应该是关于对象本身出了什么问题的概要,通常会像这个答案中的总结一样进行。 - Bob Vale
1
这个很好!谢谢!一个小细节:应该是((IDataErrorInfo)this)[null]而不仅仅是this[null]。同样适用于在“IsValid”检查中的((IDataErrorInfo)this).Error。我也将你的1行返回语句重写成了8行,以便更清晰地表达。:P - Simon F
@SimonF 我认为只有在显式实现接口时才需要进行转换,即 string IDataErrorInfo.Error { - Bob Vale
@Bob Vale:你说得对。我将“this[]”方法声明更改为“public string this[string ...]”,而不是“string IDataErrorInfo.this[ string ...]”,这样就可以正常工作,无需进行强制转换。 - Simon F
@James 你需要确保你的类实现了INotifyPropertyChanged接口,然后OnPropertyChanged方法会触发PropertyChangedEvent事件 - void OnPropertyChanged(string name) { var handler=this.PropertyChanged; if (handler!=null) {handler(this,new PropertyChangedEventArgs(name));}} - Bob Vale
显示剩余5条评论

1

目前,我已在我的模型中添加了此方法。

    public Boolean IsModelValid()
    {
        Boolean isValid = true;
        PropertyInfo[] properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (PropertyInfo p in properties)
        {
            if (!p.CanWrite || !p.CanRead)
            {
                continue;
            }
            if (this[p.Name] != null)
            {
                isValid = false;
            }
        }
        return isValid;
    }

我将对象本身绑定到PropertyChanged事件上,

    public MyClassName()
    {
        PropertyChanged += CheckModelValidity;
        CheckModelValidity(null, null);
    }

当它改变时,我调用这个方法,如果结果与我的实际公共成员不同,我就更新它:
    private void CheckModelValidity(object sender, PropertyChangedEventArgs e)
    {
        bool isModelValid = IsModelValid();
        if(isModelValid!= IsValid)
        {
            IsValid = isModelValid;
        }
    }

然后我只需绑定IsValid属性。

我不知道是否有更好的解决方案?


3
如果您要选择这条路,请在静态构造函数中初始化有效属性名称列表,并将其存储在静态字段中。然后,您只需要进行一次反射即可。 - Bob Vale
@BobVale 是的,我也不为此感到自豪,但我更喜欢这种方式,而不是冒着某人忘记执行一个检查的风险。很好,我会在静态变量中初始化它。 - J4N
这不会重新评估错误。您可能希望使用以下代码:Validator.TryValidateObject(this, new ValidationContext(this, null), null); 它将返回整个验证(包括错误)是否成功。 - Chris Bordeman

0

感谢Bob Vale提供的想法!我注意到我有很多模型,代码非常重复。我创建了这个基类:

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;

namespace Core.Models
{
    public abstract class ValidatableObject : IDataErrorInfo
    {
        public virtual bool IsValid() => this[null] == null;

        [NotMapped]
        public string? Error => this[null];

        [NotMapped]
        protected abstract (string, Func<string?>)[] Checks { get; }

        public virtual string? this[string? name] => validate(name, Checks);

        private static string? validate(string? name, params (string, Func<string?>)[] checks)
        {
            StringBuilder results = new();

            foreach ((string val, Func<string?> check) in checks)
            {
                if (String.IsNullOrEmpty(name) || name == val)
                {
                    string? result = check();
                    if (result != null)
                        results.AppendLine(result);
                }
            }

            return results.Length == 0
                ? null
                : results.ToString(0, results.Length - Environment.NewLine.Length);
        }
    }
}

这里是使用方法:

    private class Validatable : ValidatableObject
    {
        public string Email { get; set; }
        public string Comment { get; set; }

        protected override (string, Func<string?>)[] Checks => new (string, Func<string?>)[]
        {
            (nameof(Email), () => Validate.Email(Email)),
            (nameof(Comment), () => Validate.LengthOfOptionalString(Comment)),
        };
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接