【问题标题】:Asynchronous MVVM commands异步 MVVM 命令
【发布时间】:2015-08-24 18:41:22
【问题描述】:

我一直在关注 Stephen Cleary 在 MSDN 杂志 (Patterns for Asynchronous MVVM Applications) 上相当出色的系列文章,并一直在“hello world”风格的应用程序中使用他的 IAsyncCommand 模式。

但是,他没有解决的一个领域是需要传入命令参数(使用此模式)。举个简单的例子,在密码控件出于安全原因可能没有数据绑定的情况下进行身份验证。

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

【问题讨论】:

    标签: wpf asynchronous mvvm async-await task-parallel-library


    【解决方案1】:

    让 Stephen Cleary 的 IAsyncCommand 模式与在生成要执行的任务时接受参数的函数一起工作,只需要对他的 AsyncCommand 类和静态辅助方法进行一些调整。

    从上面链接中的 AsyncCommand4 示例中找到的他的类开始,让我们修改构造函数以获取一个函数,该函数带有一个参数(对象类型 - 这将是命令参数)以及一个 CancellationToken 并返回一个任务。我们还需要在 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 帮助器类也有助于更轻松地创建命令参数感知 IAsyncCommand。为了处理接受或不接受命令参数的函数的可能组合,我们将方法数量加倍,但结果还不错:

    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,它接受从命令参数(可以绑定到 UI)传入的对象参数:

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

    【讨论】:

      猜你喜欢
      • 2015-02-26
      • 1970-01-01
      • 2015-10-13
      • 2010-09-14
      • 2011-01-23
      • 2013-01-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多