2016-12-07 2 views
0

Я только начал программировать на C# и хотел что-то сделать с самого начала. Итак, я узнал о шаблоне MVVM и попытался с ним работать. Для этого я использовал делегата от ICommand с лямбда-функциями.делегат Async ICommand для шаблона MVVM с возвращаемым значением

Это прекрасно работает, но поскольку моя программа использует запросы HTML, мне нужно было найти способ сделать вызовы для асинхронных команд.

Так я нашел хорошую реализацию которой я пытался понять, но мне не удалось в какой-то момент ... Вот идет Реализация:

public interface IRaiseCanExecuteChanged 
    { 
     void RaiseCanExecuteChanged(); 
    } 

    // And an extension method to make it easy to raise changed events 
    public static class CommandExtensions 
    { 
     public static void RaiseCanExecuteChanged(this ICommand command) 
     { 
      var canExecuteChanged = command as IRaiseCanExecuteChanged; 

      if (canExecuteChanged != null) 
       canExecuteChanged.RaiseCanExecuteChanged(); 
     } 
    } 

    public class DelegateCommand : DelegateCommand<object> 
    { 
     public DelegateCommand(Action executeMethod) 
      : base(o => executeMethod()) 
     { 
     } 

     public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod) 
      : base(o => executeMethod(), o => canExecuteMethod()) 
     { 
     } 
    } 

    /// <summary> 
    /// A command that calls the specified delegate when the command is executed. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    public class DelegateCommand<T> : ICommand, IRaiseCanExecuteChanged 
    { 
     private readonly Func<T, bool> _canExecuteMethod; 
     private readonly Action<T> _executeMethod; 
     private bool _isExecuting; 

     public DelegateCommand(Action<T> executeMethod) 
      : this(executeMethod, null) 
     { 
     } 

     public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) 
     { 
      if ((executeMethod == null) && (canExecuteMethod == null)) 
      { 
       throw new ArgumentNullException("executeMethod", @"Execute Method cannot be null"); 
      } 
      _executeMethod = executeMethod; 
      _canExecuteMethod = canExecuteMethod; 
     } 

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

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

     bool ICommand.CanExecute(object parameter) 
     { 
      return !_isExecuting && CanExecute((T)parameter); 
     } 

     void ICommand.Execute(object parameter) 
     { 
      _isExecuting = true; 
      try 
      { 
       RaiseCanExecuteChanged(); 
       Execute((T)parameter); 
      } 
      finally 
      { 
       _isExecuting = false; 
       RaiseCanExecuteChanged(); 
      } 
     } 

     public bool CanExecute(T parameter) 
     { 
      if (_canExecuteMethod == null) 
       return true; 

      return _canExecuteMethod(parameter); 
     } 

     public void Execute(T parameter) 
     { 
      _executeMethod(parameter); 
     } 
    } 

    public interface IAsyncCommand : IAsyncCommand<object> 
    { 
    } 

    public interface IAsyncCommand<in T> : IRaiseCanExecuteChanged 
    { 
     Task ExecuteAsync(T obj); 
     bool CanExecute(object obj); 
     ICommand Command { get; } 
    } 

    public class AwaitableDelegateCommand : AwaitableDelegateCommand<object>, IAsyncCommand 
    { 
     public AwaitableDelegateCommand(Func<Task> executeMethod) 
      : base(o => executeMethod()) 
     { 
     } 

     public AwaitableDelegateCommand(Func<Task> executeMethod, Func<bool> canExecuteMethod) 
      : base(o => executeMethod(), o => canExecuteMethod()) 
     { 
     } 
    } 

    public class AwaitableDelegateCommand<T> : IAsyncCommand<T>, ICommand 
    { 
     private readonly Func<T, Task> _executeMethod; 
     private readonly DelegateCommand<T> _underlyingCommand; 
     private bool _isExecuting; 

     public AwaitableDelegateCommand(Func<T, Task> executeMethod) 
      : this(executeMethod, _ => true) 
     { 
     } 

     public AwaitableDelegateCommand(Func<T, Task> executeMethod, Func<T, bool> canExecuteMethod) 
     { 
      _executeMethod = executeMethod; 
      _underlyingCommand = new DelegateCommand<T>(x => { }, canExecuteMethod); 
     } 

     public async Task ExecuteAsync(T obj) 
     { 
      try 
      { 
       _isExecuting = true; 
       RaiseCanExecuteChanged(); 
       await _executeMethod(obj); 
      } 
      finally 
      { 
       _isExecuting = false; 
       RaiseCanExecuteChanged(); 
      } 
     } 

     public ICommand Command { get { return this; } } 

     public bool CanExecute(object parameter) 
     { 
      return !_isExecuting && _underlyingCommand.CanExecute((T)parameter); 
     } 

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

     public async void Execute(object parameter) 
     { 
      await ExecuteAsync((T)parameter); 
     } 

     public void RaiseCanExecuteChanged() 
     { 
      _underlyingCommand.RaiseCanExecuteChanged(); 
     } 
    } 

