Асинхронные команды MVVM

Я следил за довольно отличной серией статей Стивена Клири в журнале MSDN (шаблоны для асинхронных приложений MVVM) и его IAsyncCommand шаблон в приложении стиля" hello world".

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

интересно, удалось ли кому-нибудь получить его AsyncCommand для работы с параметрами, и если да, будут ли они делиться своими выводами?

1 ответов


получение шаблона Iasynccommand Стивена Клири, работающего с функциями, которые принимают параметр при создании задачи для выполнения, потребует всего нескольких настроек его класса AsyncCommand и статических вспомогательных методов.

начиная с его классов, найденных в Примере AsyncCommand4 в приведенной выше ссылке, Давайте изменим конструктор, чтобы взять функцию с входными данными для параметра (типа object-это будет Параметр команды), а также CancellationToken и returning задача. Нам также нужно будет внести одно изменение в метод ExecuteAsync, чтобы мы могли передать параметр в эту функцию при выполнении команды. Я создал класс AsyncCommandEx (показано ниже), который демонстрирует эти изменения.

public class AsyncCommandEx<TResult> : AsyncCommandBase, INotifyPropertyChanged
{
    private readonly CancelAsyncCommand _cancelCommand;
    private readonly Func<object, CancellationToken, Task<TResult>> _command;
    private NotifyTaskCompletion<TResult> _execution;

    public AsyncCommandEx(Func<object, CancellationToken, Task<TResult>> command)
    {
        _command = command;
        _cancelCommand = new CancelAsyncCommand();
    }

    public ICommand CancelCommand
    {
        get { return _cancelCommand; }
    }

    public NotifyTaskCompletion<TResult> Execution
    {
        get { return _execution; }
        private set
        {
            _execution = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public override bool CanExecute(object parameter)
    {
        return (Execution == null || Execution.IsCompleted);
    }

    public override async Task ExecuteAsync(object parameter)
    {
        _cancelCommand.NotifyCommandStarting();
        Execution = new NotifyTaskCompletion<TResult>(_command(parameter, _cancelCommand.Token));
        RaiseCanExecuteChanged();
        await Execution.TaskCompletion;
        _cancelCommand.NotifyCommandFinished();
        RaiseCanExecuteChanged();
    }

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    private sealed class CancelAsyncCommand : ICommand
    {
        private bool _commandExecuting;
        private CancellationTokenSource _cts = new CancellationTokenSource();

        public CancellationToken Token
        {
            get { return _cts.Token; }
        }

        bool ICommand.CanExecute(object parameter)
        {
            return _commandExecuting && !_cts.IsCancellationRequested;
        }

        void ICommand.Execute(object parameter)
        {
            _cts.Cancel();
            RaiseCanExecuteChanged();
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void NotifyCommandStarting()
        {
            _commandExecuting = true;
            if (!_cts.IsCancellationRequested)
                return;
            _cts = new CancellationTokenSource();
            RaiseCanExecuteChanged();
        }

        public void NotifyCommandFinished()
        {
            _commandExecuting = false;
            RaiseCanExecuteChanged();
        }

        private void RaiseCanExecuteChanged()
        {
            CommandManager.InvalidateRequerySuggested();
        }
    }
}

также будет полезно обновить статический вспомогательный класс AsyncCommand, чтобы упростить создание Iasynccommands с учетом параметров команды. Для обработки возможных комбинаций функций, которые принимают или не принимают команды Параметр мы удвоим количество методов, но результат не так уж плох:

public static class AsyncCommandEx
{
    public static AsyncCommandEx<object> Create(Func<Task> command)
    {
        return new AsyncCommandEx<object>(async (param,_) =>
                                              {
                                                  await command();
                                                  return null;
                                              });
    }

    public static AsyncCommandEx<object> Create(Func<object, Task> command)
    {
        return new AsyncCommandEx<object>(async (param, _) =>
        {
            await command(param);
            return null;
        });
    }

    public static AsyncCommandEx<TResult> Create<TResult>(Func<Task<TResult>> command)
    {
        return new AsyncCommandEx<TResult>((param,_) => command());
    }

    public static AsyncCommandEx<TResult> Create<TResult>(Func<object, Task<TResult>> command)
    {
        return new AsyncCommandEx<TResult>((param, _) => command(param));
    }

    public static AsyncCommandEx<object> Create(Func<CancellationToken, Task> command)
    {
        return new AsyncCommandEx<object>(async (param, token) =>
                                              {
                                                  await command(token);
                                                  return null;
                                              });
    }

    public static AsyncCommandEx<object> Create(Func<object, CancellationToken, Task> command)
    {
        return new AsyncCommandEx<object>(async (param, token) =>
        {
            await command(param, token);
            return null;
        });
    }

    public static AsyncCommandEx<TResult> Create<TResult>(Func<CancellationToken, Task<TResult>> command)
    {
        return new AsyncCommandEx<TResult>(async (param, token) => await command(token));
    }

    public static AsyncCommandEx<TResult> Create<TResult>(Func<object, CancellationToken, Task<TResult>> command)
    {
        return new AsyncCommandEx<TResult>(async (param, token) => await command(param, token));
    }
}

чтобы продолжить пример Стивена Клири, теперь вы можете создать AsyncCommand, который принимает параметр объекта, переданный из параметра Command (который может быть привязан к пользовательскому интерфейсу):

CountUrlBytesCommand = AsyncCommandEx.Create((url,token) => MyService.DownloadAndCountBytesAsync(url as string, token));