带输入验证的WPF命令绑定-如何启用;“保存”;仅当所有输入均有效时,才单击此按钮

带输入验证的WPF命令绑定-如何启用;“保存”;仅当所有输入均有效时,才单击此按钮,wpf,mvvm,icommand,idataerrorinfo,relaycommand,Wpf,Mvvm,Icommand,Idataerrorinfo,Relaycommand,在我的ViewModel中,我实现了IDataErrorInfo接口(以及INotifyPropertyChanged)。输入验证按预期工作,我在那里没有问题 我将此属性作为IDataErrorInfo的一部分公共字符串错误{get{return this[null];}}据我所知,错误应该是空的,如果所有经过验证的输入都通过了验证,那么我将其作为CanExecute方法传递 return !string.IsNullOrEmpty(Error); 但是,我的“保存”按钮从未启用。我的问题是,

在我的ViewModel中,我实现了IDataErrorInfo接口(以及INotifyPropertyChanged)。输入验证按预期工作,我在那里没有问题

我将此属性作为IDataErrorInfo的一部分
公共字符串错误{get{return this[null];}}
据我所知,
错误
应该是空的,如果所有经过验证的输入都通过了验证,那么我将其作为CanExecute方法传递

return !string.IsNullOrEmpty(Error);
但是,我的“保存”按钮从未启用。我的问题是,
canexecutechange
从不触发。如果这是真的,我应该在哪里以及如何触发它


这是我的休闲班。我尝试过其他的实施方式,但结果都是一样的。我认为它是有效的,因为如果我不将CanExecute方法传递给构造函数,“save”按钮是启用的

public class RelayCommand : ICommand
{
    private readonly Action execute;
    private readonly Func<bool> canExecute;

    public RelayCommand(Action execute, Func<bool> canExecute = null)
    {
        this.execute = execute;
        this.canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return canExecute == null || canExecute();      
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter) { execute(); }
}
在ViewModel构造函数中:

InsertCommand = new RelayCommand(ExecuteInsert, CanExecuteInsert);
CanExecute:

bool CanExecuteInsert()
{
    return !string.IsNullOrEmpty(Error);
}

您没有添加足够的代码,我们无法准确地告诉您问题所在。但是,您正在采取正确的方法。我还使用了
IDataErrorInfo
接口,但我在实现它的基类中添加了一些额外的属性:

public string Error // actual IDataErrorInfo Member
{
    get
    {
        if (!HasError) return string.Empty;
        StringBuilder errors = new StringBuilder();
        foreach (string error in Errors) errors.AppendUniqueOnNewLineIfNotEmpty(error);
        return errors.ToString();
    }
}

public virtual ObservableCollection<string> Errors
{
    get { return errors; }
}

public virtual bool HasError
{
    get { return Errors != null && Errors.Count > 0; }
}
因此,为了回答您的实际问题,我将如下处理
Save命令的
CanExecute
功能:

    public override ICommand Save
    {
        get { return new ActionCommand(action => SaveCommand(), canExecute => 
            CanSave(DigitalServiceProviderPriceTier)); }
    }
...
    private bool CanSave(DigitalServiceProviderPriceTier digitalServiceProviderPriceTier)
    {
        return digitalServiceProviderPriceTier != null && 
            digitalServiceProviderPriceTier.HasChanges && 
            !digitalServiceProviderPriceTier.HasError; // <-- Important part
    }
您正在使用
null
值调用索引器,因此索引器中的代码返回的实际上是
错误
属性值。如果没有验证错误,索引器还应返回空字符串:

public override string this[string propertyName]
{
    get
    {
        string error = string.Empty;
        if (propertyName == "SomePropertyName" && SomePropertyName.IsNullOrEmpty()) 
            error = "You must enter some property.";
        if (propertyName == "OtherPropertyName" && OtherPropertyName.Length != 3) 
            error = "The OtherPropertyName must be 3 characters long.";
        ...
        return error;
    }
}
然后在
Error
属性中,您应该调用要验证的实际属性名,就像我在
Errors
属性中所做的那样,而不是
null
。因此,在上面的示例中,您可以在
Error
属性中调用如下内容:

string error = this["SomePropertyName"];
if (error == string.Empty) error = this["OtherPropertyName"];
return error;

再一次,我写了太多的信息。。。我只是希望这一切对你有意义,你不会带着几十个新问题回来。希望这已经足够清楚了。

您还没有为我们添加足够的代码来准确地告诉您问题所在。但是,您正在采取正确的方法。我还使用了
IDataErrorInfo
接口,但我在实现它的基类中添加了一些额外的属性:

public string Error // actual IDataErrorInfo Member
{
    get
    {
        if (!HasError) return string.Empty;
        StringBuilder errors = new StringBuilder();
        foreach (string error in Errors) errors.AppendUniqueOnNewLineIfNotEmpty(error);
        return errors.ToString();
    }
}

public virtual ObservableCollection<string> Errors
{
    get { return errors; }
}

public virtual bool HasError
{
    get { return Errors != null && Errors.Count > 0; }
}
因此,为了回答您的实际问题,我将如下处理
Save命令的
CanExecute
功能:

    public override ICommand Save
    {
        get { return new ActionCommand(action => SaveCommand(), canExecute => 
            CanSave(DigitalServiceProviderPriceTier)); }
    }
...
    private bool CanSave(DigitalServiceProviderPriceTier digitalServiceProviderPriceTier)
    {
        return digitalServiceProviderPriceTier != null && 
            digitalServiceProviderPriceTier.HasChanges && 
            !digitalServiceProviderPriceTier.HasError; // <-- Important part
    }
您正在使用
null
值调用索引器,因此索引器中的代码返回的实际上是
错误
属性值。如果没有验证错误,索引器还应返回空字符串:

public override string this[string propertyName]
{
    get
    {
        string error = string.Empty;
        if (propertyName == "SomePropertyName" && SomePropertyName.IsNullOrEmpty()) 
            error = "You must enter some property.";
        if (propertyName == "OtherPropertyName" && OtherPropertyName.Length != 3) 
            error = "The OtherPropertyName must be 3 characters long.";
        ...
        return error;
    }
}
然后在
Error
属性中,您应该调用要验证的实际属性名,就像我在
Errors
属性中所做的那样,而不是
null
。因此,在上面的示例中,您可以在
Error
属性中调用如下内容:

string error = this["SomePropertyName"];
if (error == string.Empty) error = this["OtherPropertyName"];
return error;

再一次,我写了太多的信息。。。我只是希望这一切对你有意义,你不会带着几十个新问题回来。希望它足够清晰。

显示InsertCommand是如何初始化的及其CanExecute@lll在末尾添加了它。显示InsertCommand它是如何初始化的及其CanExecute@lll我最近刚开始使用WPF,在IDataErrorInfo的所有示例中,Error属性都是这样实现的。它用于验证,所以我不认为这是个问题。回家后我会试试你的建议。它奏效了,谢谢!我在这里也有一个逻辑缺陷
return!string.IsNullOrEmpty(错误)当然应该没有否定。我最近刚开始使用WPF,在IDataErrorInfo的所有示例中,Error属性都是这样实现的。它用于验证,所以我不认为这是个问题。回家后我会试试你的建议。它奏效了,谢谢!我在这里也有一个逻辑缺陷
return!string.IsNullOrEmpty(错误)应该没有否定。