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

shstlldc  于 2023-06-24  发布在  其他
关注(0)|答案(1)|浏览(80)

在我的ViewModel中,我实现了IDataErrorInfo接口(连同INotifyPropertyChanged)。输入验证按预期工作,我在那里没有问题。
我将此属性作为IDataErrorInfo的一部分

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

据我所知,如果所有验证的输入都通过了验证,Error应该是空的,所以我将其作为CanExecute方法传递

return !string.IsNullOrEmpty(Error);

但是,我的“保存”按钮从未启用。我猜CanExecuteChanged永远不会被触发。如果这是真的,我应该在哪里以及如何触发它?
这是我的RelayCommand类。我尝试过其他的实施方式,但结果是一样的。我认为它是可行的,因为如果我不将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(); }
}

“保存”按钮:

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

插入命令:

public RelayCommand InsertCommand { get; internal set; }

在ViewModel构造函数中:

InsertCommand = new RelayCommand(ExecuteInsert, CanExecuteInsert);

CanExecute:

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

hrysbysz1#

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

所以为了回答你的实际问题,我会这样处理Save CommandCanExecute功能:

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属性中所做的那样调用您想要验证的 actual 属性名称,而不是 * null。所以在上面的例子中,你可以在Error属性中调用这样的函数:

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

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

相关问题