Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Commands in MVVM

Anna | 17.06.2014 | нет комментариев
  • Пример 1 – Примитивное применение Command
  • Пример 2 — Примитивное применение Command в паре с лямда функциями
  • Пример 3 — Примитивное применение Command с параметрами
  • Пример 4 – Включение и отключение Command
  • Пример 5 – Command вызывающие события
  • Пример 6 – Асинхронные Command
  • Пример 7 — Асинхронные Command обновляющие интерфейс пользователя (UI)
  • Пример 8 — Асинхронные Command с вероятность отмены
  • Пример 9 – Привязка событий к Command
  • Как это работает – Класс Command
  • Как это работает – Класс асинхронных Command
  • Как это работает – Класс привязки дынных Command

Введение

На примере приложения, использующего паттерн MVVM (Model View View-Model) разглядим работу с командами (Commands).

Примеры используемые мной имеют идентичную логику самостоятельно от платформы: WPF, SilverLight, Windows Phone. В первую очередь разглядим несколько вариантов применения команд в любом MVVM приложении. Вначале примеры, потом разбор полетов.

Каждый использованный код представляет собой часть моей MVVM библиотеки Apex, впрочем все части будут предоставлены в полном объеме, что дозволит Вам легко интегрировать данные способы в свой план либо библиотеку, либо дозволено легко подключить ссылку на Apex и сразу приступать к работе.

image

Скриншот 1: Команды в WPF

image

Скриншот 2: Команды в SilverLight

image

Скриншот3: Команды в Windows Phone
Что такое команды?

Команды являются привязанными объектами, что разрешает поделить логику и пользовательский интерфейс друг от друга.

Если рассматривать команды больше детально, то они представляют из себя следующее:

  • Команды представляют собой объекты, реализующие интерфейс ICommand
  • Обыкновенно команды связанны с какой либо функцией
  • Элементы пользовательского интерфейса привязываются к командам — кода интерфейс активируется пользователем, то выполняется команда — вызывается соответствующая функция
  • Команды знают, включены ли они либо нет
  • Функции могут отключать команды – механическое отключение всех пользовательских элементов ассоциированных с ней
  • На сомом деле существует уйма разных использований команд. Скажем применение команд для создания асинхронных функций, обеспечивающих логику, которая может быть проверена с/без помощи применения пользовательского интерфейса и др.
Примеры

Для начала разглядим случаи, когда появляется надобность применения команд. Всякий случай будет рассмотрен больше детально дальше в соответствующем пункте.

Значимо: всякий View Model в этой части примеров содержит отслеживаемые коллекции (observable collection) строк с именем «Messages» — всякий пример приложения показывает сообщение, отображающий информацию о том, что происходит.

Разгляди базовую модель вида (View Model):

public class MainViewModel : ViewModel
{
    public MainViewModel()
    {
    }

    private ObservableCollection<string> messages = new ObservableCollection<string>();

    public ObservableCollection<string> Messages
    {
      get { return messages; }
    }
}

Данная модель вида наследует от ViewModel’, которая в свою очередь реализует интерфейс INotifyPropertyChanged, впрочем Вы можете применять всякий иной доступный тип реализации модели вида.

Пример 1 – Примитивное применение Command

Цель: Вызвать соответствующую функцию модели вида при нажатии пользователем на элемент.

Теперьразглядим самый примитивный пример применения команд. В первую очередь добавим объект Command к нашей модели вида – ассоциированную функцию, которая вызывается при вызове команды:

public class MainViewModel : ViewModel
{
    public MainViewModel()
    {
      //  Создание команды - вызов DoSimpleCommand.
      simpleCommand = new Command(DoSimpleCommand);
    }

    /// <summary>
    /// The SimpleCommand function.
    /// </summary>
    private void DoSimpleCommand()
    {
      //  Добавляем сообщение
      Messages.Add("Вызываем 'DoSimpleCommand'.");
    }

    /// <summary>
    /// Легкой объект команды
    /// </summary>
    private Command simpleCommand;

    /// <summary>
    /// Приобретение легкой команды
    /// </summary>
    public Command SimpleCommand
    {
      get { return simpleCommand; }
    }
}