Теперь у меня есть два вопроса. 1. Хорошо ли это реализовать, как они это сделали? Как я вижу, объект T просто используется как параметр функции, который я могу передать своей лямбда-функции.

Что я могу сделать с этим кодом является следующее:

private ObservableList<string, string> dict; 
    private IAsyncCommand searchCommand; 
    public async Task myFunction() { 
     //changes global variable dict bound to view 
    public IAsyncCommand MyCommand 
    { 
     get 
     { 
      if (myCommand == null) 
      { 
       myCommand = new AwaitableDelegateCommand(
        () => 
        { 
         return myFunction(myParameter); 
        }); 
      } 
     return searchCommand; 
     } 
    } 

где туРипсЫоп редактирует глобальную переменную Dict, который связан с точки зрения с помощью функции MyFunction с добытчиками и сеттеров. Надеюсь, это обычная практика, потому что я просто так ее нашел. Мы возвращаем задачу здесь, и AwaitableDelegateCommand внутренне ждет ее, поэтому нам не нужно заботиться. Но мы не можем получить доступ к задаче или ее результату, правильно?

Итак, как я мог достичь этого, если myFunction не возвращает void, а переменную. Тогда мне нужно было бы получить результат задания (после ожидания async) и назначить его списку.

код будет выглядеть следующим образом:

public async Task<ObservableDictionary<string, string>> myFunction() { 
     var dict = new ObservableDictionary<string, string>(); 
     //do work.... 
     return dict; 
    } 
    public IAsyncCommand MyCommand 
    { 
     get 
     { 
      if (myCommand == null) 
      { 
       //what to do here to assign dict the result of the Task? 
      } 
     return searchCommand; 
     } 
    } 

Это сделает мой код более многократного использования, и я хочу, чтобы это было хорошо :) Надежда кто-то может мне помочь.

EDIT: Поскольку моя проблема не очень ясна, я пытаюсь объяснить ее еще раз.

Мой метод возвращает значение, и я хочу, чтобы мой метод для запуска асинхронной с AwaitableDelegateCommand

Теперь проблема ... А метод, который возвращает значение, как правило, ничего не делать с остальной частью кода, но со значением который возвращается ... В моем случае он заполняет словарь из запроса HTML и возвращает его. Если я теперь запускаю метод async в лямбда, он ничего не делает .... потому что я не могу получить результат задачи в фоновом режиме. Итак, я решил это сейчас, переписав функцию. Вместо того, чтобы заполнять локальную переменную словаря, она заполняет глобальную. Это единственный способ узнать, как получить доступ к данным функции. Я просто хочу знать, есть ли другой способ сделать это, или если это единственный/лучший способ.

Надеюсь, что это лучше.

ответ

1

