суббота, 27 сентября 2014 г.

Построение контекстно-зависимого интерфейса. Part II

В предыдущем примере построения контекстно-зависимого интерфейса, есть существенный минус - прямая зависимость между объектом, инициирующим операцию, и непосредственным исполнителем. В результате потери гибкости, мы не можем независимо изменить взаимодействие между инициатором и исполнителем операции. 

Плодить прямые зависимости между множествами объектов - гарантировать себе и коллегам, в будущем, головную боль, особенно в условиях постоянно развивающегося проекта. Поэтому, если вы не хотите, чтобы у вас отняли клавиатуру, нужно на ранней стадии проекта сказать прямым зависимостям решительное нет и обратиться к классике Design Patterns с существующими практиками. 

Для решения данной задачи давно используется шаблон Command, который отделяет объект инициирующий операцию, от объекта, который знает как её выполнить. Это позволяет добиться высокой гибкости при проектировании пользовательского интерфейса. Пункт меню и кнопка могут быть одновременно ассоциированы с методом презентера, для этого достаточно привязать оба компонента UI к одному и тому же экземпляру Command.
Далее необходимо будет развязать взаимодействие между компонентом UI и объектом Command, для этих целей подойдет шаблон-посредник (Mediator). Посредник знает о компоненте UI и о команде, управляет взаимодействиями между ними, таким как: активация/деактивация компонента UI, вызов метода Execute. 

Ну и чтобы совсем получилось вкусно, хочу чтобы решение работало на платформах WPF и Winforms, иначе для чего было городить весь огород с Separated Presenter. Поскольку WPF поддерживает работу с командами (в виде привязки объекта ICommand), а Windows Forms нет, то далее речь пойдет о практике взаимодействия команд и компонентов UI платформы Windows Forms

Итак, для решения задачи на платформе Windows Forms требуется реализовать следующую схему:
Участники

UI Component – объект, инициирующий операцию:  кнопка (button), пункт меню (menu Item), элемент ленты (ribbon item).

CommandBinding – посредник, осуществляющий управление взаимодействием компонента UI и команды ICommand.

CommandManager –  управляет процессом создания/удаления связей компонентов UI и команд.

ICommand – в своей реализации призван инкапсулировать запрос в виде объекта. Инициатор запроса взаимодействует только с ICommand, ничего не зная об исполнителе. Инициирование операции осуществляется вызовом метода Execute.

Для возможности привязки через XAML рекомендуется использовать описание из пространства имен System.Windows.Input.

DelegateCommand – реализация интерфейса ICommand из библиотеки Prism. Инкапсулирует два делегата, каждый из которых ссылается на метод, созданный в пределах объекта, ответственного за выполнение операции. Реализует методы Execute и CanExecute интерфейса ICommand, вызывая эти делегаты.

Чтобы не повторяться, подробнее о назначении View, ViewModel и Presenter можно узнать в посте «Практика применения шаблона MVP-VM в Winforms».

Реализация

Реализация начинается с внесения изменений во ViewModel предложенной в первой части. Согласно спецификации (см. первую часть), для каждой операции работы с заказом, создаю свойство типа ICommand. Все флаг-свойства переопределяю в check-функции с префиксом IsAvable..., эти функции будут использоваться для проверки доступности команд и управления активацией ассоциированных компонентов UI.
Далее необходимо создать экземпляры команд и связать их с объектом ответственным за выполнение операций, в данном случае с презентером.

В качестве конкретной  реализации ICommand используется DelegateCommand (реализация из библиотеки Prism), конструктору необходимо передавать в качестве параметров делегаты исполняемой и check-функции. DelegateCommand является фактически адаптером, выполняющим функцию адаптации методом делегирования. Соответственно, при вызове клиентом метода ICommand.Execute(), DelegateCommand передаёт управление методу презентера, а при вызове ICommand.CanExecute() запросит метод ViewModel.IsAvaible.. 

Для упрощения примера функция CanExecute ограничена обращением к методу ViewModel, в действительности, как минимум, должно быть ещё обращение к службе авторизации.

На данном этапе, после создания экземпляров команд, решение уже может быть применено для платформы WPF, но для winforms необходимо ещё кое-что сделать.

ICommand для Windows form

Реальность такова, что объект, реализующий интерфейс ICommand, не может штатными средствами платформы winforms быть привязанным к компоненту UI, как это можно сделать в WPF:
Но это не проблема, необходимо компоненты UI winforms научить работать с командами. Для этих целей мне понадобится посредник, который наладит соответствующее взаимодействие.

На верхней схеме таким посредником является CommandBingind. Он обеспечивает между объектами слабую связанность и позволяет им развиваться независимо друг от друга. Посредник наблюдает за командой, в частности подписан на событие CanExecuteChanged, при возникновении которого, выполняет активацию/деактивацию компонента UI методом ICommand.CanExecute.

Посредник также наблюдает за компонентом UI, подписан на событие Click, и при его возникновении, вызовет метод ICommand.Execute(), передавая управление объекту, знающему о правилах выполнения операции.

Но не для всех компонентов UI события Click имеют одинаковую сигнатуру, поэтому для данного примера, в котором используются элементы меню, кнопки и лента (ribbon), созданы частные реализации CommandBinding.

Пример реализации для System.Windows.Forms.ButtonBase

Не все события, инициирующие выполнение команды, сводятся к Click. Вы можете создать посредника для любого события, к примеру DragDrop и привязать его команде RemoveOrderCommand. В результате, при перетаскивании заказа в метку с изображением корзины, он будет удален из общего списка. 

Привязка компонента UI к ICommand

После того как вся цепочка взаимодействия собрана, не составляет особого труда привязать компоненты UI к соответствующим командам, для этого достаточно вызвать метод CommandManager.Bind и передать ссылки на взаимодействующие объекты. При этом CommandManager берет на себя ответственность за создание корректных посредников CommandBindings и хранение связей на протяжении всего цикла взаимодействия.

После запуска приложения видно, что правила активации, которые описаны в ICommand.CanExecute, в зависимости от контекста представления, одновременно применяются ко всем ассоциированным с командой объектам.

Управление активацией компонентов UI

ViewModel - это тот самый контекст, в который транслируются действия пользователя, а команды являются наблюдателями. При изменении контекста, в данном случае, списка выбранных строк заказов ViewModel.CurrentOrdersSelected, модель представления уведомляет команды об изменениях состояния, вызывая метод ViewModelBase.RaiseCanExecuteChanged(). Данный метод находит все команды, отмеченные специальным атрибутом RaiseCanExecute, и генерирует событие ICommand. OnCanExecuteChanged.

На это событие подписан посредник CommandBinding  который обновляет свойство Enable компонента UI значением метода ICommand.CanExecute.

Заключение 

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

Исходный код демо-решения можно скачать здесь, группа Sample4.

Используемые источники

Практика применения шаблона MVP-VM в Winforms
Паттерн Посредник (Mediator)
Руководство разработчика Prism — часть 5
ICommand and CommandManager for Windows Forms

1 комментарий:

  1. Harrah's Lake Tahoe - MapyRO
    Harrah's Lake Tahoe. 아산 출장샵 Address: 777 충청북도 출장마사지 Casino Drive, Lake 양산 출장샵 Tahoe. Phone: (86) 751-6343. 시흥 출장샵 Website: https://www.caesars.com/harrahs-tahoe. Rating: 4.8 정읍 출장샵 · ‎23 reviews

    ОтветитьУдалить