The Caliburn framework is packed with a variety of features spread across three main assemblies. In this post, I'll be discussing a few features available in Caliburn.dll. I'll cover basic configuration of the framework and demonstrate how a WPF UI can communicate with a Controller/Presenter.
NOTE: I'm using the terms Controller, Presenter, MVC, MVP pretty loosely here. The main point is that the framework has a number of facilities that make this style of development much easier. The way in which you end up implementing the patterns is your own business.
The first thing you'll need to do to get things working is configure the framework. We'll be using the default configuration for now, so all that is required is to make your App constructor look like this:
Lets also add a class to serve as a simple presenter:
Now we need to make a view:
The two important things to note are Engine:Presenters.AddBinding and Engine:Views.AddMessageSource. AddBinding causes the instance of Calculator to be bound to the Window. Effectively it is set as the Window's DataContext and registered with the framework as being connected to the Window. You can bind a presenter to any DependencyObject, although it must be a FrameworkElement to get DataContext functionality. PresenterBindings are directly related to ActionMessages. The ActionMessage that is set with the AddMessageSource attached property has the following result: When the Button is Clicked (Trigger) the Divide (Action) method on Calculator will be called. The Text values of the TextBoxes "leftSide" and "rightSide" will be passed into the method as parameters. The return value of the method will be bound to the Text property of the "result" TextBox.
A couple of other important things to know: Caliburn provides a ResolveExtension that can be used with AddBinding to ask a DI container to resolve the instance of the presenter for you. I have provided adapters for Windsor, Spring.net and StructureMap (StructureMap is not quite complete but will work in this scenario). You must use a custom configuration to turn on this functionality (discussed in my next post). Also, WPF Controls have default events and value properties, also configurable; so you don't have to specify that the ActionMessage in the above example is triggered by a Click event because this is the default for a Button. If we follow a few more conventions, we can further streamline the ActionMessage markup to this:
if we change our class to this:
If the message does not declare any parameters, and the action requires them, the framework will look for elements by name, based on the parameter names. It will then query the found elements for their default property values (configurable) and supply those values to the method. If it cannot find matches and the method only takes one parameter, it will attempt to bind that to the message sender's default property value. If a message doesn't need to declare a return value or parameters, you can declare it with a simple string value:
If you play around with the sample long enough, you'll eventually put a zero in the second TextBox and throw a nasty exception. You'd probably want to check for zero in reality, but you can handle exceptions like this:
You can also place an Action attribute on any method to declare custom exception handling for that method. What if the method is a long running action?
The return value still binds! Sweet! Need a callback instead/in addition to the return value? The AsyncAction attribute lets you specify the name of a callback method (parameterless).
NOTE: Another, way to get the same functionality as all the above examples would be to give the presenter LeftSide, RightSide and Result properties bound to the UI along with a parameterless Divide method. This simplifies the use of ActionMessages. (I tried hard with Caliburn not to box myself into one specific implementation of MVC/MVP because I found that in practice I would use both these techniques and others, depending on the specific situation.)
That just about sums up ActionMessages. There's one more important thing you need to know. ActionMessages can bubble. If, for example, a message was looking for an action called "DoSomething," but did not find it on the presenter, the message would bubble up the UI looking for a presenter until it found one with the appropriate action. This is useful if a UserControl needs to have its own presenter, but also wants to forward messages on to its parent Window.
Everything I'm shown above in XAML can be done imperatively through the Engine static gateway. You'll find that the static gateway mirrors the attached properties exactly. I've thought about creating a C# dsl for hooking up the bindings in code. It could use lambdas and such to create a nice compile-time checked version of the above. It would then allow all bindings to be removed from XAML and the framework could just locate your bindings class, instantiate it and hook everything up. This would also allow for Unit Testing of bindings. I'm not sure if this is overkill or not and would like some feedback as to whether I should do something like this or take another direction.
Looking back, ActionMessages were the most important feature we used on the WPF LOB we recently finished because of the flexibility of communication between classes that it allowed. I don't think I adequately represented them in the Caliburn sample (it overemphasizes the EventBroker which we very rarely used in practice), so I hope this clarifies things a bit more and maybe offers you some creative ideas for solving WPF problems in different ways.
The above sample has been added into the source along with several bug fixes and can be found at: http://bluespire.com/svn/Caliburn/trunk/.
NEXT POST: Custom Configuration, MarkupExtensions and Gestures Triggers
01-09-2008 10:14 PM