Сейчас добавим команду в качество «Command’» элемента управления кнопка(button).

<Button Content="Simple Command" Command="{Binding SimpleCommand}" />

Вот и все. Самый примитивный пример сделан. Мы прикрепили объект команды к свойству Command элемента интерфейса. При активизации элемента, то есть при нажатии на кнопку вызывается команда( вызывается соответствующая ей функция DoSimpleCommand).

Пример 2 – Примитивное применение Command в паре с лямда функциями

Цель: Вызвать функцию модели вида во время активизации либо нажатия на элемент интерфейса.

Впрочем это дюже простая функция, которую я выберу не прописывать в очевидном виде и вместо использую лямда выражение.

Во всех примерах данного мануала Вы можете как и очевидно создавать функцию, скажем, как в Пример 1, либо применять лямды, что несколько сократит код и сделает его больше изящным. Выбор за Вами.

public MainViewModel()
{
      //  Создание команды с поддержкой лямда выражения. Никаких дополнительных функций огромнее не нужно.
      lambdaCommand = new Command(
        () =>
        {
          Messages.Add("Вызов команды с поддержкой лямда. Никаких дополнительных функций огромнее не нужно.
");
        });
}

/// <summary>
/// The command object.
/// </summary>
private Command lambdaCommand;

/// <summary>
/// Gets the command.
/// </summary>
public Command LambdaCommand
{
  get { return lambdaCommand; }
}

Вновь повторим процесс привязки команды к свойству Command нашей кнопки:

<Button Content="Lambda Command" Command="{Binding LambdaCommand}" />
Пример 3 – Примитивное применение Command с параметрами

Цель: Создание команды, которая использует параметр, передаваемый ей при привязке данных.

В этом примере используем объект Command (так же есть вероятность применять объект AsynchronousCommand, что мы увидим чуть позже). Переданный параметр может быть использован в вызываемой функции.

public class MainViewModel : ViewModel
{
    public MainViewModel()
    {
      //  Создаем параметризированную команду
      parameterizedCommand = new Command(DoParameterisedCommand);
    }

    /// <summary>
    /// Функция команды
    /// </summary>
    private void DoParameterisedCommand(object parameter)
    {
      Messages.Add("Вызов параметризированной команды – передаваемый параметр: '"   
                   parameter.ToString()   "'.");
    }

    /// <summary>
    /// The command object.
    /// </summary>
    private Command parameterizedCommand;

    /// <summary>
    /// Gets the command.
    /// </summary>
    public Command ParameterisedCommand
    {
      get { return parameterizedCommand; }
    }
}

Повторим привязку команды к кнопке либо любому иному элементу. Так же не позабудем добавить параметр:

<Button Content="Parameterized Command" Command="{Binding ParameterizedCommand}" CommandParameter={Binding SomeObject} />

Всюду, где применяются команды допустимо передавать в них параметры. При создании команды дозволено применять Action (функция команды без параметров) либо Action<object> (функция кома> Значимо: в моем представлении, DataContext с именем viewModel:

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        viewModel.EventsCommand.Executing  = 
          new Apex.MVVM.CancelCommandEventHandler(EventsCommand_Executing);
        viewModel.EventsCommand.Executed  = 
          new Apex.MVVM.CommandEventHandler(EventsCommand_Executed);
    }

    void EventsCommand_Executed(object sender, Apex.MVVM.CommandEventArgs args)
    {
        viewModel.Messages.Add("Команда завершила свое выполнение. Говорит  View!");
    }

    void EventsCommand_Executing(object sender, Apex.MVVM.CancelCommandEventArgs args)
    {
        if (MessageBox.Show("Отменить команду?", 
                 "Cancel?", 
                 MessageBoxButton.YesNo) == MessageBoxResult.Yes)
            args.Cancel = true;
    }
}

Наконец то мы подобрались к пониманию каждой мощи реализации команд. Есть вероятность подписываться на события Executed и Executing в представлении (либо даже в ином ViewModel, либо объекте), а так же знаем когда оно появляется. Событие Executing передает объект CancelCommandEventArgs — это и есть качество с именем «Cancel». Если его установить в true — то команда не выполнится. Оба объекта CommandEventArgs и CancelCommandEventArgs поддерживает еще одно качество – параметр. Это тот параметр, тот, что может быть передан в Command(если он вообще есть).

