c# - 强制 INotifyDataErrorInfo 验证

标签 c# wpf validation mvvm

我完全按照以下链接中的描述实现了 INotifyDataErrorInfo:

http://blog.micic.ch/net/easy-mvvm-example-with-inotifypropertychanged-and-inotifydataerrorinfo

我有一个 TextBox,它绑定(bind)到我模型中的一个字符串属性。

XAML

<TextBox Text="{Binding FullName,
                        ValidatesOnNotifyDataErrors=True,
                        NotifyOnValidationError=True,
                        UpdateSourceTrigger=PropertyChanged}" />

型号

private string _fullName;
public string FullName
{
    get { return _fullName; }
    set
    {
        // Set raises OnPropertyChanged
        Set(ref _fullName, value);

        if (string.IsNullOrWhiteSpace(_fullName))
            AddError(nameof(FullName), "Name required");
        else
            RemoveError(nameof(FullName));                
    }
}

INotifyDataError 代码

private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

// get errors by property
public IEnumerable GetErrors(string propertyName)
{
    if (_errors.ContainsKey(propertyName))
        return _errors[propertyName];
    return null;
}

public bool HasErrors => _errors.Count > 0;

// object is valid
public bool IsValid => !HasErrors;

public void AddError(string propertyName, string error)
{
    // Add error to list
    _errors[propertyName] = new List<string>() { error };
    NotifyErrorsChanged(propertyName);
}

public void RemoveError(string propertyName)
{
    // remove error
    if (_errors.ContainsKey(propertyName))
        _errors.Remove(propertyName);
    NotifyErrorsChanged(propertyName);
}

public void NotifyErrorsChanged(string propertyName)
{
    // Notify
    if (ErrorsChanged != null)
       ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}

现在一切正常,但只有当我在我的文本框中输入内容时它才会生效。我想要一些方法来按需验证,甚至不触摸文本框,比如单击按钮。

我已尝试按照 this 中的描述为我的所有属性提高 PropertyChanged问题,但它没有检测到错误。我不知何故需要调用我的属性 setter ,以便可以检测到错误。我正在寻找 MVVM 解决方案。

最佳答案

恕我直言,您使用的 INotifyDataErrorInfo 实现有些缺陷。它依赖于附加到对象的状态(列表)中保存的错误。存储状态的问题是,有时,在移动的世界中,您没有机会在需要时更新它。这是另一个 MVVM 实现,它不依赖于存储的状态,而是动态计算错误状态。

处理方式略有不同,因为您需要将验证代码放在中央 GetErrors 方法中(您可以创建从该中央方法调用的每个属性的验证方法),而不是属性 setter 。

public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
    public event PropertyChangedEventHandler PropertyChanged;
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public bool HasErrors
    {
        get
        {
            return GetErrors(null).OfType<object>().Any();
        }
    }

    public virtual void ForceValidation()
    {
        OnPropertyChanged(null);
    }

    public virtual IEnumerable GetErrors([CallerMemberName] string propertyName = null)
    {
        return Enumerable.Empty<object>();
    }

    protected void OnErrorsChanged([CallerMemberName] string propertyName = null)
    {
        OnErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }

    protected virtual void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
    {
        var handler = ErrorsChanged;
        if (handler != null)
        {
            handler(sender, e);
        }
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(sender, e);
        }
    }
}

这里有两个示例类来演示如何使用它:

public class Customer : ModelBase
{
    private string _name;

    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            if (_name != value)
            {
                _name = value;
                OnPropertyChanged();
            }
        }
    }

    public override IEnumerable GetErrors([CallerMemberName] string propertyName = null)
    {
        if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Name))
        {
            if (string.IsNullOrWhiteSpace(_name))
                yield return "Name cannot be empty.";
        }
    }
}

public class CustomerWithAge : Customer
{
    private int _age;
    public int Age
    {
        get
        {
            return _age;
        }
        set
        {
            if (_age != value)
            {
                _age = value;
                OnPropertyChanged();
            }
        }
    }

    public override IEnumerable GetErrors([CallerMemberName] string propertyName = null)
    {
        foreach (var obj in base.GetErrors(propertyName))
        {
            yield return obj;
        }

        if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Age))
        {
            if (_age <= 0)
                yield return "Age is invalid.";
        }
    }
}

使用像这样的简单 XAML,它就像一个魅力:

<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" />

(UpdateSourceTrigger 是可选的,如果您不使用它,它只会在失去焦点时起作用)。

使用这个 MVVM 基类,您不必强制执行任何验证。但是如果您需要它,我已经在 ModelBase 中添加了一个应该可以工作的 ForceValidation 示例方法(我已经使用例如 _name 之类的成员值对其进行了测试,该值在不通过公共(public) setter 的情况下会被更改)。

关于c# - 强制 INotifyDataErrorInfo 验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34665650/

相关文章:

c# - 当 .Net Sockets Keep Alive 没有收到确认时会引发什么事件?

c# - 在构造函数中使用运行时参数进行依赖注入(inject)

c# - 无法添加基于服务的数据库

c# - 绑定(bind)到 WPF 静态类中的静态属性

javascript - Jquery 表单验证

html - ngbDatepicker : Differentiate between invalid date format and out of range(minDate/maxDate) value for user feedback

javascript - Angular 中的只读组件不显示换行符

c# - 无法使用样式禁用 WPF 扩展器

c# - 如何只在鼠标悬停很长时间时才触发wpf动画?

api - 使用 "List from a range"对范围进行谷歌表格 API V4 数据验证