I have been working with a WPF app on my spare time. I decided to use the WPF application framework called Caliburn. Caliburn is a lightweight framework that aids WPF and Silverlight development considerably.

Caliburn Goals:

  • Support building WPF/SL application that are TDD friendly.
  • Implement functionality for simplifying various UI design patterns in WPF/SL. These patterns include MVC, MVP, Presentation Model (MVVM), Commands, etc.
  • Ease the use of a dependency injection container with WPF/SL.
  • Simplify or provide alternatives to common WPF/SL related tasks.
  • Provide solutions to common UI architecture problems.

How does Caliburn work? A big part of WPF is it’s strong data binding functionality, however WPF control event handlers are normally defined in the control or view code behind. Caliburn lets you route using a declarative syntax control events to normal methods on your data bound presentation model.

Example:

<UserControl x:Class="GenArt.Client.Views.TargetImageView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Action.Target="{Binding}"
    >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Border Style="{StaticResource TargetImageBorder}">
            <Image x:Name="TargetImage" Source="..\Resources\Images\ml.bmp" Grid.Row="0" MinHeight="150" MinWidth="150"></Image>            
        </Border>
        <Grid Grid.Row="1">                            
            <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
                <Button Height="Auto" Message.Attach="[Event Click] = [Action BrowseForTargetImage] : TargetImage.Source">Select Target Image</Button>
                <Button Height="Auto" Message.Attach="[Event Click] = [Action StartPainting] : ">Start Painting</Button>
            </StackPanel>
        </Grid>
    </Grid>
</UserControl>

The first interesting Caliburn part is the attribute Action.Target="{Binding}" set on the top UserControl. This tells Caliburn that the action target is the current data binding instance (that is a presentation model). The second is the attribute Message.Attach="[Event Click] = [Action StartPainting]” set on the last Button Control. This two is a Caliburn WPF extension to declaratively attach the button click event to the method StartPainting.

The StartPainting method is defined on the class named ApplicationModel (this is the top, root data bound class for the entire WPF app).

public class ApplicationModel : PropertyChangedBase, IApplicationModel
{
  private DrawingStatsModel stats;
  private PaintingCanvasModel paintingCanvas;

  public ApplicationModel(GenArtDispatcher dispatcher) : base(dispatcher)
  {
      stats = new DrawingStatsModel(this, dispatcher);
      paintingCanvas = new PaintingCanvasModel(this, dispatcher);      
  }

        
  public ImageSource BrowseForTargetImage()
  {    
    //...
  }

  [AsyncAction(BlockInteraction = true)]
  public void StartPainting()
  {
      ///...
  }

}
As you can see Caliburn can route WPF control events to normal methods, methods can have arguments taken from other WPF controls, methods can return values that Caliburn can use to update control properties (as in the case of the BrowseForTargetImage that returns a ImageSource). This is very powerful as it almost allows for an MVC like separation between UI and the underlying presentation behavior.

Threading

Caliburn also makes async actions dead simple, if you need to have a WPF event handled in a background thread (so that it doesn't lock the UI) you only need to add a AsyncAction attribute. When the BlockInteraction parameter is set to true Caliburn will disable the WPF control that initiated the event and re-enable it when the action completes.

Almost all logic in a WPF app should be handled in a background threads however all UI interactions need to be done on the main UI thread. This can be handled easily by using a Dispatcher and a base class PropertyChangedBase.
public abstract class PropertyChangedBase : INotifyPropertyChanged
{
    protected GenArtDispatcher dispatcher;

    public PropertyChangedBase(GenArtDispatcher dispatcher)
    {
        this.dispatcher = dispatcher;
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    protected void RaisePropertyChanged(string propertyName)
    {
        var ChangeEvent = new ChangeEvent();
        ChangeEvent.PropertyName = propertyName;
        ChangeEvent.Source = this;
        dispatcher.Invoke(ChangeEvent);
    }

    public void RaisePropertyChangedEventImmediately(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
This class is very important if you want your data bound WPF presentation model to be able to automatically update the UI by just raising a PropertyChanged event. The WPF infrastructure will subscribe to this event for all data bound classes. The dispatcher part is used so that the event is always raised on the UI thread, this is powerful as you can set presentation model properties without having to think about which thread you are in. Example:
private void UpdateStats()
{
    Fitness = Math.Max(0, MaxFitness - model.EvolutionProcess.CurrentFitness);
    Generations = model.EvolutionProcess.Generations;
    SelectedGenerations = model.EvolutionProcess.SelectedGenerations;
}

public double Fitness
{
    get { return fitness; }
    set
    {
        fitness = value;
        RaisePropertyChanged("Fitness");
    }
}
I was very impressed with Caliburn and how it makes WPF development easier. It allows you to move some of the code you would normally write in a code behind class or in a presenter directly into the presentation model and at the same time making this code easier to unit test. There are still scenarios that would require presenters but I think a majority of UI interactions could be handled using Caliburn in this way. There are more to Caliburn than I have mentioned in this post, so be sure to check it out yourself

2 comments:

Rob said...

Great to see you are trying out Caliburn! Please feel free to send me feedback at any time. I though I would mention that Caliburn has a PropertyChangedBase which implement INotifyPropertyChanged for you. Presently, it doesn't marshall events to the UI thread as you do in your example, but in the next week I am planning on fixing that. Along with this will come some support for performant batching of large amounts of events to the UI, for scenarios where there are intesive model changes.

Torkel Ödegaard said...

Yea I saw the base class in Caliburn, but most of the background logic was handled in threads other than the UI. A batching feature would be great, I have noticed that the WPF dispatcher can easily get flodded.