WPF Application With Caliburn - Part One
Back in May 2008, I did two posts on how to implement a composite application using Caliburn framework. By that time, Caliburn was still in pre-alpha stage. With lots of the changes along the way to reach Release Candidate, those content are not valid anymore, so I thought a new post to show how you can use Caliburn to build a WPF application is in order.
In fact, a while ago I started porting my old .NET 1.1 pay roll application to WPF and .NET 3.5 so I naturally chose to use Caliburn and it did facilitate things a lot, but to keep things simple, I’m not going to discuss my own application here. I’ll show how you how can create your own application using Caliburn framework and how it will help you out.
First thing you need to do, is to setup a new WPF application. Let’s call it PayRoll application. After doing so, add reference to Caliburn and Castle assemblies. Caliburn honors the Dependency-Injection principle by depending only on abstractions. The idea is to allow you to easily plug in your own implementation of that abstraction and customize how Caliburn components work. To do so, Caliburn uses an IoC Container to lookup the services it requires.
If you’re new to Dependency Injection stuff, don’t worry, there is a built-in SimpleContainer which will do the job for you but I’ll be using Castle container (Windsor) here. If you’re using other IoC containers such as AutoFac, Ninject, StructureMap, Spring or even MEF (not technically an IoC container), Caliburn comes with existing adapters for those too, so no worries. To lookup instances of the services it needs, Caliburn uses the Common ServiceLocator and Service Locator design pattern. This allows looking up for services from various IoC containers, without actually depending on those containers.
Now to initialize Caliburn framework at startup there are various ways, but the easiest would be to create a CaliburnApplication. We already have an application object (App.xaml and App.xaml.cs) so we only need to change them to derive from CaliburnApplication class. To do so, open the App.xaml file first, and include the Caliburn namespace and change the type to Caliburn application. Also, we don’t want to specify any StartupUri, so get rid of that too. The result would be this:
With this, the project won’t compile! Because we’ve specified different base classes in our Xaml and Code Behind. Since the base class in code behind is inferred, open the code behind and remove the base class. The code would look like this:
So far so good. Now, If you look at the Caliburn Application class, there are interesting things that we can do. The obvious one is a method called CreateContainer. The default implementation, as said before, will create a SimpleContainer but we’re going to use our own container, so let’s do so:
protected override IServiceLocator CreateContainer()
Next step would be specifying a RootViewModel (also called ShellViewModel) that will act as ViewModel for our MainWindow. Notice that we have nothing to do with the actual View (UI) that will be displayed, we just specify the ViewModel for it:
protected override object CreateRootModel()
At this point, we need to create a ViewModel and a View for our Shell and register the VM in the container. Create two folders in your solution one named Views and the other ViewModels. Create an interface in ViewModels folder named IShellViewModel and create a class implementing this yet empty interface, named ShellViewModel. So now we need to register these (along with other services, view models, etc.) in the container:
Notice that lifestyle of ShellViewModel is set to singleton. This is logical because there’s always one instance of the ShellView running. We’ll see how this allow us interact from other ViewModels with Shell VM.
Now, let’s run the application and see what happens. When we run the application, Caliburn throws an exception complaining that a View can not be created for the ShellViewModel. Since we didn’t specify any information about our Views, you might say: How is Caliburn supposed to create a View for my ViewModel anyway? Well, Caliburn tries many things before giving up and throwing an exception:
- First, if your ViewModel implements the IMetadataContainer interface, the GetMetadata>T< method is called on your VM, asking for a view. Since this not a good option for us, let’s skip it.
- Second, if the ViewModel is decorated with a ViewAttribute, the specified view will be created. This is relatively easy, but it clutters your ViewModel with an attribute and I don’t like it.
- Third, specified assemblies will be searched to find the matching view according to a specified naming convention. This sound interesting, let’s use this method.
- Fourth, if you don’t like any of these behaviors, you can plug in your own IViewStrategy implementation.
If you remember, we’ve created an empty Views folder. If you create a UserControl named ShellView (mind the naming), Caliburn will automagically pick it up as the View for our ShellViewModel. How is this done, you may ask? According to pre-defined naming rules, Caliburn will look in folders named Presenter, Model, ViewModel and PresentationModel to create a ViewModel instance, and to create a View instance, it will look for it in the Views folder. An example might clear it up a little bit. Supposing you have an IShellPresenter, the implementation would be named ShellPresenter in the folder named Presenters. You should name the view for this presenter ShellView and place it in Views folder. The ViewModel names refers to different implementation of Presentation Model design pattern, but those terms are used interchangeably in Caliburn.
So, we’re going to use the naming convention (fourth method). Let’s create an empty UserControl named ShellView and put it in Views folder:
You can run the application now and it works. Caliburn has created a View (UI) and a POCO class as ViewModel and set the DataContext property of the View to the instance of the ViewModel. If you’ve noticed, we’ve set a UserControl as our root View. While you can create a Window object as your root View, if you create a UserControl (or anything other than a Window), Caliburn will create a MainWindow for you and place the View object as its content. This behavior is for Caliburn v1.1 so if you’re using older version 1.0 you can either upgrade or use a Window as your root view.
To check if View and ViewModel are really bound together, let’s create a command on the ViewModel and invoke it from the View. When invoked, it will change a Message property on the ViewModel. So first, open the IShellViewModel interface and add the ICommand and Message properties to the interface definition:
public interface IShellViewModel
…and implement the command property on your ShellViewModel class. To facilitate command handling, we’ll use a DelegateCommand class (originally RelayCommand) introduced by Josh Smith. Then instead of just displaying a MessageBox, let’s Create a TextBlock and bind it to the Message property on our ViewModel. When binding some element to a property on the ViewModel, we need to implement INotifyPropertyChanged interface on the ViewModel. Fortunately, there’s a base class in Caliburn which does exactly that:
public class ShellViewModel : PropertyChangedBase, IShellViewModel
…Finally, let’s place a Button and a TextBlock on the View and bind those to our ViewModel:
In next post, I’ll show you how to add DevExpress components to the pot and how to create a re-usable shell and along the way. We’ll see how to host other ViewModels in our ShellView and how to manage them. You can get the source code of this part from here.