Асинхронный метод CanExecute с использованием DelegateCommand в MVVM - PullRequest
0 голосов
/ 21 сентября 2019

У меня есть простой класс DelegateCommand, который выглядит следующим образом:

public class DelegateCommand<T> : System.Windows.Input.ICommand where T : class
{
    public event EventHandler CanExecuteChanged;

    private readonly Predicate<T> _canExecute;
    private readonly Action<T> _execute;

    public DelegateCommand(Action<T> execute) : this(execute, null)
    {
    }

    public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
    {
        this._execute = execute;
        this._canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (this._canExecute == null)
            return true;

        return this._canExecute((T)parameter);
    }

    public void Execute(object parameter)
    {
        this._execute((T)parameter);
    }


    public void RaiseCanExecuteChanged()
    {
        this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

Я использую GalaSoft.MvvmLight для проверки, и обычно я просто делаю что-то подобное в конструкторе View:

this.MyCommand = new DelegateCommand<object>(o => {
   //Do execute stuff
}, o => 
{
   //Do CanExecute stuff
   var validateResult = this.Validator.ValidateAll();
   return validateResult.IsValid;
});

public DelegateCommand<object> MyCommand { get; }

Все это прекрасно работает, когда у меня есть простая проверка, например:

this.Validator.AddRequiredRule(() => this.SomeProperty, "You must select.......");

, но теперь мне нужен метод проверки, который выполняет долго выполняющуюся задачу (в моем случае вызов WebService), поэтому, когда яхочу сделать что-то вроде этого:

this.Validator.AddAsyncRule(async () =>
{
    //Long running webservice call....
    return RuleResult.Assert(true, "Some message");
});

и, следовательно, объявить команду следующим образом:

this.MyCommand = new DelegateCommand<object>(o => {
   //Do execute stuff
}, async o => 
{
   //Do CanExecute ASYNC stuff
   var validateResult = await this.Validator.ValidateAllAsync();
   return validateResult.IsValid;
});

Я немного расстроен, потому что стандартная реализация ICommand не делаетпохоже, имеют дело с асинхронными сценариями.

Не слишком задумываясь, кажется, что вы могли бы потенциально переписать класс DelegateCommand для поддержки такой функциональности, но я смотрел на то, как Prism работает с этим * 1020.* Однако кажется, что они также НЕ поддерживают асинхронные методы CanExecute.

Итак, есть ли способ обойти эту проблему?Или что-то принципиально не работает при попытке запустить метод Async из CanExecute с использованием ICommand?

Ответы [ 2 ]

1 голос
/ 21 сентября 2019

Ответ Энди великолепен и должен быть принят.Версия TL; DR: «Вы не можете сделать CanExecute асинхронным».

Я просто собираюсь ответить на эту часть вопроса здесь:

isЕсть что-то принципиально сломанное при попытке запустить метод Async из CanExecute с использованием ICommand?

Да, определенно есть.

Рассмотрим это с точки зрения используемой вами инфраструктуры пользовательского интерфейса,Когда ОС просит ее нарисовать экран, фреймворк должен отобразить пользовательский интерфейс, и он должен отобразить его сейчас .При рисовании экрана нет времени на сетевые звонки.Представление должно отображаться немедленно в любое время.MVVM - это шаблон, в котором ViewModels являются логическим представлением пользовательского интерфейса, а привязка данных между представлениями и виртуальными машинами означает, что ViewModels должны предоставлять свои данные представлениям немедленно и синхронно.Поэтому свойства ViewModel должны быть обычными значениями данных.

CanExecute - это странно разработанный аспект команд.Логически, он действует как свойство с привязкой к данным (но с аргументом, поэтому я думаю, что он был смоделирован как метод).Когда операционная система просит инфраструктуру пользовательского интерфейса отобразить свое окно, а инфраструктура пользовательского интерфейса просит свое представление визуализировать (например) кнопку, а представление запрашивает ViewModel, отключена ли кнопка, результат должен быть возвращен немедленно и синхронно.

Другими словами: пользовательские интерфейсы по своей сути синхронны.Вам нужно будет принять различные шаблоны UI, чтобы объединить синхронный код UI с асинхронными действиями.Например, пользовательский интерфейс «Загрузка ...» для асинхронной загрузки данных или отключение кнопок с помощью справки по тексту до проверки для асинхронной проверки (как в этом вопросе), или основанная на очереди система асинхронных запросов с уведомлениями об ошибках.

1 голос
/ 21 сентября 2019

Delegatecommand удовлетворяет интерфейсу ICommand.Вы не можете просто изменить подпись материала, и это все равно будет работать.Он также должен выполнять эту функцию в потоке пользовательского интерфейса, чтобы вы не могли его выполнять.

Имейте в виду, что все команды в представлении могут проверяться на выполнение при каждом взаимодействии с пользователем.Даже если бы вы могли сделать предикат асинхронным, его можно было бы неоднократно затронуть с точки зрения производительности.

В этом случае нужно сделать так, чтобы CanExecute быстро возвращал значение bool.

Инкапсулируйте свой код в другом месте, скажем, в Задаче.Вызовите этот код асинхронно и верните результат в bool.Затем поднимите canexecutechanged в вашей делегатной команде, чтобы прочитать значение этого bool.

Возможно, вы также хотите изначально установить этот bool false и проверить его значение в вашем Action.Таким образом, пользователь не может нажимать кнопку, привязанную к ней несколько раз.

В зависимости от объема вводимых данных и количества таких вещей, которые вы можете иметь в представлении, вы можете рассмотреть возможность предпринять шаги, чтобыдорогой процесс выполняется только тогда, когда данные изменились с момента последнего запуска.

Поскольку у вас уже есть предикат canexecute, вы можете изменить реализацию своей команды делегата, чтобы добавить этот защитный бул и сделать его общедоступным.

Примечание

Когда требуется сложная дорогостоящая проверка, часто используются два других подхода.

1) Проверяйте свойства по мере их ввода.Это распределяет валидацию, так что это происходит, когда пользователь заполняет поля и, вероятно, выполняется до того, как он будет готов нажать эту кнопку отправки.

2) Пусть пользователь нажимает кнопку «Отправить», но в этот момент выполняет любые (более дорогие) проверки.и сообщить об ошибках проверки.Это гарантирует, что есть только один чек, когда они нажимают «Отправить».

...