ICommand привязки в wpf являются огненными и забытыми, то есть они не имеют прямого значения возврата.Эффект команды возвращается из модели представления посредством использования INotifyPropertyChanged:

internal class MyViewModel : INotifyPropertyChanged 
{ 
    public MyViewModel(IServer server) 
    { 
     MyCommand = new DelegateCommand(async() => MyProperty = await server.GetNewData()); 
    } 

    #region Bindings 
    public string MyProperty 
    { 
     get 
     { 
      return _myProperty; 
     } 
     set 
     { 
      _myProperty = value; 
      OnPropertyChanged(); 
     } 
    } 

    public ICommand MyCommand { get; } 
    #endregion 

    #region INotifyPropertyChanged 
    public event PropertyChangedEventHandler PropertyChanged; 
    #endregion 

    #region private 
    private string _myProperty; 

    private void OnPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
    #endregion 
} 

public interface IServer 
{ 
    Task<string> GetNewData(); 
} 

и MyView будет иметь что-то вроде этого:

<Button Content="Click me!" Command={Binding MyCommand}" /> 
<TextBlock Text={Binding MyProperty} /> 

Теперь, если нажать на кнопку, то MyCommand выполняется , запрашивает данные с сервера и обновляет MyProperty после поступления данных. MyProperty запускает обновление MyView «s TextBlock и пользователь видит новые данные ...

EDIT:

В моем случае [метод] заполнит словарь из запроса HTML и возвращает его.

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

MyCommand = new DelegateCommand(async() => _model.Add(await ParseDataFromRequest()); 

Это, как говорится, ParseDataFromRequest не должен быть членом модели представления в любом случае, она принадлежит к логике часть заявки. Модель просмотра должна просто перенаправлять и адаптировать данные для потребления по представлению.

+0

Извините, нажал ввод и не смог отредактировать во времени -.- « Я не использовал функцию OnPropertyChanged до сих пор. Это действительно полезно, если в моем словаре появились новые данные;) Но мой вопрос состоял в том, возвращаемое значение, работающее с командами (т. е. заставить его вести себя как задача?) Я имею в виду, что реализация полностью перегружена: возможно, я могу изменить какой-то код, чтобы это работало? Или это невозможно? Я просто не хочу моя функция для редактирования глобальной переменной, чтобы сделать ее повторно используемой для других программ. – syc

+0

Почему вы не можете использовать функции с возвращаемыми значениями в команде? Просто отбросьте возвращаемое значение ... 'new Command (() => ThisMethodReturnsSomething()) ' – Haukinger

+0

Но мне нужно возвращаемое значение, чтобы назначить его моей глобальной переменной , Я просто хочу сделать его многоразовым.Если я использую функцию без возврата, она ничего не делает, потому что то, что я возвращаю, также является тем, что я создал/отредактировал. Я не хочу, чтобы мой метод был изменен только для использования его в качестве команды. – syc

0

Поскольку вы асинхронно извлекаете данные, я рекомендую использовать что-то вроде NotifyTask<T> в моем Mvvm.Async library. Это основано на старой статье, которую я написал о async data binding.

Е.Г., учитывая:

public async Task<Result> MyFunctionAsync(); 

Вы могли бы ваши ViewModel выглядеть примерно так:

public NotifyTask<Result> MyFunctionResults { get { ... } private set { ... /* raise PropertyChanged */ } } 
public ICommand StartMyFunction { get; } = new DelegateCommand(() => 
{ 
    MyFunctionResults = NotifyTask.Create(MyFunctionAsync()); 
}); 

И тогда ваши привязки данных будет использовать MyFunctionResults.Result, чтобы показать результат. Связывание данных также может использовать MyFunctionResults.IsNotCompleted и другие полезные свойства для отображения/скрытия указателей поворота и ошибок.

Обратите внимание, что здесь не нужна асинхронная команда. Только команда запускает асинхронную операцию, а привязка данных async используется для обновления пользовательского интерфейса при поступлении данных.

Смежные вопросы