среда, 27 февраля 2008 г.

WPF command & CAB command - run into one another

В прошлый раз я писал про DM-V-VM "framework", как расширение к PnP smart client software factory (SCSF) и SCSFContrib. Одним из интересных моментов в snippet'e был класс ActionViewModel, в котором содержится список команд View, и который автоматически привязывает команды к UI. Там мы использовали класс CommandModel, являвшийся оберткой над WPF командой. Но CAB также предлагает совю реализацию паттерна Command. И как же быть? какую реализацию использовать?

CAB реализация имеет преимущества доступа к IoС-контейнеру, а следовательно и ко всем ресурсам (локальные сервисы, ресурсы, виды) composite приложения, разработанного при помощи CAB. А WPF команда тесно интегрирована с WPF framework'ом и следовательно вам не надо будет заботиться о логике опроса команды и управления состоянием UI. Как использовать преимущества одной и другой реализаций?

Здесь я собираюсь рассказать о своем решении, являющемся слиянием 2ух сущностей - WPF command и CAB command.
На самом деле в SCSFContrib (интеграция WPF и CAB) Kent Boogaart уже все сделал - WrappedCabCommand, но у его решения есть серьезный недостаток - невозможность передачи параметра команде. Эту проблему я решил, развив его идею.
IActionCatalogService

Основное усовершенствование WrappedCabCommand заключается в использовании IActionCatalogService - локального сервиса SCSF, как основного обработчика команды и ее состояния. Здесь есть хорошее описание данного локального сервиса SCSF. Сервис этот связывает действия(actions) с методами, помеченными атрибутом [Action(ActionName)],


регистрирует условия(conditions) - код реализующий логику принятия решения о возможности выполнения конкретного действия,

и позволяет выполнять действия - цепочку подписанных методов, по указанной уникальной строковой константе.


Методы, помеченные атрибутом [Action(ActionName)] должны быть публичными и соответствовать сигнатуре делегата ActionDelegate. Подпиской методов управляет ActionStrategy из SCSF, так что для присоединения метода к действию, надо либо создать класс, в котором этот метод определен, с помощью IoC-контейнера CAB, либо просто добавить его в коллекцию WorkItem.Items.


Условия - классы, реализующие интерфейс IActionCondition. Автоматически вызываются сервисом IActionCatalogService перед выполнением действия, либо явно вызываются клиентом через тот же сервис. Сервис строит из условий цепочку и конечный результат является произведением всех отдельных результатов опроса IActionCondition.


СAB Command.

В CAB команды представляются коллекцией объектов Command, хранящихся в WorkItem, доступ к которым осуществляется по уникальному строковому идентификатору. Сам CAB работает с командами через наследников класса CommandAdapter, управляя подписчиками, триггерами и т.п. Command'ы создаются CommandStrategy ObjectBuilder'a (OB), когда в процессе создания объекта OB встречает публичные методы-обработчики комманды, соответсвующие сигнатуре EventHandler'a и помеченные атрибутом [CommandHandler(CommandName)]. CommandAdapter содержит внутреннюю таблицу триггеров(объектов вызывающих выполнение команды) и подписчиков на указанные сообщения. И когда происходит событие в объекте-триггере, указанное при привязке триггера к команде, WorkItem вызывает всех обработчиков подписанных при помощи [CommandHandler(CommandName)].


Добавление UI-control'a в список триггеров команды ActivateOverview.

Метод-обработчик команды ActivateOverview. Будет автоматически добавлен в список методов-обработчиков CommandAdapter'a для комманды ActivateOverview.

WPF Command.

В WPF команды представляются объектами, реализующими интерфейс ICommand. В библиотеке есть 2 класса реализующих этот интерфейс - RoutedCommand, RoutedUICommand, которые маршрутизируют события CanExecute и Executed по всему визуальному дереву. Сами команды по умолчанию не содержат никакой логики, а предлагают реализовать ее любому объекту, подписавшемуся на эти комманды через CommandBinding где-либо в визуальном дереве. UI элементы, с которыми можно связать комманду, реализуют интерфейс ICommandSource и содержат свойства: Command - собственно комманда (объект с интерфейсом ICommand), CommandParameter - параметр, который передается команде (необязательное свойство) и CommandTarget - цель, на которой эта команда должна выполняться (необязательное свойство).

