Caliburn Micro, convention based binding

Caliburn Micro implements a convention based system for binding to “default” properties on controls and actions etc. This is on by default but can be switched off using ViewModelBinder.ApplyConventionsByDefault and setting this to false. You can also enable/disable conventions on a view-by-view basis by setting the attached property View.ApplyConventions to true in a view, for example (with other namespace etc. removed)

<UserControl x:Class="Sample.ShellView"
   xmlns:Micro="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
   Micro:View.ApplyConventions="False">

Let’s take a look at some basics of the convention based code.

In a view model class (assuming you’re derived from PropertyChangedBae) we can add the following code

private string fullName;

public string FullName
{
   get { return fullName; }
   set
   {
      if(fullName!= value)
      {
         fullName= value;
         NotifyOfPropertyChange(() => FullName);
      }
   }
}

Now, in the corresponding view add the following

<TextBox Name="FullName" />

Note the use of the name property and no binding property, this is because Caliburn Micro creates the link for us based upon the name and the binding property defined within it for mapping within Caliburn Micro (in other words, it’s binding based upon a naming convention).

For example the convention for a TextBox is that the binding property to be used is the Text property. Internally this looks like this

AddElementConvention<TextBlock>(TextBlock.TextProperty, "Text", "DataContextChanged");

We can, in fact, add our own convention based bindings for any controls not supported by default such as our own controls. For example, let’s create a convention based binding for a Rectangle object (which isn’t one of the default supported controls) whereby the convention based binding will look like this in XAML.

<Rectangle Width="100" Height="100" Name="Colour" />

and in our code (somewhere such as the top most view model or boostrapper) we would have something like this

ConventionManager.AddElementConvention<Rectangle>(Shape.FillProperty, "Fill", "FillChanged");

This will then create a binding from the name “Colour” on our viewmodel to the Fill property of the Rectangle (or Shape). Thus the Colour (obviously assuming it is a Brush) will be bound to the Fill property automatically.

The last argument in the AddElementConvention method is for the eventName and this can be used to use the same mechanism to fire commands on our view model.

Let’s add a Button to our code that looks like this, in XAML

<Button Name="Save" Content="Save" />

Now in the associated view model simply add the following code

public void Save()
{
}

It’s as simple as that to bind to the click event of the button. So, when the button is clicked the Save method on the view model is called. This is because of the following code, which Caliburn Micro

AddElementConvention<ButtonBase>(ButtonBase.ContentProperty, "DataContext", "Click");

One very useful bit of code in Caliburn Micro is for situations where you might wish to pass an argument to a method in a view model. Let’s assume that you have multiple save buttons on a form, one for draft save and one for full save (for want of a better example).

We want to use the same code on the view model to handle both saves, however depending on the argument pass to the method we will do a draft of full save. Maybe these two operations share a bunch of code so it suits us to reuse this code. Okay so it’s a slightly convoluted example, but you get the idea.

So our method will look like this

public void Save(string draftOrFull)
{
   // do a load of shared things

   if(draftOrFull == "draft")
   {
      // draft save
   }
   else if(draftOrFull == "save")
   {
      // full save
   }
}

Now add the following code to the XAML

<Button Content="Draft" cal:Message.Attach="[Event Click] = [Action Save('draft')]"/>
<Button Content="Save" cal:Message.Attach="[Event Click] = [Action Save('full')]"/>

The attached property Message.Attach will attach the Click event to the action “Save” passing the string draft or full – note the single quotes for the string.

We can achieve the same as above in a longer format as follows

<Button Content="Draft">
   <i:Interaction.Triggers>
      <i:EventTrigger EventName="Click">
         <cal:ActionMessage MethodName="Save">
            <cal:Parameter Value="draft" />
         </cal:ActionMessage>
      </i:EventTrigger>
   </i:Interaction.Triggers>
</Button>