WPF命令与输入验证绑定 - 如何启用" save"仅当所有输入都有效时按钮

时间:2014-04-16 13:26:24

标签: wpf mvvm icommand idataerrorinfo relaycommand

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

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

return !string.IsNullOrEmpty(Error);

但是,我的“保存”按钮永远不会启用。我的理由是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(); }
}

“保存”按钮:

<Button Content="Save" Command="{Binding InsertCommand}"/>

的InsertCommand:

public RelayCommand InsertCommand { get; internal set; }

在ViewModel构造函数中:

InsertCommand = new RelayCommand(ExecuteInsert, CanExecuteInsert);

CanExecute:

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

1 个答案:

答案 0 :(得分:4)

您还没有为我们添加足够的代码来准确地告诉您问题所在。但是,您正在采取正确的方法。我也使用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; }
}

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;
    }
}

因此,为了回答您的实际问题,我会像CanExecute那样处理Save Command的{​​{1}}功能:

    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
    }

所以,似乎你在几乎中以同样的方式这样做 - 我的附加属性当然是可选的。如果您的Error属性永远不会为空,那么我会说 是你的问题。首先调试它,然后看看它实际上有什么价值......也许总有一个不应该出现的错误?

啊......我刚刚注意到你的Error属性代码...... 是你的问题:

public string Error { get { return this[null]; } } 

您正在使用值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;
    }
}

然后在Error属性中,您应该像我在Errors属性中那样调用要验证的实际属性名称而不是 null。因此,在上面的示例中,您可以在Error属性中调用类似的内容:

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

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