WPF命令绑定和输入验证-仅在所有输入均有效时如何启用“保存”按钮

Saš*_*vić 1 wpf mvvm idataerrorinfo relaycommand icommand

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

我将此属性作为IDataErrorInfo的一部分,public string Error { get { return this[null]; } }据我了解,Error如果所有已验证的输入均通过验证,则应为空,因此我将此作为我的CanExecute方法传递

return !string.IsNullOrEmpty(Error);
Run Code Online (Sandbox Code Playgroud)

但是,我的“保存”按钮从未启用。我的CanExecuteChanged猜想是永远不会被触发。如果是这样,我应该在哪里以及如何触发它?


这是我的RelayCommand类。我尝试了其他实现方式,但结果相同。我认为它有效,因为如果不将CanExecute方法传递给构造函数,则会启用“保存”按钮。

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(); }
}
Run Code Online (Sandbox Code Playgroud)

“保存”按钮:

<Button Content="Save" Command="{Binding InsertCommand}"/>
Run Code Online (Sandbox Code Playgroud)

InsertCommand:

public RelayCommand InsertCommand { get; internal set; }
Run Code Online (Sandbox Code Playgroud)

在ViewModel构造函数中:

InsertCommand = new RelayCommand(ExecuteInsert, CanExecuteInsert);
Run Code Online (Sandbox Code Playgroud)

可以执行:

bool CanExecuteInsert()
{
    return !string.IsNullOrEmpty(Error);
}
Run Code Online (Sandbox Code Playgroud)

She*_*dan 5

您尚未真正为我们添加足够的代码来准确地告诉您您的问题是什么。但是,您正在采用正确的方法。我也使用该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; }
}
Run Code Online (Sandbox Code Playgroud)

Errors集合使我能够同时维护多个错误,并HasError告诉我是否存在任何错误。该Errors集合使用填充IDataErrorInfo在每个数据类型索引:

public override ObservableCollection<string> Errors
{
    get
    {
        errors = new ObservableCollection<string>();
        errors.AddUniqueIfNotEmpty(this["Title"]);
        errors.AddUniqueIfNotEmpty(this["Artist"]);
        ...
        errors.AddUniqueIfNotEmpty(this["DealerPrice"]);
        return errors;
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,要回答您的实际问题,我将处理如下CanExecute功能Save Command

    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
    }
Run Code Online (Sandbox Code Playgroud)

因此,似乎您几乎以相同的方式执行此操作-我的其他属性当然是可选的。如果您的Error财产永远都不为空,那我想就是您的问题。首先调试它,看看它实际有什么价值……也许总有一个错误不应该存在?

啊...我刚刚注意到您的Error财产代码... 就是您的问题:

public string Error { get { return this[null]; } } 
Run Code Online (Sandbox Code Playgroud)

您正在使用值为调用索引器null,因此索引器中返回的代码实际上就是您的Error属性值。如果没有验证错误,索引器还应该返回一个空字符串:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,在您的Error媒体资源中,您应该像在我的媒体资源中那样调用要验证的实际媒体名称,Errors不是 null。因此,在上面的示例中,您可以在Error媒体资源中调用如下代码:

string error = this["SomePropertyName"];
if (error == string.Empty) error = this["OtherPropertyName"];
return error;
Run Code Online (Sandbox Code Playgroud)

再一次,我写了太多的信息.​​..我只是希望这对您有意义,并且您不会再提出许多新问题。希望它已经足够清楚了。