Пример 6 – Асинхронные Command

Цель: Если команды выполняются довольно длинно, то блокируется интерфейс. Должна быть вероятность запускать их асинхронно.

Дозволено сделать что-то как бы фонового процесса и там запустить команду. Впрочем сразу появляется несколько вопросов:
— Что будет, если мы захотим обновить ViewModel в функции, находящейся в потоке? Мы не можем это сделать, не прибегая к действиям в интерфейсе пользователя.
— Как удостовериться, что одна команда не вызвала нечаянно несколько потоков при происхождении только одного воздействия на интерфейс?
— Как не замусорить View Model, если будет уйма команд, которые обязаны будут выполняться в отдельных потоках?
— Как обеспечить совместимость с WP и Silverlight, если имеются разные опции для потоков на всякой системе?

Объект AsynchronousCommand был сделан с учетом всех этих нюансов и не только. Знакомимся:

public class MainViewModel : ViewModel
{
    public MainViewModel()
    {
      //  Создание асинхронной команды
      asyncCommand1 = new AsynchronousCommand(
          () =>
          {
            for (int i = 1; i <= 10; i  )
            {
              //  Отчет о ходе работы.
              asyncCommand1.ReportProgress(() => { Messages.Add(i.ToString()); });

              System.Threading.Thread.Sleep(200);
            }
          });
    }

    /// <summary>
    /// The command object.
    /// </summary>
    private AsynchronousCommand asyncCommand1;

    /// <summary>
    /// Gets the command.
    /// </summary>
    public AsynchronousCommand AsyncCommand1
    {
      get { return asyncCommand1; }
    }
}

Привязка к элементу XAML:

<Button Content="Asynchronous Command" Command="{Binding AsyncCommand1}" />

Позже вызова команды вызывается соответствующая функция, которую мы указали с поддержкой лямда выражения, которая в свою очередь вызывается в отдельном потоке из пула потоков.

Если же появляется надобность что-то сделать с объектами View Model (которые могут быть связаны с элементами пользовательского интерфейса), мы можем воспользоваться функцией ReportProgress:

asyncCommand1.ReportProgress(() => { Messages.Add(i.ToString()); });

ReportProgress гарантирует, что переданный код будет запущен в необходимом потоке, что разрешает достаточно легко изменять интерфейс пользователя при необходимости.

Пример 7 – Асинхронные Command обновляющие интерфейс пользователя (UI)

Цель: Команда выполняется дольно длинно. Показать прогрессбар.

В AsynchronousCommand есть качество с именем «IsExecuting». Если оно установлено в true — значит команда в процессе выполнения. Так как AsynchronousCommand реализует интерфейс INotifyPropertyChanged, тот, что подразумевает, что у нас есть вероятность привязаться к этому свойству и показывать процесс выполнения.

public class MainViewModel : ViewModel
{
    public MainViewModel()
    {
      //  Создание асинхронной команды
      asyncCommand2 = new AsynchronousCommand(
          () =>
          {
            for (char c = 'A'; c <= 'Z'; c  )
            {
              //  Уведомлять о прогрессе
              asyncCommand2.ReportProgress(() => { Messages.Add(c.ToString()); });

              System.Threading.Thread.Sleep(100);
            }
          });
    }

    /// <summary>
    /// The command object.
    /// </summary>
    private AsynchronousCommand asyncCommand2;

    /// <summary>
    /// Gets the command.
    /// </summary>
    public AsynchronousCommand AsyncCommand2
    {
      get { return asyncCommand2; }
    }
}

Команду свяжем с кнопкой. А качество IsExecuting привяжем к иному элементу интерфейса, скажем, StackPanel, в котором будут находится соответственно TextBlock и ProgressBar:

<Button Content="Asynchronous Command" Command="{Binding AsyncCommand2}" 
        Visibility="{Binding AsyncCommand2.IsExecuting, 
          Converter={StaticResource BooleanToVisibilityConverter}, 
          ConverterParameter=Invert}" />

