2015-06-09 11 views
12

Ho seguito la serie piuttosto eccellente di articoli di Stephen Cleary nella rivista MSDN (Patterns for Asynchronous MVVM Applications) e ho utilizzato il suo modello IAsyncCommand in un'applicazione "ciao mondo".Comandi MVVM asincroni

Tuttavia, un'area che non indirizza è quando è necessario passare un parametro di comando (utilizzando questo modello). Per un esempio banale, effettuare l'autenticazione in cui il controllo della password potrebbe non essere associato ai dati per motivi di sicurezza.

Mi chiedo se qualcuno sia riuscito a far funzionare il suo AsyncCommand con i parametri e, in caso affermativo, avrebbero condiviso i risultati ottenuti?

risposta

14

La modalità IAsyncCommand di Stephen Cleary, che funziona con le funzioni che richiedono un parametro durante la produzione dell'attività da eseguire, richiede solo alcune modifiche alla sua classe AsyncCommand e ai metodi di supporto statici.

A partire dalle sue classi trovate nell'esempio AsyncCommand4 nel link sopra, modifichiamo il costruttore per prendere una funzione con input per un parametro (di tipo oggetto - questo sarà il parametro di comando) così come un metodo di annullamento e ritorno un compito. Dovremo anche effettuare una singola modifica nel metodo ExecuteAsync in modo da poter passare il parametro in questa funzione quando si esegue il comando. Ho creato una classe chiamata AsyncCommandEx (mostrata sotto) che mostra queste modifiche.

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

Sarà anche utile per aggiornare la classe helper AsyncCommand statica per rendere la creazione di IAsyncCommands più facili Comando Parametro-aware. Per gestire le possibili combinazioni di funzioni che fanno o non avere un parametro di comando ci sarà raddoppiare il numero di metodi, ma il risultato non è male:

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

Per continuare con il campione di Stephen Cleary, è ora possibile costruire un AsyncCommand che accetta un parametro oggetto passato dal parametro di comando (che può essere associato all'interfaccia utente):

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