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: выложил код для скачивания здесь.