<StackPanel Visibility="{Binding AsyncCommand2.IsExecuting, 
         Converter={StaticResource BooleanToVisibilityConverter}}">
  <TextBlock Text="The command is running!" />
  <ProgressBar Height="20" Width="120" IsIndeterminate="True" />
</StackPanel>

В этом примере, как только команда начинает выполняться кнопка пропадает, и возникают надпись и прогрессбар. Учтите, что мы осуществляем привязку к свойству IsExecuting команды.

asyncCommand1.ReportProgress(() => { Messages.Add(i.ToString()); });

На заметку: Параметр Invert может быть передан в BooleanToVisilityConverter, так как он представляет собой расширенную версию стандартного BooleanToVisibilityConverter, тот, что определен в Apex.Converters. Инвертирует итог. Дюже пригодная штучка в определенных моментах.

Пример 8 – Асинхронные Command с вероятностью отмены

Цель: Дозволить пользователю отменять процесс выполнения асинхронной команды.

Воспользуемся некоторыми вероятностями AsynchronousCommand. Всякий объект AsynchronousCommand так же содержит и команду с именем CancelCommand. И она может быть привязана к UI пользователя либо вызвана внутри кода в необходимом месте. При вызове этой команды, качество IsCancellationRequested объекта AsynchronousCommand устанавливается в true (учтите, что качество использует INotifyPropertyChanged и у Вас есть вероятность привязаться к нему). Вы можете периодично вызывать функцию CancelIfRequested, и если внезапно она вернет true, то команда остановится.

public class MainViewModel : ViewModel
{
    public MainViewModel()
    {
       //  Создание команды с вероятностью остановки
       cancellableAsyncCommand = new AsynchronousCommand(
         () => 
           {
             for(int i = 1; i <= 100; i  )
             {
               //  Остановить?
               if(cancellableAsyncCommand.CancelIfRequested())
                 return;

               //  Прогрессбар.
               cancellableAsyncCommand.ReportProgress( () => { Messages.Add(i.ToString()); } );

               System.Threading.Thread.Sleep(100);
             }
           });
    }

    /// <summary>
    /// The command object.
    /// </summary>
    private AsynchronousCommand cancellableAsyncCommand;

    /// <summary>
    /// Gets the command.
    /// </summary>
    public AsynchronousCommand CancellableAsyncCommand
    {
      get { return cancellableAsyncCommand; }
    }
}

Привяжем команду к кнопке, а качество IsExecuting к StackPanel:

<Button Content="Cancellable Async Command" 
    Command="{Binding CancellableAsyncCommand}" 
    Visibility="{Binding CancellableAsyncCommand.IsExecuting, 
             Converter={StaticResource BooleanToVisibilityConverter},
    ConverterParameter=Invert}" />

<StackPanel Visibility="{Binding CancellableAsyncCommand.IsExecuting, 
      Converter={StaticResource BooleanToVisibilityConverter}}">
  <TextBlock Margin="4" Text="The command is running!" />
  <ProgressBar Margin="4" Height="20" 
     Width="120" IsIndeterminate="True" />
  <Button Margin="4" Content="Cancel" 
      Command="{Binding CancellableAsyncCommand.CancelCommand}" />
</StackPanel>

В этом примере показываем кнопку Cancel во время выполнения команды. Эта кнопка связана со свойством CancellableAsyncCommand.CancelCommand. Вследствие тому, что мы используем функцию CancelIfRequested, у нас есть прекрасная вероятность остановить выполнение асинхронной команды.

На заметку: При остановке выполнения асинхронной команды событие Executed не вызывается. Впрочем взамен него вызывается событие Cancelled, которое может принимать те же самые параметры.

Пример 9 – Привязка событий к Command

Цель: Вызвать команду при активизации пользовательского элемента, у которого не установлено качество Command, но заданно событие.

В этом случае дозволено воспользоваться свойством прикрепления EventBindings. Оно располагается в классе Apex.Commands. EventBindings принимает EventBindingCollection, которое представляет собой примитивную коллекцию объектов EventBinding. Всякий EventBinding принимает два параметра: имя события и имя команды, которую необходимо вызвать.

