2015-06-09 12 views
12

Śledziłem dość doskonałą serię artykułów Stephena Cleary w magazynie MSDN (Patterns for Asynchronous MVVM Applications) i używam jego wzoru IAsyncCommand w aplikacji stylu "cześć świat".Asynchroniczne polecenia MVVM

Jednak jeden obszar, który nie jest adresowany, to sytuacja, w której trzeba przekazać parametr polecenia (używając tego wzoru). Aby uzyskać prosty przykład, weź uwierzytelnianie, gdy kontrola hasła może nie być związana z danymi ze względów bezpieczeństwa.

Zastanawiam się, czy komuś udało się uzyskać jego AsyncCommand do pracy z parametrami, a jeśli tak, czy podzielą się swoimi odkryciami?

Odpowiedz

14

Uzyskanie schematu IAsyncCommand Stephena Cleary'a z funkcjami, które pobierają parametr podczas tworzenia zadania do wykonania, wymagałoby tylko kilku poprawek w jego klasie AsyncCommand i statycznych metodach pomocniczych.

Zaczynając od jego klas znalezionych w przykładzie AsyncCommand4 w powyższym linku, zmodyfikujmy konstruktora tak, aby przyjmował funkcję z wejściami dla parametru (typu object - będzie to parametr Command), a także CancellationToken i return zadanie. Będziemy również musieli dokonać pojedynczej zmiany w metodzie ExecuteAsync, abyśmy mogli przekazać ten parametr do tej funkcji podczas wykonywania polecenia. Stworzyłem klasę AsyncCommandEx (pokazaną poniżej), która demonstruje te zmiany.

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

będzie również pomocny w celu zaktualizowania statyczną klasę AsyncCommand pomocnika do dokonania stworzenie Polecenie Parametr-świadomych IAsyncCommands łatwiejsze. Aby poradzić sobie z możliwych kombinacji funkcji, które wykonują lub nie biorą parametr polecenia będziemy podwoić liczbę metod, ale wynik nie jest tak źle:

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

aby kontynuować próby Stephen Cleary, można teraz budować AsyncCommand, która pobiera parametr obiektu przekazywany z parametru polecenia (który może być powiązany z interfejsem użytkownika):

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