C# 强制INotifyDataErrorInfo验证

C# 强制INotifyDataErrorInfo验证,c#,wpf,validation,mvvm,C#,Wpf,Validation,Mvvm,我已经完全按照以下链接中的描述实现了INotifyDataErrorInfo: 我有一个文本框,它绑定到模型中的字符串属性 XAML <TextBox Text="{Binding FullName, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True, UpdateSou

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

我有一个
文本框
,它绑定到模型中的字符串属性

XAML

<TextBox Text="{Binding FullName,
                        ValidatesOnNotifyDataErrors=True,
                        NotifyOnValidationError=True,
                        UpdateSourceTrigger=PropertyChanged}" />
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));
}
private Dictionary\u errors=new Dictionary();
公共事件事件处理程序错误更改;
//按属性获取错误
公共IEnumerable GetErrors(字符串propertyName)
{
if(_errors.ContainsKey(propertyName))
返回_错误[propertyName];
返回null;
}
public bool hasrerrors=>\u errors.Count>0;
//对象是有效的
公共bool IsValid=>!有错误;
public void AddError(字符串属性名称,字符串错误)
{
//将错误添加到列表中
_错误[propertyName]=新列表(){error};
NOTIFYERRSCHANGED(属性名称);
}
公共无效删除错误(字符串propertyName)
{
//删除错误
if(_errors.ContainsKey(propertyName))
_错误。删除(propertyName);
NOTIFYERRSCHANGED(属性名称);
}
public void NotifyErrorsChanged(字符串属性名称)
{
//通知
if(ErrorsChanged!=null)
ErrorsChanged(这是新数据ErrorSchangedEventArgs(propertyName));
}
现在,所有这些都可以正常工作,但它只在我在文本框中键入内容时进行验证。我想找到一种方法来按需验证,甚至不用触摸文本框,比如点击按钮


我已经尝试为我的所有属性提升PropertyChanged,如前面所述,但它没有检测到错误。我不知何故需要调用我的属性设置程序,以便能够检测到错误。我正在寻找MVVM解决方案。

您最好的选择是使用中继命令界面。看看这个:

public class RelayCommand : ICommand
{
    Action _TargetExecuteMethod;
    Func<bool> _TargetCanExecuteMethod;

    public RelayCommand(Action executeMethod)
    {
        _TargetExecuteMethod = executeMethod;
    }

    public RelayCommand(Action executeMethod, Func<bool> canExecuteMethod)
    {
        _TargetExecuteMethod = executeMethod;
        _TargetCanExecuteMethod = canExecuteMethod;
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged(this, EventArgs.Empty);
    }
    #region ICommand Members

    bool ICommand.CanExecute(object parameter)
    {
        if (_TargetCanExecuteMethod != null)
        {
            return _TargetCanExecuteMethod();
        }
        if (_TargetExecuteMethod != null)
        {
            return true;
        }
        return false;
    }

    public event EventHandler CanExecuteChanged = delegate { };

    void ICommand.Execute(object parameter)
    {
        if (_TargetExecuteMethod != null)
        {
            _TargetExecuteMethod();
        }
    }
    #endregion
}
现在,除了使用
OnSave
CanSave
方法注册
SaveCommand
之外,由于您是从
INotifyDataErrorInfo
扩展而来的,您还可以在构造函数中注册
ErrorsChanged

public YourViewModel()
{
    SaveCommand = new RelayCommand(OnSave, CanSave);
    ErrorsChanged += RaiseCanExecuteChanged;
}
您将需要以下方法:

private void RaiseCanExecuteChanged(object sender, EventArgs e)
{
        SaveCommand.RaiseCanExecuteChanged();
}

public bool CanSave()
{
    return !this.HasErrors;
}

private void OnSave()
{
    //Your save logic here.
}
此外,每次调用
PropertyChanged
后,都可以调用此验证方法:

    private void ValidateProperty<T>(string propertyName, T value)
    {
        var results = new List<ValidationResult>();
        ValidationContext context = new ValidationContext(this);
        context.MemberName = propertyName;
        Validator.TryValidateProperty(value, context, results);

        if (results.Any())
        {
            _errors[propertyName] = results.Select(c => c.ErrorMessage).ToList();
        }
        else
        {
            _errors.Remove(propertyName);
        }

        ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }
private void ValidateProperty(字符串propertyName,T值)
{
var results=新列表();
ValidationContext=新的ValidationContext(此);
context.MemberName=propertyName;
TryValidateProperty(值、上下文、结果);
if(results.Any())
{
_errors[propertyName]=results.Select(c=>c.ErrorMessage.ToList();
}
其他的
{
_错误。删除(propertyName);
}
ErrorsChanged(这是新数据ErrorSchangedEventArgs(propertyName));
}
使用此设置,如果您的viewmodel同时从
INotifyPropertyChanged
INotifyDataErrorInfo
(或从这两个扩展的基类)扩展,当您将按钮绑定到上面的
SaveCommand
时,如果出现验证错误,WPF框架将自动禁用它


希望这能有所帮助。

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

由于需要将验证代码放在一个中心GetErrors方法中(您可以创建从该中心方法调用的每个属性验证方法),而不是放在属性设置器中,所以处理方式有点不同

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);
        }
    }
}
公共类模型库:INotifyPropertyChanged,INotifyDataErrorInfo
{
公共事件属性更改事件处理程序属性更改;
公共事件事件处理程序错误更改;
公共布尔错误
{
得到
{
返回GetErrors(null).OfType().Any();
}
}
公共虚拟验证()
{
OnPropertyChanged(空);
}
公共虚拟IEnumerable GetErrors([CallerMemberName]字符串propertyName=null)
{
返回可枚举的.Empty();
}
受保护的无效OnErrorsChanged([CallerMemberName]字符串propertyName=null)
{
OnErrorsChanged(这是新的DataErrorsCHANGEDVENTARGS(propertyName));
}
受保护的虚拟空OnErrorsChanged(对象发送方,DataErrorsCHANGEDVENTARGS e)
{
var handler=ErrorsChanged;
if(处理程序!=null)
{
处理人(发送者,e);
}
}
受保护的OnPropertyChanged无效([CallerMemberName]字符串propertyName=null)
{
OnPropertyChanged(这是新的PropertyChangedEventArgs(propertyName));
}
受保护的虚拟void OnPropertyChanged(对象发送方,PropertyChangedEventArgs e)
{
var handler=PropertyChanged;
if(处理程序!=null)
{
处理人(发送者,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.";
        }
    }
}
公共类客户:模型库
{
私有字符串\u名称;
公共字符串名
{
得到
{
返回_name;
}
设置
{
如果(_name!=值)
{
_名称=值;
OnPropertyChanged();
}
}
}
公共重写IEnumerable GetErrors([CallerMemberName]字符串propertyName=null)
{
if(string.IsNullOrEmpty(propertyName)| | propertyName==nameof(Name))
{
if(string.IsNullOrWhiteSpace(_name))
收益返回“名称不能为空。”;
}
}
}
公共类Customer,其名称:Customer
{
私人互联网;
公共信息
{
得到
{
回归年龄;
}
设置
{
如果(_age!=值)
{
_年龄=价值;
OnPropertyChanged();
}
}
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.";
        }
    }
}
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" />