public class MainViewModel : ViewModel
{
    public MainViewModel()
    {
      //  Создаем прикрепленное качество
      EventBindingCommand = new Command(
        () =>
        {
          Messages.Add("Команда вызвана событием.");
        });
    }

    /// <summary>
    /// The command object.
    /// </summary>
    private Command eventBindingCommand;

    /// <summary>
    /// Gets the command.
    /// </summary>
    public Command EventBindingCommand
    {
      get { return eventBindingCommand; }
    }
}

Связка команды с событием происходит дальнейшим образом:

<Border Margin="20" Background="Red">

  <!—привязка команды  EventBindingCommand к событию  MouseLeftButtonDown. -->
  <apexCommands:EventBindings.EventBindings>
     <apexCommands:EventBindingCollection>
        <apexCommands:EventBinding EventName="MouseLeftButtonDown" 
            Command="{Binding EventBindingCommand}" />
     </apexCommands:EventBindingCollection>
  </apexCommands:EventBindings.EventBindings>

  <TextBlock VerticalAlignment="Center" 
     HorizontalAlignment="Center" Text="Left Click on Me" 
     FontSize="16" Foreground="White" />

</Border>

При применении EventBindings допускается присоединение всякий команды к любому событию.

Как это работает – Класс Command

Разглядим класс Command, тот, что применялся в примерах 1-5.

/// <summary>
/// Класс ViewModelCommand – реализующий интерфейс ICommand, вызывает надобную функцию.
/// </summary>
public class Command : ICommand
{
    /// <summary>
    /// Инициализация нового экземпляра класса без параметров <see cref="Command"/>.
    /// </summary>
    /// <param name="action">Действие.</param>
    /// <param name="canExecute">Если установлено в<c>true</c> [can execute] (выполнение разрешено).</param>
    public Command(Action action, bool canExecute = true)
    {
        //  Set the action.
        this.action = action;
        this.canExecute = canExecute;
    }

    /// <summary>
    /// Инициализация нового экземпляра класса с параметрами <see cref="Command"/> class.
    /// </summary>
    /// <param name="parameterizedAction">Параметризированное действие.</param>
    /// <param name="canExecute"> Если установлено в <c>true</c> [can execute](выполнение разрешено).</param>
    public Command(Action<object> parameterizedAction, bool canExecute = true)
    {
        //  Set the action.
        this.parameterizedAction = parameterizedAction;
        this.canExecute = canExecute;
    }

В первую очередь создаем два перегруженных конструктора, в тот, что передается действие без параметров: Action, либо с параметрами: Action<object>, где object — тип.

Дальше задаем флаг canExecute, отвечающий за вероятность выполнения команды. Позже метаморфозы флага canExecute нужно вызвать canExecuteChanged.

/// <summary>
/// Действие(либо параметризованное действие) которое вызывается при активизации команды.
/// </summary>
protected Action action = null;
protected Action<object> parameterizedAction = null;

/// <summary>
/// Будевое значение, отвечающие за вероятность выполнения команды.
/// </summary>
private bool canExecute = false;

/// <summary>
/// Установка /  приобретение значения, отвечающего за вероятность выполнения команды
/// </summary>
/// <value>
///     <c>true</c> если выполнrmark!    ///  <c>true</c> команда может выполняться.</param>
    public AsynchronousCommand(Action action, bool canExecute = true) 
      : base(action, canExecute)
    { 
      //  Инициализация команды
      Initialise();
    }

