Показаны сообщения с ярлыком Caliburn. Показать все сообщения
Показаны сообщения с ярлыком Caliburn. Показать все сообщения

четверг, 11 августа 2011 г.

Execute Caliburn coroutine in action pipeline without UI trigger

Seems rather easy, isn't it?

Just put it into Presenter's Execute method and the did is done. Well, should it be like that I wouldn't wake up in the night and write this stuff!
Let's see what's there in Execute:

So, it just packs every single coroutine it has in SequentialResult and execute 'em. No filters pipeline used, no routedMessage. None of your neat filters applied to these coroutines.

You might ask what is the difference with executing coroutines as a result of some UI action, be it a routed event or a command. The answer is in the Caliburn.PresentationFramework.Actions namespace.
To be prrecise it is an Action Execute overloaded method that in simple case of SynchronousAction does the following:

namely calls preprocessor filters, delegates a call to method that might be returning coroutine, handles errors with rescue filters, calls postprocessor filters. That is a filter pipeline I am talking about.
Well, seems that Caliburn has everything I need. And all I have to do is just use it.

As header states there is no UI trigger, i.e. I want to execute a coroutine when nobody clicked a button, but rather on some external trigger. As an example, consider model update with call to any data storage during Presenter initialization stage.

Enter DegenerateMessage. This intellectual piece of code is a data container that is being handed to participants on courutine call pipeline in order to hint what was the original method name called.

Another intellectual piece of code is DegenerateMessageTrigger. It is so damn smart that it knows that in order to trigger an action it has to pass a message to message handler!

And yet another piece is a DegenerateMessageHandler. It really shouldn't be called that way as it performs really complicated things. Namely, it creates an ActionHost and when Process is called converts DegenerateMessage to an ActionMessage and put it into the standard Caliburn action pipeline.

If it seems like dancing on your head, then know that you are not alone. But wait, there is another part I forgot to mention. It is a simple interface IResultExecutor aimed to hide all that smart code under two methods accepting Expressions with lambdas calling methods that return coroutines.

Oh, and there is yet another thing. With Caliburn Micro you can accomplish the same with only one line of code.

Cheers!

четверг, 24 июня 2010 г.

Trigger Caliburn action status update through Prism's IEventAggregator

И Caliburn и Prism, в принципе решают одни и те же задачи - создание композитных приложений на WPF. Но, вот Роб Эйзенберг (создатель Caliburn) не считает, что они друг друга взаимоисключают, а я и подавно с ним согласен.
Я использую следующие "фичи" Prism'a:

  • modularity;
  • region manager;
  • event aggregator;
, а из Caliburn следующие:
  • actions engine;
  • application & model framework, даже не смотря на то, что с первого взгляда model framework кажется "overarchitected";
  • convention over configuration;
Вот сейчас я вам расскажу как расширить actions в Caliburn, при помощи Event Aggregator из Prism.
Главный способ расширения - это написание фильтров, которые вставляются в последовательность выполнения action. С их помощью можно проверять предусловия или состояние данных (к примеру можно проверить обладает ли Principal достаточными правами для выполнения действия), дабы определить статус action и обновить эффекты, применяющиеся к UI элементам. Примером такого фильтра является поставляющийся с framework фильтр Dependencies, который вызывает исполнение всей последовательности фильтров, на основании изменения какого-либо свойства, имя которого передается в качестве параметра ему. Но что, если вам нужно просто обновлять статус action, не связанный ни с каким полем в объекте, на основании какого-либо события/условия. Вы можете завести специальное поле и передергивать его значение или даже еще лучше - не заводите никакого поля, а просто кидайте нотификацию с именем несуществующего поля, переданного фильтру. Не знаю как вам, а у меня от таких решений зудит в одном месте.

Так вот зуд мой унялся только когда я написал фильтр, запускающий всю цепочку обработки статуса события по получению уведомления от EventAggregator сервиса Prism.

DependsOnEvent




Фильтр применяется как атрибут, которому передается тип события. Тут есть одно ограничение - событие должно быть унаследовано от generic типа CompositePresentationEvent закрытого типом object, т.е. CompositePresentationEvent_Of_Object. В методе Initialize происходит магия с помощью reflection. Дело в том, что IEventAggregator Prism имеет исключительно generic интерфейс, за что можно разработчиков его только поругать. Мы получаем событие переданного типа и
подписываемся на него. Вот именно для этого и приходится колдовать с волшебным зеркальцем, т.к. заранее тип события не известен. Часть, относящуюся к запуску обработки action, я содрал из кода упомянутого выше Dependencies фильтра.


Здесь мы создаем Observer, который будет следить за изменениями зависимых свойств. Т.к. реально никакие свойства не меняются я просто использую трюк с несуществующим свойством - в Initialize при подписке на событие я передаю делегат, который кидает событие обновления свойства Update, которого-то как раз и нет. Observer, поймав это событие запускает всю цепочку обновления статуса action.




PublishEventAfter
Ну и напоследок, фильтр, выполняющий все ровно наоборот, после выполнения action он кидает событие, которое обрабатывает EventAggregator.




Удачи!