首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >强制INotifyDataErrorInfo验证

强制INotifyDataErrorInfo验证
EN

Stack Overflow用户
提问于 2016-01-07 21:38:33
回答 2查看 8.9K关注 0票数 16

我实现了INotifyDataErrorInfo,正如下面的链接所描述的那样:

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

我有一个TextBox,它绑定到模型中的string属性。

XAML

代码语言:javascript
复制
<TextBox Text="{Binding FullName,
                        ValidatesOnNotifyDataErrors=True,
                        NotifyOnValidationError=True,
                        UpdateSourceTrigger=PropertyChanged}" />

模型

代码语言:javascript
复制
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码

代码语言:javascript
复制
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));
}

现在,所有这些都很好,但是只有在我在我的TextBox中输入某项内容时,它才会生效。我想要一些方式来验证按需,甚至不接触文本框,例如在一个按钮点击。

我尝试为我的所有属性(如PropertyChanged问题中所描述的)提出this,但它没有检测到错误。不知何故,我需要调用我的属性设置器,以便能够检测到错误。我在找一个MVVM解决方案。

EN

回答 2

Stack Overflow用户

发布于 2016-01-11 13:20:09

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

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

代码语言:javascript
复制
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);
        }
    }
}

下面是两个示例类,演示如何使用它:

代码语言:javascript
复制
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来说,它就像一种魅力:

代码语言:javascript
复制
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" />

(UpdateSourceTrigger是可选的,如果不使用它,它只在失去焦点时才能工作)。

对于这个MVVM基类,您不应该强制进行任何验证。但是,如果您需要它,我已经在ModelBase中添加了一个应该工作的ModelBase样例方法(例如,我已经用一个成员值(例如,一个成员值,如_name )进行了测试,如果没有经过公共设置器,这个值就会被更改)。

票数 23
EN

Stack Overflow用户

发布于 2016-01-08 08:49:55

您最好的选择是使用中继命令接口。看一看这个:

代码语言:javascript
复制
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
}

您可以在视图模型中声明此中继命令如下:

代码语言:javascript
复制
public RelayCommand SaveCommand { get; private set; }

现在,除了将SaveCommand注册到OnSaveCanSave方法之外,由于您是从INotifyDataErrorInfo扩展的,您还可以在构造函数中注册ErrorsChanged

代码语言:javascript
复制
public YourViewModel()
{
    SaveCommand = new RelayCommand(OnSave, CanSave);
    ErrorsChanged += RaiseCanExecuteChanged;
}

你需要这些方法:

代码语言:javascript
复制
private void RaiseCanExecuteChanged(object sender, EventArgs e)
{
        SaveCommand.RaiseCanExecuteChanged();
}

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

private void OnSave()
{
    //Your save logic here.
}

此外,每次调用PropertyChanged之后,都可以调用此验证方法:

代码语言:javascript
复制
    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));
    }

使用此设置,如果您的视图模型都从INotifyPropertyChangedINotifyDataErrorInfo扩展(或者从从这两个视图扩展来的基类),那么当您将按钮绑定到上面的SaveCommand时,如果存在验证错误,WPF框架将自动禁用它。

希望这能有所帮助。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34665650

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档