异步MVVM命令 [英] Asynchronous MVVM commands

查看:50
本文介绍了异步MVVM命令的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在关注MSDN杂志中Stephen Cleary撰写的相当出色的系列文章(IAsyncCommand模式.

I have been following the rather excellent series of articles by Stephen Cleary in the MSDN magazine (Patterns for Asynchronous MVVM Applications) and have been using his IAsyncCommand pattern in a "hello world" style application.

但是,他未处理的区域是何时需要传递命令参数(使用此模式).举一个简单的例子,采用身份验证,出于安全原因,密码控件可能未绑定数据.

However, one area that he does not address is when one needs to pass in a Command Parameter (using this pattern). For a trivial example, take Authentication where the Password control may not be data-bound for security reasons.

我想知道是否有人设法让他的AsyncCommand使用参数,如果可以,他们是否会分享他们的发现?

I wonder if anyone had managed to get his AsyncCommand to work with parameters, and if so, would they share their findings?

推荐答案

获取Stephen Cleary的IAsyncCommand模式与在生成要执行的Task时使用带有参数的函数一起使用,只需对其AsyncCommand类和静态帮助器方法进行一些调整即可. .

Getting Stephen Cleary's IAsyncCommand pattern working with functions that take a parameter when producing the Task to be executed would require just a few tweaks to his AsyncCommand class and static helper methods.

从上面链接中的AsyncCommand4示例中找到的类开始,让我们修改构造函数,以使用带有参数(类型为object的对象,这将是Command Parameter)的输入以及CancellationToken的输入的函数,并返回一个任务.我们还需要对ExecuteAsync方法进行一次更改,以便在执行命令时可以将参数传递给此函数.我创建了一个名为AsyncCommandEx的类(如下所示),以演示这些更改.

Starting with his classes found in the AsyncCommand4 sample in the link above, let's modify the constructor to take a function with inputs for a parameter (of type object - this will be the Command Parameter) as well as a CancellationToken and returning a Task. We will also need to make a single change in the ExecuteAsync method so we can pass the parameter into this function when executing the command. I created a class called AsyncCommandEx (shown below) that demonstrates these changes.

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更容易.为了处理不带命令参数的功能的可能组合,我们将方法数量加倍,但结果还不错:

It will also be helpful to update the static AsyncCommand helper class to make the the creation of Command Parameter-aware IAsyncCommands easier. To handle the possible combinations of functions that do or do not take a Command Parameter we will double the number of methods but the result is not too bad:

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

要继续使用Stephen Cleary的示例,您现在可以构建一个AsyncCommand,它采用从Command Parameter(可以绑定到UI)中传入的对象参数:

To continue with Stephen Cleary's sample, you can now build an AsyncCommand that takes an object parameter passed in from the Command Parameter (which can be bound to the UI):

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

这篇关于异步MVVM命令的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