    /// <summary>
    /// Инициализация нового экземпляра класса с параметрами<see cref="AsynchronousCommand"/>.
    /// </summary>
    /// <param name="parameterizedAction">Параметризированное действие.</param>
    /// <param name="canExecute"> Если установлено в <c>true</c> [can execute] (может выполняться).</param>
    public AsynchronousCommand(Action<object> parameterizedAction, bool canExecute = true)
      : base(parameterizedAction, canExecute) 
    {

      //  Инициализация команды
      Initialise(); 
    }

Вследствие реализации интерфейса INotifyPropertyChanged возникает вероятность уведомления при изменении переменной IsExecuting. Так как оба конструктора вызывают способ Initialise, разглядим его больше детально:

/// <summary>
/// Команда отмены
/// </summary>
private Command cancelCommand;

/// <summary>
/// Приобретение команды отмены.
/// </summary>
public Command CancelCommand
{
  get { return cancelCommand; }
}

/// <summary>
/// Получить/Установить значение, указывающее, поступила ли команда отмены
/// </summary>
/// <value>
///     <c>true</c> если есть запрос на отмену; запроса нет -  <c>false</c>.
/// </value>
public bool IsCancellationRequested
{
  get
  {
    return isCancellationRequested;
  }
  set
  {
    if (isCancellationRequested != value)
    {
      isCancellationRequested = value;
      NotifyPropertyChanged("IsCancellationRequested");
    }
  }
}

/// <summary>
/// Инициализация экземпляра
/// </summary>
private void Initialise()
{
  //  Конструктор команды отмены
  cancelCommand = new Command(
    () =>
    {
      //  Set the Is Cancellation Requested flag.
      IsCancellationRequested = true;
    }, true);
}

Все, что здесь выполняется, невзирая на обилие кода — легко установка флага IsCancellationRequested в значение true. Инициализация создает объект и разрешает получить к нему доступ. Так же имеется качество IsCancellationRequested, которое оповещает, когда оно меняет свое состояние.

Так же нужно знать, когда команда в процессе выполнения. Добавим дальнейший код:

/// <summary>
/// Флаг, отображающий, что команда в процессе выполнения.
/// </summary>
private bool isExecuting = false;

/// <summary>
/// Получение/Установка флага, тот, что показывает, что команда в процессе выполнения..
/// </summary>
/// <value>
///     <c>true</c> если в процессе выполнения; напротив <c>false</c>.
/// </value>
public bool IsExecuting
{
  get
  {
    return isExecuting;
  }
  set
  {
    if (isExecuting != value)
    {
      isExecuting = value;
      NotifyPropertyChanged("IsExecuting");
    }
  }
}

Продолжим. Так как у нас есть вероятность отмены добавим события Cancelled и PropertyChanged (реализующее интерфейс INotifyPropertyChanged):

/// <summary>
/// The property changed event.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;

/// <summary>
/// Появляется, когда команда отменена.
/// </summary>
public event CommandEventHandler Cancelled;

Так же изменился и способ DoExecute.

/// <summary>
/// Выполнение команды.
/// </summary>
/// <param name="param">Параметр.</param>
public override void DoExecute(object param)
{
  //  Если теснее в процессе выполнения, тоне продолжаем.
  if (IsExecuting)
    return;

  //  Вызов выподняющейся команды, что разрешает отменить ее выполнение.
  CancelCommandEventArgs args = 
     new CancelCommandEventArgs() { Parameter = param, Cancel = false };
  InvokeExecuting(args);

  //  Если отмена -  прерываем.
  if (args.Cancel)
    return;

  //  В процессе выполнения.
  IsExecuting = true;

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

//  Сохранение вызванного диспатчера.
#if !SILVERLIGHT
      callingDispatcher = Dispatcher.CurrentDispatcher;
#else
      callingDispatcher = System.Windows.Application.Current.RootVisual.Dispatcher;
#endif

Мы обязаны сохcatch (Exception e) { string s = e.ToString(); } } /// <summary> /// Proxy to actually fire the event. /// </summary> /// <param name=”o”>The object.</param> /// <param name=”e”>The <see /// cref=”System.EventArgs”/> instance /// containing the event data.</param> private void EventProxy(object o, EventArgs e) { #if SILVERLIGHT // If we’re in Silverlight, we have NOT inherited the data context // because the EventBindingCollection is not a framework element and // therefore out of the logical tree. However, we can set it here // and update the bindings – and it will all work. DataContext = ParentElement != null ? ParentElement.DataContext : null; var bindingExpression = GetBindingExpression(EventBinding.CommandProperty); if(bindingExpression != null) bindingExpression.UpdateSource(); bindingExpression = GetBindingExpression(EventBinding.CommandParameterProperty); if (bindingExpression != null) bindingExpression.UpdateSource(); #endif if (Command != null) Command.Execute(CommandParameter); }

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB