Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
EF StartUp Manager - программа для слежением за программами, которые запускаются вместе с загрузкой операционной системы...
EF Mailbox Manager - программа для периодической проверки неограниченного количество аккаунтов электронной почты. Вы мож...
Программа позволяет узнать, чем занято место на жестком диске. Сканирует папки очень быстро и выводит наглядную диаграмм...
HashMyFiles - это маленькая утилита, которая предоставляет возможность рассчета контрольных сумм MD5 и SHA1 для одного и...
Файловый менеджер с очень малыми системными требованиями, но тем не менее с большими возможностями. Программа имеет ориг...
OSzone.net Microsoft Разработка приложений .NET Framework Поддержка команд в WPF на основе шаблона конечных автоматов RSS

Поддержка команд в WPF на основе шаблона конечных автоматов

Текущий рейтинг: 4 (проголосовало 1)
 Посетителей: 1991 | Просмотров: 2139 (сегодня 0)  Шрифт: - +

Windows Presentation Foundation (WPF) имеет мощную инфраструктуру поддержки команд, которая позволяет разделять UI и логику команд. При использовании проектировочного шаблона Model-View-ViewModel (MVVM) команда предоставляется в ViewModel как свойство, которое реализует интерфейс ICommand. Элементы управления в View связываются с этими свойствами. Когда пользователь взаимодействует с таким элементом управления, тот выполняет команду.

Как всегда, дьявол кроется в деталях. Настоящая трудность не в том, чтобы выполнить команду, а в том, чтобы гарантировать ее выполнение, когда ViewModel находится в допустимом состоянии для этой команды. Как правило, проверка «can execute» («можно выполнять») реализуется условным выражением, использующим локальные переменные, например IsLoading, CanDoXYZ, SomeObject != null и т. д. Каждое новое состояние требует дополнительных условий, которые нужно проверять, поэтому данная стратегия быстро становится чрезмерно сложной.

Конечный автомат решает проблему «can execute», так как ограничивает операции, допустимые для конкретного состояния. Связывание команд напрямую с конечным автоматом дает хорошее решение этой хитроумной задачи.

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

Конечные автоматы

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

Конечные автоматы могут находиться единовременно только в одном состоянии. Иерархические конечные автоматы допускают состояния и подсостояния. Иерархические конечные автоматы зачастую полезнее, так как подсостояния наследуют все атрибуты своих надсостояний (super-state). Это может сократить объем требуемого конфигурирования.

Например, пользователь щелкает кнопку Search: приложение переходит в состояние Searching. Пока оно находится в этом состоянии, пользователю нужно запретить выполнение других операций (кроме операции Cancel). Состояние Searching, следовательно, должно иметь правило, которое предотвращает или игнорирует все операции, кроме Cancel. Некоторые реализации конечных автоматов также поддерживают операции входа (entry) и выхода (exit). Вы можете написать логику, которая будет выполняться при входе в некое состояние и выходе из него.

Структура команды

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

Эта концепция развита в современных интерфейсах для абстрагирования нужного пользователю действия. Например, если пользователь хочет скопировать какой-то текст, то командой является Copy Text. Пользователь может добраться до этой команды, выбрав элемент в меню, щелкнув правой кнопкой мыши или даже использовав комбинацию клавиш CTRL+C. Как реализуется команда, зависит от нижележащей программы и инфраструктуры, под которую она создана.

WPF реализует концепцию команд через интерфейс ICommand. Это часть Microsoft .NET Framework. Данный интерфейс имеет два метода и событие:

  • void Execute(object parameter) — выполняет код при вызове команды;
  • bool CanExecute(object parameter) — определяет, можно ли вызвать команду;
  • event EventHandler CanExecuteChanged — уведомляет инфраструктуру об изменении условий, влияющих на CanExecute. В основном это обрабатывается инфраструктурой WPF.

В своей основе источник команды, такой как кнопка или элемент меню, реализует интерфейс ICommandSource. В этом интерфейсе есть свойство Command типа ICommand. При связывании этого свойства с реализацией ICommand в ViewModel элемент управления будет вызывать метод Execute. Кроме того, вы можете включать и отключать это поведение на основе результата метода CanExecute. Если элемент управления, действующий как источник команды, не имеет свойства ICommand (что, к сожалению, встречается довольно часто), то можно использовать событие.

Реализации ICommand на основе .NET Framework, — RoutedCommand и RoutedUICommand. Они направляют события вверх и вниз по дереву визуальных элементов и не годятся для применения с шаблоном MVVM. С этим шаблоном обычно используют следующие реализации: RelayCommand от Джоша Смита (Josh Smith) и DelegateCommand, которая является частью инфраструктуры Prism.

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

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

*
Рис. 1. Загруженный список сотрудников, готовый к редактированию

