Привязка команды WPF с проверкой ввода - как включить кнопку сохранения, только если все входные данные действительны

В моей 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}"/>

Команда Вставки:

public RelayCommand InsertCommand { get; internal set; }

В конструкторе ViewModel:

InsertCommand = new RelayCommand(ExecuteInsert, CanExecuteInsert);

Можно выполнить:

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

person Saša Ćetković    schedule 16.04.2014    source источник
comment
Покажите InsertCommand, как он был инициализирован, и его CanExecute.   -  person 123 456 789 0    schedule 16.04.2014
comment
@lll Добавил в конец.   -  person Saša Ćetković    schedule 16.04.2014


Ответы (1)


Вы действительно не добавили достаточно кода, чтобы мы могли точно сказать вам, в чем заключается ваша проблема. Тем не менее, вы придерживаетесь правильного подхода. Я также использую интерфейс 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 следующим образом:

    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;

Еще раз, я написал слишком много информации... Я просто надеюсь, что все это имеет смысл для вас, и вы не собираетесь возвращаться с десятками новых вопросов. Надеюсь, это достаточно ясно.

person Sheridan    schedule 16.04.2014
comment
Я начал с WPF совсем недавно, и во всех примерах для IDataErrorInfo свойство Error было реализовано именно так. Это работает для проверки, поэтому я не думал, что это проблема. Я попробую ваши предложения, когда вернусь домой. - person Saša Ćetković; 16.04.2014
comment
Это сработало, спасибо! Здесь у меня тоже была логическая ошибка return !string.IsNullOrEmpty(Error); Это должно быть без отрицания, конечно. - person Saša Ćetković; 17.04.2014