If you have read anything I have written in the past, you are probably familiar with the fact that I am a huge fan of MVVM. There will have to be a lot of convincing before anyone gets me to switch pattern when working with Silverlight. There are several things one must learn and understand before one can use MVVM well.
There is the obvious part of learning the actual pattern and how it works. Learning how to build a good viewmodel, which is dependent on the data model and time and other circumstances. I know that there are a lot of people out there, especially bloggers, that will tell you that you have to build something according to this or that and be a true purist to be a good developer. However, a lot of these people forget that most of us do actually have clients that don’t feel like spending 2 months or $10.000 just to get it to be a perfect solution. In most cases one must adhere to this and make the best of the situation.
But…to get back on track…there are more things one must know and master before being able to use MVVM in a good way. Today’s topic is about data binding. Data bindings are special objects designed to bind values from one object to properties on another. In MVVM bindings are REALLY important, since they are responsible for moving values from the viewmodel into the properties of the controls.
So I want to take a look at the different bindings options that are available, as well as how we can configure them. In part one I will cover the basic stuff. How to bind your models values to your view’s controls. In part 2 I’m going to look at some of the new binding possibilities in Silverlight 3, such as element to element bindings, data validation and refresh bindings from code.
Getting started…to start off this talk, I will need a simple model. Let’s use a Person object. It is quite a simple class. And it will be used in a really useless way, but bear with me. The model looks like this:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Mood Mood { get; set; }
public bool IsMarried { get;set; }
public bool IsBroke { get; }
}
public enum Mood
{
Happy,
Sad
}
This is a interface for my model. The actual implementation needs to be a little bit more complex for my bindings to show off their powers. First off, I want my model to implement the INotifyPropertyChanged interface. This interface has a single event that needs to be implemented, PropertyChanged. This interface makes it possible for my model to notify all bindings of changes to the models properties.
The binding will read the value from the model initially when the binding is created, and set the corresponding control property. It will then leave that property and value alone until the model raises the PropertyChanged event. The event passes along the name of the property being changed. If the bindings property has changed, the control’s property value is updated.
This is the default behavior. However, we can set the binding to update in the other direction as well, causing the models value to be changed when the controls property has changed. I will talk more about this later…
All the properties with both getters and setters work in the same way, so I will only show you one of them as well as the implementation of the INotifyPropertyChanged interface. The IsBroke property however, is a little different and will be shown as well
public class Person : INotifyPropertyChanged
{
string _name;
// more fields...
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
OnPropertyChanged("Name");
}
}
//more properties...
public bool IsBroke
{
get { return _isMarried; }
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
As you can see, every setter calls the OnPropertyChanged helper method that raises the PropertyChanged event. The IsMarried property calls it twice. The reason for this, is that since IsBroke is a “calculated” property that is dependent on the IsMarried property, so every time the IsMarried property changes, so does IsBroke. So to tell any bindings connected to this property that it has changed, the PropertyChanged event needs to be raised whenever one of the properties that affect its value is changed. In this case it is only one other property, but in more complex situations it can be a whole bunch…
If we were working with collections and needed to notify a control that the collection had changed, the collection would need to implement the INotifyCollectionChanged interface, which is an interface that does the same thing as INotifyPropertyChanged, but notifies when the contents of a collection has changed. The easiest way to solve this, is to always use ObservableCollection<T> when binding collections, since this already implements this interface…
The last piece of the puzzle is to add some business logic to my model. In this case, the marital status of my object will also affect the Mood. So if the user goes from being single to being married, the Mood will change to Happy. And going in the other direction will cause the Mood to be Sad. The implementation looks like this
public bool IsMarried
{
get { return _isMarried; }
set
{
if (_isMarried == value)
return;
_isMarried = value;
Mood = (_isMarried) ? Mood.Happy : Mood.Sad;
OnPropertyChanged("IsMarried");
OnPropertyChanged("IsBroke");
}
}
As you can see, I set the Mood from the IsMarried property, causing the PropertyChanged event to be raised twice. Once for IsMarried and once for Mood.
So…that is the model that I will be working with…that has taken a lot of text already…sorry about that…but without the model, the bindings would be boring…
Next to the UI. Once again, the simplest possible UI will do. I want a TextBox for the name and the Age, DropDown for the Mood and Checkbox for the marital status. So I add this to a “form” that can be used to modify the values. It looks like this
<UserControl x:Class="ListBoxStyling_for_Blog.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Grid x:Name="LayoutRoot" Background="White">
<Grid Width="250" Height="120">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<TextBlock Text="Name" />
<TextBlock Grid.Row="1" Text="Age" />
<TextBlock Grid.Row="2" Text="Mood" />
<TextBlock Grid.Row="3" Text="Married" />
<TextBox Grid.Column="1" Margin="0,2" Text="{Binding Name, Mode=TwoWay}" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="0,2" Width="30" HorizontalAlignment="Left" />
<ComboBox Grid.Column="1" Grid.Row="2" Margin="0,2">
<sys:String>Happy</sys:String>
<sys:String>Sad</sys:String>
</ComboBox>
<CheckBox Grid.Column="1" Grid.Row="3" Margin="0,2" HorizontalAlignment="Left" />
</Grid>
</Grid>
</UserControl>
Which turns into a simple little form. However, right now it is a very boring form, so lets make it a little more entertaining. Lets start data binding it. The first thing to know about data bindings is that they by default to bind against the parent controls DataContext property. The DataContext property is actually “inherited” from parent to child. So if the child does not specifically set it, it will just carry on the parent context. So setting the DataContext of a control, will affect all controls inside it, unless the specifically change it. It is possible to bind against objects in a controls Resources as well, which is what Blend does. But for now, I will keep it to the DataContext. So in the controls constructor I create a new person and set the values I want. Normally the DataContext will not be set like this, but for this demo it will suffice. The common way is to set it from the Application’s start up method, when the control is created, or even better by using a service locator or dependency injection framework.
public Page()
{
InitializeComponent();
Person chris = new Person() { Name = "Chris Klug", Age = 29, IsMarried = false, Mood = Mood.Happy };
this.DataContext = chris;
}
So far, this has solved nothing, but it has set everything up so that we can start binding our controls. Lets start by binding the Name TextBox. This will use a simple, but yet powerful TwoWay binding. The binding looks like this
<TextBox Grid.Column="1" Margin="0,2" Text="{Binding Path=Name, Mode=TwoWay}" />
So what I am basically doing, is creating a binding responsible for “synchronizing” the TextBox’s Text property with the DataContext object’s Name property. The binding is totally ignorant about what is in the DataContext, which gives us a really clean separation.
The “Path=” part can be left out if you feel like it, leaving only {Binding Name,Mode=TwoWay}. And it can also be more complex including several “levels”. Just like when coding. So we could have done “Path=Name.Length”. I’m also telling it that I want it to be a TwoWay binding. That means that it will be cause changes bi-directionally. So any changes to the TextBox’s Text property will change the value in the model, and any change in the model will be cause the TextBox’s Text property to change.
There are two other binding Modes available, OneWay and OneTime. OneWay means that data will only flow from the model to the control. OneTime means that it will only set the controls property once and then forget about the binding. If no mode is specified, OneWay will be used.
So, the base for all data bindings is {Binding Path=MyProperty,Mode=OneWay}. But wait, there’s more. Of course there is. There is a LOT more. And you will quickly realize that you need more. Intitially I wanted to use the following Xaml so show you that binding an integer to a string property wouldn’t work. However, it does, which I think is a new thing in Silverlight 3. I recall this being a problem in 2, but I only have 3 so I can’t verify this… Anyhow, binding Age is just as easy as the Name
<TextBox Grid.Column="1" Grid.Row="1" Margin="0,2" Width="30"
HorizontalAlignment="Left" Text="{Binding Age, Mode=TwoWay}" />
But, since binding the Age to the TextBloc worked, I guess I have to show how to solve type mismatches using the Combobox for the Mood. So I start off by binding the Combobox using a simple binding
<ComboBox Grid.Column="1" Grid.Row="2" Margin="0,2" SelectedItem="{Binding Mood, Mode=TwoWay}">
<sys:String>Happy</sys:String>
<sys:String>Sad</sys:String>
</ComboBox>
However, this will fail. I have even tried it… And the reason is that the items in the Combobox are strings and the Mood property is an Enum value. The solution is a converter. A converter is a class that implements an interface called IValueConverter. Its only responsibility is to convert objects from one type to another for the binding to work. It has 2 methods, one to go from the models value type to the control property’s type, and one for the other direction. For the dropdown, it need to convert from the Enum value to string and back again. So I add a new class called MoodConverter (I whish I had a Mood converter…). It looks like this
public class MoodConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (Mood)Enum.Parse(typeof(Mood), value as string, true);
}
}
This implementation is overly simple, and in most situations they become more complex, but it works for this. The Convert method is responsible for changing the value from the model to the control, and the ConvertBack is responsible for the other direction. Both methods are passed parameters that help you to do the right thing. It passes in the value as object, which means it can be anything. It also passes in the target type, indicating what type to convert it to. It also gets a parameter object and a CultureInfo object. Both these parameters can be set when creating the binding. By doing this, you can pass “configuration values” from the binding to the converter. And even though the converter right now is a simple thing, it could become much more clever and converter several source types to several target types if needed.
Using the converter is not really that hard. All you have to do is to add another parameter to the binding. However, to be able to add the converter it has to be added as a resource and added to the binding as a StaticResource. Like this
<UserControl x:Class="ListBoxStyling_for_Blog.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:MyNamespace.Converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<converters:MoodConverter x:Key="MoodConverter" />
</Grid.Resources>
...
<ComboBox Grid.Column="1" Grid.Row="2" Margin="0,2"
SelectedItem="{Binding Mood, Mode=TwoWay,Converter={StaticResource MoodConverter}}">
<sys:String>Happy</sys:String>
<sys:String>Sad</sys:String>
</ComboBox>
And all of the sudden. the binding works…Cool! If I were to pass in a parameter I would just modify the binding like this
<ComboBox Grid.Column="1" Grid.Row="2" Margin="0,2"
SelectedItem="{Binding Mood, Mode=TwoWay,Converter={StaticResource MoodConverter},ConverterParameter=MyParameter}">
And the final binding, the marital status, is just as easy
<CheckBox Grid.Column="1" Grid.Row="3" Margin="0,2" HorizontalAlignment="Left"
IsChecked="{Binding IsMarried}" />
If we run the example right now, we get a fully functional form that looks like this
So…what’s next? Well, I want to show the power of TwoWay bindings since I have used them. And I want to do this by adding a summary table. Basically a grid, with one way bindings to the same model, showing the current values of the model. So I wrapped the existing form in a StackPanel and added another grid with only TextBoxes. I also added a TextBlock for the IsBroke property. The table/Grid looks like this
<Grid Width="250" Height="150">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<TextBlock Text="Name" />
<TextBlock Grid.Row="1" Text="Age" />
<TextBlock Grid.Row="2" Text="Mood" />
<TextBlock Grid.Row="3" Text="Married" />
<TextBlock Grid.Row="4" Text="Broke" />
<TextBlock Grid.Column="1" Margin="0,2" Text="{Binding Name}" />
<TextBlock Grid.Column="1" Grid.Row="1" Margin="0,2" Text="{Binding Age}" />
<TextBlock Grid.Column="1" Grid.Row="2" Margin="0,2" Text="{Binding Mood}" />
<TextBlock Grid.Column="1" Grid.Row="3" Margin="0,2" Text="{Binding IsMarried}" />
<TextBlock Grid.Column="1" Grid.Row="4" Margin="0,2" Text="{Binding IsBroke}" />
</Grid>
Running this, will now show you something really nice. If you modify the values in the form, it automatically updates the values in the display part. And as I promised, changing the marital status also changes the mood and financial status… Changing the values in the TextBoxes, does not change the models values until the loose focus. So is you change a TextBox, you need to move the caret out of there for it to update the models value.
Before ending part one, I want to show you a somewhat more interesting way of using converters. Converters are simple, but VERY flexible and useful. So, how about changing the color of the mood text depending on the mood…Well…the easiest way to do this is to just extent the existing MoodConverter with the following
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType == typeof(Brush))
{
Mood currentMood = (Mood)value;
switch (currentMood)
{
case Mood.Happy:
return new SolidColorBrush(Colors.Magenta);
case Mood.Sad:
return new SolidColorBrush(Colors.Red);
}
}
return value.ToString(); ;
}
And then the xaml with this
<TextBlock Grid.Column="1" Grid.Row="2" Margin="0,2" Text="{Binding Mood}"
Foreground="{Binding Mood,Converter={StaticResource MoodConverter}}" />
Resulting in this
But the coolest converter work I have seen is probably Bea Stollnitz’s solar system listbox…which I do know is WPF, but is doable in Silverlight (I think)…
This is it for part one. I will add more details and more interesting facts in part 2. It will as I mentioned earlier cover among other things element-to-element bindings, data validation and a few more bits and pieces.
I hops you have enjoyed the binding information so far, and that you come back soon to check up on part 2. Cheers!
BTW! I suggest subscribing to the RSS so that you get notified automatically when the next part comes on-line.