Проектирование конечного автомата

Первый шаг в проектировании — определение раздела приложения, используя блок-схему или диаграмму состояний. На рис. 2 показана блок-схема для экрана Employee Manager. Она также включает блоки, которые указывают на потенциально длительный процесс, такой как Searching. Вы должны особым образом обрабатывать такие блоки и предотвращать повторный запуск пользователем еще незавершенной операции. Создание этих промежуточных блоков «busy» особенно важно после появления новой асинхронной функциональности в .NET Framework, поскольку элемент возвращает управление пользователю и потенциально тот мог бы попытаться запустить ту же операцию снова, что привело бы к повторному вводу.

*
Увеличить

Рис. 2. Схема обработки экрана Employee Manager

StartStart
SearchingПроцесс Searching
SearchCompleteSearchComplete
NoSelectionNoSelection
SelectedSelected
EditingEditing
Search CommandКоманда Search
SearchSearch
Search FailedНеудачный поиск
Search SucceededУспешный поиск
DeselectDeselect
SelectSelect
EditEdit
EndEditEndEdit
Edit CommandКоманда Edit
EndEdit CommandКоманда EndEdit

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

Второй шаг в процессе проектирования — определение любых команд, позволяющих взаимодействовать с приложением. С экрана Employee Manager можно выдавать команды Search, Edit an Employee, а затем End Editing. Пользователь также управляет процессами Selecting и Deselecting (выбор и отмена выбора сотрудника), однако они не управляются как команды, а обрабатываются как привязки данных в элементе управления DataGrid. Команды теперь отображаются на стрелки рабочих процессов (или поток управления) в релевантных точках, как показано на рис. 2.

Реализация конечного автомата

Вам не нужно создавать инфраструктуру конечных автоматов с нуля, так как есть много бесплатных библиотек. Единственный вариант в .NET Framework — Workflow Foundation (WF) State Machine Activity. Он слишком сложен для задачи. которую я пытаюсь решить здесь, но великолепно подходит для длительно выполняемых постоянных рабочих процессов. После некоторых предварительных изысканий я остановился на библиотеке Stateless, доступной в виде NuGet-пакета по ссылке bit.ly/ZL58MG.

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

public enum States
{
  Start, Searching, SearchComplete, Selected, NoSelection, Editing
}
public enum Triggers
{
  Search, SearchFailed, SearchSucceeded, Select, DeSelect, Edit, EndEdit
}

Определив перечисления, я должен сконфигурировать каждое состояние с помощью текучего интерфейса (fluent interface). Важными моментами являются:

  • SubstateOf(TState state) — указывает, что у состояния есть надсостояние и что оно унаследует все его конфигурации;
  • Permit(TTrigger trigger, TState targetState) — позволяет состоянию переходить в целевое через триггер;
  • Ignore(TTrigger trigger) — заставляет состояние игнорировать триггер, если он сработал;
  • OnEntry(Action entryAction) — вызывает выполнение операции при входе в состояние;
  • OnExit(Action exitAction) — вызывает выполнение операции при выходе из состояния.

Конечный автомат сгенерирует исключение, если триггер сработает в состоянии без допустимой конфигурации. Вот конфигурация для состояния Searching:

Configure(States.Searching)
  .OnEntry(searchAction)
  .Permit(Triggers.SearchSucceeded, States.SearchComplete)
  .Permit(Triggers.SearchFailed, States.Start)
  .Ignore(Triggers.Select)
  .Ignore(Triggers.DeSelect);

OnEntry выполняет поиск, Permits разрешает срабатывание релевантных триггеров, Ignores предотвращает в View срабатывание триггеров Select и DeSelect, когда элемент управления DataGrid связывается с нижележащим источником данных (такое срабатывание — весьма раздражающая особенность большинства списочных элементов управления в WPF).

Конечный автомат также предоставляет два важных метода:

  • void Fire(TTrigger) — переводит конечный автомат в другое состояние, используя предыдущие конфигурации;
  • bool CanFire(Trigger trigger) — возвращает true, если текущее состояние разрешает срабатывание данного триггера.

Это ключевые методы, необходимые для создания команд. Они выполняют логику «execute» и «can execute».

Связывание команд с конечным автоматом

Шаблон MVVM предоставляет свойство, реализующее интерфейс ICommand в ViewModel. Создание этого свойства команды теперь не требует ничего, кроме связывания ее методов Execute и CanExecute с методами Fire and CanFire конечного автомата соответственно. Создайте метод расширения для централизации этой логики:

public static ICommand CreateCommand<TState, TTrigger>(
  this StateMachine<TState, TTrigger> stateMachine, TTrigger trigger)
    {
      return new RelayCommand
        (
          () => stateMachine.Fire(trigger),
          () => stateMachine.CanFire(trigger)
        );
    }

После этого создайте свойства ICommand в ViewModel (вернитесь к схеме на рис. 2 за списком команд, выявленных на этапе анализа):

SearchCommand = StateMachine.CreateCommand(Triggers.Search);
EditCommand = StateMachine.CreateCommand(Triggers.Edit);
EndEditCommand = StateMachine.CreateCommand(Triggers.EndEdit);

Связывание View с командой в ViewModel

Для элемента управления, который действует как источник команды, вы можете связать его свойство Command со свойством ICommand в ViewModel. В приложении-примере есть две кнопки и элементы меню, связанные с командными свойствами ViewModel:

<Button ToolTip="Search" VerticalAlignment="Center"
  Style="{StaticResource ButtonStyle}"
  Command="{Binding SearchCommand}">
  <Image Source="Images\Search.png"></Image>
</Button>

Теперь команда напрямую связана с конечным автоматом. Она вызовет сконфигурированный триггер в период выполнения, но, что важнее, будет отключена, если этот триггер не разрешен в текущем состоянии. Это позволяет покончить с хитроумной логикой «can execute», и все аккуратно настраивается в конечном автомате. На рис. 3 показан экран Employee Manager, пока идет поиск. Заметьте, что команда Search отключена, поскольку в состоянии Searching триггер Search не разрешен.

*
Рис. 3. Анимация занятости в диалоге Search

Дополнительные преимущества

Реализовав конечный автомат, вы также можете связать с его состоянием другие визуальные элементы. Когда функция поиска занята (и учтите, что это могла бы быть длительно выполняемая операция внутри асинхронного метода), желательно выводить пользователю индикатор занятости. На рис. 3 показано анимированное изображение, отображаемое, только когда конечный автомат находится в состоянии Searching. Для этого создайте собственный конвертер, который преобразует состояние конечного автомата в значение Visibility:

public class StateMachineVisibilityConverter : IValueConverter
  {
    public object Convert(object value, Type targetType,
      object parameter, CultureInfo culture)
    {
      string state = value != null ?
        value.ToString() : String.Empty;
      string targetState = parameter.ToString();
      return state == targetState ?
        Visibility.Visible : Visibility.Collapsed;
    }
 }

Анимированное изображение можно потом связать с состоянием конечного автомата, используя собственный конвертер:

<local:AnimatedGIFControl Visibility="{Binding StateMachine.State,
                          Converter={StaticResource StateMachineConverter},
                          ConverterParameter=Searching}"/>

Вы можете применить те же принципы к диалогу редактирования, показываемому поверх экрана Employee Manager (рис. 4). Когда конечный автомат находится в состоянии Editing, этот диалог становится видимым, и пользователь может редактировать информацию о выбранном сотруднике.

*
Рис. 4. Состояние Editing

Заключение

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

Дополнительные ссылки

  • Конечный автомат Найти хорошо написанное введение в конечные автоматы на удивление трудно, но разработчик игр Боб Нистром (Bob Nystrom) все же написал такое введение (bit.ly/1uGxVv6).
  • WPF MVVM «Отец» WPF-MVVM Джош Смит (Josh Smith) представил RelayCommand, свою реализацию интерфейса ICommand, в февральском номере «MSDN Magazine» за 2009 год (msdn.microsoft.com/magazine/dd419663).
  • Команды Маршрутизируемые команды и события, являющиеся частью .NET Framework, обсуждаются в официальной документации MSDN по поддержке команд в WPF (bit.ly/1mRCOTv).
  • Маршрутизируемые команды Брайен Нойз (Brian Noyes) рассматривает маршрутизируемые команды (routed commands) в сентябрьском номере «MSDN Magazine» за 2008 г. Его статья — хорошая отправная точка для понимания разницы между маршрутизируемыми командами и шаблоном команд в MVVM (bit.ly/1CihBVZ).
  • Команды, RelayCommand и EventToCommand Лёро Буньон (Laurent Bugnion) в статье за май 2013 года показывает, как преобразовывать события в команды, что полезно для элементов управления, которые не реализуют интерфейс ICommandSource (msdn.microsoft.com/magazine/dn237302).
  • Инфраструктура Prism Шаблон MVVM и DelegateCommand обсуждаются в официальной документации на инфраструктуру Prism (bit.ly/1k2Q6sY).
  • NuGet-пакет Stateless Этот пакет с веб-сайта NuGet скачивается вместе с инструкциями (bit.ly/1sXBQl2).
Автор: Таркин Вон-Скотт  •  Иcточник: msdn.microsoft.com  •  Опубликована: 05.10.2015
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   WPF.


Оценить статью:
Вверх
Комментарии посетителей RSS

Чтобы оставить комментарий, зарегистрируйтесь или войдите с учетной записью социальной сети.