Introduction
Data binding is one of the key features in MVVM. It establishes a connection between the application UI and presentation logic. It updates the user interface automatically according to the changes introduced in the business logic. In order to update the property in UI, we need to notify the change from the view model. For that, we need to implement an interface such as INotifyPropertyChanged
which notifies the UI about any change in Model. But implementation of interface and linking it with the logic itself is a little cumbersome task.
There is an open source framework designed to implement MVVM (Model-View-ViewModel) pattern called Caliburn.Micro. It basically simplifies the coding by reducing the number of lines of code and the extra effort put on to implement MVVM. It’s one of the benefits is that it simplifies the process of Data binding. Caliburn.Micro works on the principle of convention over configuration. We may not need to explicitly implement INotifyPropertyChanged
. We just have to follow some simple conventions and work on our logic.
Background
MVVM facilitates a clear separation of the development of the graphical user interface (either as markup language or GUI code) from the development of the business logic or back end logic known as the model (also known as the data model to distinguish it from the view model). The view model of MVVM is a value converter meaning that the view model is responsible for exposing the data objects from the model in such a way that those objects are easily managed and consumed [6]. And for more details on what is MVVM and how to implement it, please follow the link:
Data Binding
Every time when the data of our business model changes, it needs to be reflected to the user interface and vice versa. For example, if the user edits the value in a TextBox element, the underlying data value is automatically updated to reflect that change.
Databinding can be unidirectional (source -> target or target -> source), or bidirectional (source <-> target). The source of a databinding can be a normal .NET property or a Dependency Property, but the target property of the binding must be a Dependency Property.
Now the question arises that how does the source or target get to know that a change has been made and data update needs to be done. For that, usually we implement INotifyPropertyChanged
interface. On Dependency Properties, it is done by the PropertyChanged
event of this interface.
Databinding is typically done in XAML by using the {Binding}
markup extension. The following example shows a simple binding between the text of a TextBox
and a Label
that reflects the typed value:
<Window x:Class="MyWPFdataBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Label
Content="{Binding ElementName=NameoftextBox, Path=Text}"
HorizontalAlignment="Left"
Name="label1" />
<TextBox
HorizontalAlignment="Left"
Name="textBox1"/>
</Grid>
</Window>
And then, we need to implement INotifyPropertyChanged
interface in ViewModel
to update the data.
The process of Data Binding can be explained in a better way using the following diagram:
Caliburn.Micro
Caliburn.Micro's one very important feature is that it follows a series of conventions, i.e., instead of writing the binding code manually; conventions take care of it on the platform provided by Caliburn.Micro. But similar to the two-faced coin, some people love conventions and some don’t like them. That’s why the Caliburn.Micro has been designed in such a way that its conventions are fully customizable and can even be turned off completely if not desired. But the motivation behind this article is useful only if you are going to use these conventions, and since they are ON by default, it’s good to know what those conventions are and how they work.
The very first convention that one encounters when using Caliburn.Micro is binding between View and ViewModel. The way Caliburn.Micro executes data binding can be explained in the following steps:
Passing the ViewModel
There is a
Bootstrapper
class in Caliburn.Micro which is the starting point of the application if one chooses to use this framework in application development. Here, the root
ViewModel
is passed to the
ViewModelLocator
class in order to determine how your application’s shell should be rendered. The syntax for passing the
ViewModel
is
Bootstrapper<T>
, where in place of
T
, we pass the name of our
ViewModel
. This step is taken when we follow the
ViewModel
-First approach. Caliburn.Micro supports both
ViewModel
-First and
View
-first approach. We can follow any of them according to our need and use. But we need to understand the difference between the two approaches. We don’t need to change our code much to implement any of these approaches. There is a slight change in the
ViewModelLocator
class of the Caliburn.Micro.
ViewModel first
It simply means that we have an existing ViewModel
that we need to render to the screen. In this scenario, the ViewModel
is responsible for creating the view and binding itself to the view. Also the binding is more straightforward because you can use a convention-based approach to map the view to the view model. Caliburn.Micro generally prefers this approach. To implement this, we pass the name of our ViewModel
in a separate class called AppBootStrapper
which we need to add manually. The ViewLocator.LocateForModelType
looks like this:
public static Func<Type, DependencyObject, object, UIElement> LocateForModelType =
(modelType, displayLocation, context) =>
{
var viewTypeName = modelType.FullName.Replace("Model", string.Empty);
if (context != null)
{
viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
viewTypeName = viewTypeName + "." + context;
}
var viewType = (from assmebly in AssemblySource.Instance
from type in assmebly.GetExportedTypes()
where type.FullName == viewTypeName
select type).FirstOrDefault();
return viewType == null ? new TextBlock {Text = string.Format("{0} not found.", viewTypeName) } : GetOrCreateViewType(viewType);
};
View first
It is another approach to achieve the MVVM application where View
and ViewModel
s are loosely coupled. Most users operate by moving through screens and most developers comprehend their applications in terms of screens. So, this approach best suits them. In this approach, it is the View
which drives the creation or search of the ViewModel
. The view typically binds to the view model as a resource, uses a locator pattern, or has the view model injected via MEF, Unity, or some other means. This approach is generally preferred when working with WP7. In this implementation, we need to bind our view model in XAML using command “cal:View.Model
” as follows:
<ContentControl cal:View.Model="{Binding ListingViewModel}" />
The code in the ViewModelLocator.LocateForViewType
:
public static Func<Type, object> LocateForViewType = viewType =>
{
var typeName = viewType.FullName;
if (!typeName.EndsWith("View"))
typeName += "View";
var viewModelName = typeName.Replace("View", "ViewModel");
var key = viewModelName.Substring(viewModelName.LastIndexOf(".") + 1);
return IoC.GetInstance(null, key);
};
Naming
convention: Here for this article, we are using ViewModel
First
approach and for implementing this approach, Caliburn.Micro uses a simple naming
convention to find a UserControl
that it should bind to the ViewModel
and
display. What we need to do is to append the text “ViewModel
” in the name of our
ViewModel
s, and remove the text “Model
” from that name when naming the
corresponding View
. In other words, we can say that UI name should suffix with
“View
”. So suppose we are developing some application for customer client
interface and name our ViewModel
as CustomerViewModel
, then the name of our View
would be CustomerView
. Example:
Let’s take an example where we are implementing the tic-tac-toe game using Caliburn.Micro.
The name of our View is “TTTView
” and corresponding ViewModel
is “TTTViewModel
”.
We need to add the “AppBootstrapper
”
class which is derived from CM’s Bootstrapper
class and pass the name of our
ViewModel
. This class contains the following code:
namespace Caliburn_TTT
{
class AppBootstrapper : Bootstrapper<TTTViewModel>
{
}
}
How Binding Works
This
is the most important step in this whole process. After the binding of View
and
ViewModel
is complete, regardless of whether we used ViewModel
-First or a
View
-First approach, the ViewModelBinder.Bind
method is called. This method
sets the Action.Target
of the View
to the ViewModel
, i.e., the controls in the
View
will be updated according to the ViewModel
/View
based on type of binding
selected in XAML file. ViewModelBinder
also determines if it needs to create
any conventional property bindings or actions. For this, it searches the View
for a list of element candidates for bindings/actions and compares them against
the properties and methods we defined in our ViewModel
. When a match is found,
it binds that element to the corresponding property or method.
Search for the match : There is a proper method using which Caliburn.Micro
searches for the elements. It searches going up and down the tree until it
finds a suitable root node for example, a Window
or UserControl
or element
without a parent. Once it discovers such elements, it proceeds for the second
task, i.e., out of these elements, searching the elements which have names. This
function then returns the found elements to ViewModelBinder
to apply
conventions.
<UserControl x:Class="Caliburn_TTT.TTTView"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:cal="http://www.caliburnproject.org">
<Grid x:Name="grid1" Margin="0,0,0,0" Height="473" VerticalAlignment="Top"
HorizontalAlignment="Left" Width="450">
<Grid.RowDefinitions>
<RowDefinition Height="20*"/>
<RowDefinition Height="150*"/>
</Grid.RowDefinitions>
<Menu Height="21" Grid.ColumnSpan="3" Grid.RowSpan="2"/>
<Button x:Name="Shape00" Background="White" Grid.Row="1">
<TextBlock Width="150" Height="150"
cal:Message.Attach="[Event DoubleClick] = [Action DrawShape(0,0)]"/>
</Button>
<Button x:Name="Shape01" Background="White" Grid.Row="1" Grid.Column="1">
<TextBlock Width="150" Height="150"
cal:Message.Attach="[Event DoubleClick] = [Action DrawShape(0,1)]"/>
</Button>
</Grid>
</UserControl>
There are two techniques which Caliburn uses to bind a UserControl
to
its corresponding method:
- If the
UserControl
and the method have the same name. As in our example, the Button
can
be bound to a method having the same name as that of button (here “Shape00
” and
“Shape01
”). But there are some limitations when we are using this binding
technique as we can’t pass parameters and the method is bound to the default
events of the controls as in case of button, the “Click
” event, i.e., the method
will be called when user clicks the button. - Second
technique is to use the command “
cal:Message.Attach
”. This technique overcomes
the limitations of the previous one. And also, in this technique it’s not necessary
that the name of the method should be the same as that the name of the control. We
can give any name to the method. As in the above example, we are using the name
“DrawShape
” and (0,0)
is being passed as parameters. "[Event DoubleClick]
” represents when the method will be called. In this case,
doubleclick on the TextBlock
will call the method DrawShape
. Similarly, we can
give any event on occurrence of which we want our method to get called like
click, keypress, etc.
Search of Methods
After the elements get located, ViewModelBinder
searches for the
corresponding methods in the ViewModel
for convention bindings. It first
searches for the public
methods in the ViewModel
. Then it goes for case
sensitive string comparison of the element name and the method name. After the
match is found, it binds the element functionality to that method. The methods
in the following code “Shape00
” and “Shape01
” are bound to the TextBlock
defined in the part of code above. And the method
“DrawShape
” is bound to the click of the button. Row and Column number has been
passed as the parameter.
public string Shape00
{ _______;
_______; }
public string Shape01
{_________;
_________;
}
public void DrawShape(int row, int col)
{____________;
____________;
}
Binding Multiple
Views to a Single ViewModel
This
is another one of the various capabilities of Caliburn.Micro. It also supports
multiple View
s of same ViewModel
, i.e., one can present different view to the
user on the basis of some event occurrence bound to the same ViewModel
.
Generally, beginners face issue with using the Caliburn.Micro’s convention
property in this case. However, we don’t need this for our example but it is
being explained for general issue.
Example:
A ViewModel
"MainViewModel
" is bound to "MainView
" through
convention. But after event occurs, we want the application to be displaying
"MainView2
" instead of "MainView
". This can be solved as
follows: We need to use
“cal:View.Context
” attached property and then name our view like
OurNamespace.Something.ContextView
(removing "ViewModel
" from our
view model name, add a dot, and the value of Context
property). Using this, we
can even bind several views to one view model.
<ContentControl x:Name="Toolbar" cal:View.Model="{Binding ActiveItem}" cal:View.Context="Toolbar" />
According to the above code, name of your ViewModel
should be Something.Toolbar
.
Conclusion
It becomes less cumbersome when we have to bind View
with ViewModel
using Caliburn.Micro. This helps in enforcing MVVM. Most of the times, we can
get rid of xaml.cs file whose presence always runs the risk of view
contamination.
From the above, we can easily judge that using Caliburn.Micro makes
life easier in case of Data Binding. But its uses are not limited to Binding
only. Binding is just one of the many features which gets simplified by Caliburn.Micro.
References
- http://en.wikipedia.org/wiki/Data_binding
- http://msdn.microsoft.com/en-US/
- http://www.codeproject.com/script/Articles/Submit.aspx
- http://www.mindscapehq.com/blog/index.php/2012/01/16/caliburn-micro-part-2-data-binding-and-events/
- http://caliburnmicro.codeplex.com/documentation
- http://en.wikipedia.org/wiki/Model_View_ViewModel