Come together!

За основу для объединения сущностей CAB и WPF комманд возьмем класс CommandModule.


Реализацией идеи является класс ModuleCommand:

Интерфейс класса ModuleCommand. IActionCatalogService инжектируется IoC-контейнером CAB при создании.

В отличие от WrappedCabCommand этот класс не требует передачи ему объекта CAB комманды. CAB комманду он находит сам, для этого ему лишь требуется имя - CommandName.

Для уникальной строковой константы CommandName принято соглашение - она идентифицирует следующие сущности: действия IActionCatalogService, Cab команды: собственно команду CommandName, и если определены строковые константы CommandName с префиксами Before[CommandName] и After[CommandName] - команды-тригеры до и после выполнения действия.


Переопределенный виртуальный метод OnQueryEnabled автоматически вызывается WPF framework. В нем производится опрос команды и ActionCondition'ов можно ли выполнять команду, вынося всю специфичную логику наружу (вызов CanExecute сервиса), и в зависимости от результата UI соответственно отображает состояние - enabled или disabled.

В интерфейсе класса есть свойство ActionTarget - дополнительный параметр, который команда будет автоматически передавать в метод-подписчик на действие.
CAB комманда учавствует в логике управления состояния комманды через свое свойство State. Оно влияет на принятие решения в OnQueryEnabled класса ModuleCommand.


Переопределенный виртуальный метод OnExecute вызывается WPF framework при перехвате события от UI элемента, связанонго с командой и вызывающего ее срабатывание, и делегирует выполнение кода триггеров и собственно команды IActionCatalogService и CAB командам.
Таким образом путем делегирования логики принятия решения о состоянии комманды и логики выполнения комманды IActionCatalogService решается проблема слияния CAB и WPF сущностей комманды. CommandBinding связывает комманду с UI, а IActionCatalogService связывает комманду с инфраструктурой CAB. Использование IActionCatalogService также позволило решить проблему передачи параметра в команду, т.к. CAB реализация команды такой возможности не предусматривает.

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

I said work!

Дабы не быть голословным и продемонстрировать решение в действии давайте модифицируем пример из прошлого поста. Основные изменения коснутся FooViewModel. Теперь коллекция его комманд будет состоять не из двух объектов различных классов с жестко зашитой в их код логикой, а из двух объектов одного типа (ModuleCommand), логика которых будет делегирована IActionCatalogService.


+ в проекте добавятся 2 класса:
обработчик действий


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


+ список идентификаторов:

Удачи!

PS: Интересный подход к реализации логики комманд демонстрирует Josh Smith в своей статье. Суть его предложения в следующем - сделать RoutedCommand "умнее", добавив ей самой возможность определять логику. Он наследуется от RoutedCommand и в классе наследнике вводит attached-свойство, помечающее UI-объект как обработчик комманды и вводящий 2 абстрактных метода OnCanExecuteCore и OnExecuteCore для реализации логики в классах наследниках. Таким образом можно CommandModel заменить на SmartRoutedCommand, но в таком случае, усложнится xaml UI, т.к. там надо будет прописывать CommandSink'и каждой команды вручную.
PPS: вспомнил, что видел еще одну реализацию основанную на использовании CAB EventBroker. Работает, и кстати в реализации проще, но считаю, что зря чувак смешал сущности.
UPD: выложил код для скачивания здесь.

6 комментариев:

Unknown комментирует...

мусчина вы бы лучше для айпфона написали бы программу : )

Анонимный комментирует...

Кент уже сделал WPFCommand и CABCommand в своем последнем решении. Но все это у меня не получается интегрировать с шорткатами. Напиши, был у тебя подобный опыт или нет?

RobertT комментирует...

Единственная причина, по которой я взялся за написание своей обертки - невозможность в варианте Кента передавать параметр команде. А чтобы связать команду с шорткатом, вам надо добавить в коллекцию InputGestures RoutedCommand или UIElement KeyGesture с необходимыми комбинациями клавиш.

Анонимный комментирует...

если не сложно что выложи код еще раз, исходники по прежней ссылке не доступны

RobertT комментирует...

http://www.megaupload.com/?d=OEWPQ15D

правда код давно не обновлялся, появилось много нового. как обновлю, выложу новую версию.

Анонимный комментирует...

ок, спасибо большое