Introduction
Over the course of the last two years, I had the chance to speak at various conferences (e.g., BASTA, dot.net Cologne, Microsoft BigDays, etc.) about data binding in WPF and Silverlight. I also wrote two articles on this topic in the German dot.net magazine. The goal of the talks at the conferences has been to give newbies an impression when and how they could use data binding in their WPF and/or Silverlight applications to write lesser code, build more robust applications, and develop more maintainable solutions.
Next week, I will be at a conference again. At the DEVcamp in Vienna, the topic of one of my talks will be data binding again. However, this time, the session is not targeted at people who are new in developing with WPF and Silverlight; at the DEVcamp, I want to present more advanced topics. My session will be code-only, and therefore I am writing this article. The participants can reread the points I will make, and maybe the content is interesting for other readers who do not have the opportunity to come to the DEVcamp, too.
This is what I am going to cover in the next few paragraphs:
- MVVM: Model-View-Viewmodel: Why is this architectural pattern important when we use data binding?
- From WPF to Silverlight: Where are the differences between WPF and Silverlight concerning data binding?
- Converter rulez! Why are converters so important for data binding in WPF and especially in Silverlight?
- Scripting in XAML: How can you make use of the DLR in XAML data bindings?
- Command Bindings: How can you make the methods in your viewmodel-layer bindable?
- Async Data Binding: Where, when, and why do we need async data bindings, especially in Silverlight?
- And finally, due to popular demand - RelativeSource Bindings: Something many people are in a struggle with...
MVVM: Model-View-Viewmodel
As mentioned before, this article assumes that you have a certain knowledge about WPF and/or Silverlight. If you have, I am quite sure that you have heard about MVVM (or about MVC or MVP which are tightly related to MVVM; read more about MVVM, e.g., in Wikipedia). The basic idea of MVVM is to introduce a layer between the view and the model that builds the bridge between these two. Why do we need a bridge? Isn't WPF/Silverlight data binding powerful enough to handle the classes from the model-layer directly? Well, it turns out that it isn't. The view-layer implemented in XAML should describe the user interface structure and behavior (this is why we call XAML a declarative language; read more about declarative programming, e.g., in Wikipedia). It should never contain business logic. Now, think of the following:
- Your model contains a property called
IsFemale
of type boolean
.
- In your UI, you want to hide a specific part of your window if a person is a man. Hiding in WPF/Silverlight means setting the
Visiblity
property of the corresponding UIElement
instance to Collapsed
.
- Who is responsible for converting the boolean value to
Collapsed
or Visible
?
You do not really want to add a property of type Visibility
in your model-layer, do you? XAML, on the other side, does not offer something like IF
or IIF
, therefore you cannot do the mapping there either. This is where the viewmodel-layer comes into play. It builds the bridge between the view and the model - in our case, by offering a converter from boolean
to Visibility
. If you have experience in WPF/Silverlight, I am quite sure that you have already become familiar with converters. Now, you should be aware of how your converters fit into the MVVM pattern. We will look at quite a few code examples about converters, a little bit later.
Another reason why we use MVVM in WPF, and especially in Silverlight, is to factor out display logic from the view-layer into the viewmodel-layer. The reason for this is especially testability. As you possibly know, it is quite hard to test the view-layer. It is much easier to test class libraries that do not have a user interface. Therefore, it makes sense to move as much logic as possible into the viewmodel to make it easier to test it.
I want to demonstrate what I mean using a small example. Our goal is to implement a calendar displaying the days of a month:
The model-layer in our example is extremely simple; it just consists the .NET Framework's DateTime
class because this is what we want to work with: just dates. So, let's first think about if we need a wrapper class around DateTime
in our viewmodel-layer. As you can see, in the image shown above, the calendar displays all weeks of the month. In the first week and in the last week, there may be dates that do not really belong to the currently selected month. However, let's assume that our specification tells us that we have to display them too. It will be the goal of a subsequent step in the example to give them a different look than those days that are really inside the selected month. This is where the viewmodel gets important. Later, we will need an indicator per day from which we can say if the date should be displayed differently. It is clear that we cannot add this indicator to our model because it is something that is just important for the UI. A solution is to add a wrapper class around our model (i.e., DateTime
) with an additional property:
using System;
namespace ViewModelLibrary.CalendarViewModel
{
public sealed class CalendarDay
{
public CalendarDay(DateTime day, int currentYear, int currentMonth)
{
this.Day = day;
this.IsInCurrentMonth = day.Year == currentYear && day.Month == currentMonth;
}
public DateTime Day { get; private set; }
public bool IsInCurrentMonth { get; private set; }
public bool IsSunday
{
get
{
return this.Day.DayOfWeek == DayOfWeek.Sunday;
}
}
}
}
The next step towards a solution for our problem is to offer the UI a list of dates against which it can bind. Is it enough to just add a class (e.g., Calendar
) with a property (e.g., List<CalendarDay>
)? Well, we could implement it that way. However, if we do so, we give the UI developer quite a hard problem. He will have to figure out a way to separate the list of days into weeks - impossible to do that in XAML! Therefore, it is a better solution for the viewmodel-layer to offer a list of weeks, which then offers a list of days of the corresponding week. Note that the structure of the UI influences the class design of the viewmodel-layer again. That is quite typical in MVVM.
This is how our Week
class could look like:
using System;
using System.Collections.Generic;
namespace ViewModelLibrary.CalendarViewModel
{
public sealed class Week
{
public Week(DateTime anyDayInWeek, int currentYear, int currentMonth)
{
var result = new List<CalendarDay>();
var currentDate = anyDayInWeek.AddDays(((
(int)anyDayInWeek.DayOfWeek) + 6) % 7 * (-1));
for ( int i=0; i<7; i++ )
{
result.Add(new CalendarDay(currentDate, currentYear, currentMonth));
currentDate = currentDate.AddDays(1);
}
this.Days = result;
}
public List<CalendarDay> Days { get; private set; }
}
}
Last but not least, we need the Calendar
class. It is the core viewmodel class which our view-layer will bind to. The important difference to the previously developed classes Day
and Week
is that Calendar
is not immutable (read more about immutable objects, e.g., in Wikipedia). On the contrary, it reflects the actions that a user can perform in the UI. In our case, the user will later be able to navigate between days in the calendar view using two buttons (next month, previous month). Therefore, our Calendar
class offers two properties:
CurrentMonth
- any day in the currently selected month (typically the first day)
Weeks
- list of weeks for the currently selected month that has to be displayed in the UI
If you take a look at the following implementation of Calendar
, note that Weeks
is automatically changed whenever CurrentMonth
is changed. The UI is informed about property changes using INotifyPropertyChanged
(I will not go into details about this interface, because I assume that you have already heard about it; if not, you can find more details about it in MSDN).
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace ViewModelLibrary.CalendarViewModel
{
public sealed class Calendar : INotifyPropertyChanged
{
public Calendar()
{
this.PropertyChanged +=
new PropertyChangedEventHandler(OnCalendarPropertyChanged);
this.CurrentMonth = new DateTime(2009, 10, 13);
}
private void OnCalendarPropertyChanged(object sender,
PropertyChangedEventArgs e)
{
if (e.PropertyName == "CurrentMonth")
{
var result = new List<Week>();
DateTime currentDate;
currentDate = (currentDate = this.CurrentMonth.AddDays((-1) *
(this.CurrentMonth.Day - 1)) - this.CurrentMonth.TimeOfDay).AddDays(
(((int)currentDate.DayOfWeek) + 6) % 7 * (-1));
while (currentDate.Month == this.CurrentMonth.Month ||
currentDate.AddDays(6).Month == this.CurrentMonth.Month)
{
result.Add(new Week(currentDate, this.CurrentMonth.Year,
this.CurrentMonth.Month));
currentDate = currentDate.AddDays(7);
}
this.Weeks = result;
}
}
private DateTime currentMonth;
public DateTime CurrentMonth
{
get { return this.currentMonth; }
set
{
if (this.currentMonth != value)
{
this.currentMonth = value;
this.OnPropertyChanged("CurrentMonth");
}
}
}
private IList<Week> weeks;
public IList<Week> Weeks
{
get { return this.weeks; }
private set
{
this.weeks = value;
this.OnPropertyChanged("Weeks");
}
}
#region INotifyPropertyChanged
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
Now our viewmodel-layer is ready to be used. Before we go on to the view-layer, please note that our viewmodel-layer has no dependencies to WPF or Silverlight. It is generally usable! Additionally, it is quite easy to test. You can write a simple unit test that checks if the application responds to changes of the currently selected month correctly, just be testing the methods of the Calendar
class. No need for complex UI automation magic for that!
The view-layer uses data binding to connect the different UI elements with the model classes through the viewmodel-layer. Before I show you the XAML code, let me turn your attention to the following important points:
- I do not use styles for formatting the sample UI here. This is done just for the sake of simplicity; in practice, you should separate the styles of your UI from its structure (see app.xaml, themes, etc., for details).
- Note that you can do string formatting for numeric, date, and time values in
Binding
-expressions to a certain extent without writing your own converters. My sample shows how this can be done using Binding
's StringFormat
property: <TextBlock [...] Text="{Binding Path=CurrentMonth, StringFormat={}{0:MMMM yyyy}}"/>
.
<Window x:Class="DataBindingUI.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ViewModelLibrary.CalendarViewModel;assembly=ViewModelLibrary"
Title="Data Binding Sample"
MinHeight="300" MinWidth="300" Background="Black" SizeToContent="WidthAndHeight">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:Week}">
<ItemsControl ItemsSource="{Binding Path=Days}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:CalendarDay}">
<Border Background="DarkGray" Margin="5" CornerRadius="5" BorderThickness="2">
<TextBlock Text="{Binding Path=Day, StringFormat={}{0:dd}}" Margin="10" />
</Border>
</DataTemplate>
</Window.Resources>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Prev." />
<TextBlock Grid.Column="1"
Text="{Binding Path=CurrentMonth, StringFormat={}{0:MMMM yyyy}}"
Foreground="White" HorizontalAlignment="Center" />
<Button Grid.Column="2" Content="Next" />
<Border Grid.Row="1" Grid.ColumnSpan="3" Margin="0,5,0,0"
BorderBrush="LightGray" CornerRadius="5" BorderThickness="2"
Background="White">
<ItemsControl ItemsSource="{Binding Path=Weeks}" />
</Border>
</Grid>
</Window>
One final piece is missing: the code-behind file for the XAML. If you use MVVM, it is typical that your code-behind files are nearly empty. You see this in our example, too:
using System.Windows;
using ViewModelLibrary.CalendarViewModel;
namespace DataBindingUI
{
public partial class Window1 : Window
{
private Calendar viewModel;
public Window1()
{
InitializeComponent();
this.DataContext = this.viewModel = new Calendar();
}
}
}
From WPF to Silverlight - Converter rulez!
WPF and Silverlight are similar, no one questions that. However, if you try to go for a single-codebase for the WPF and the Silverlight version of your application, you will run against many walls. Soon, you will become aware of many subtle differences between WPF and Silverlight that will make you crazy; data binding is no exception from that rule. Let's try to migrate the existing example from above to Silverlight. The first step is the viewmodel. In most cases, you can share your viewmodel implementation between WPF if you follow a single, simple guideline: whenever you access the network (e.g., call a Web Service), make your call async! The reason for this is that Silverlight can only access the network asynchronously.
But, let's get back to our case. As of now, we do not use any Web Service. Because of this, we can use our viewmodel without a single change. However, we cannot use the XAML file we have written for Silverlight because of the following reasons:
- Silverlight does not support assigning a
DataType
to a DataTemplate
- Silverlight's
Binding
class does not know StringFormat
The first one is easy to solve. We have to remove the DataType
property from the template and reference the template using its key (I will not go into details here because it has little to do with data binding).
The second one is harder. It is symptomatic for data binding in Silverlight: you will need to write many, many converters. Data binding mechanisms in Silverlight are much less powerful than those in WPF. You have to get around the weaknesses yourself by using converters. The converter in our case is - by now - quite simple (note that the implementation is quite rudimental, no error handling etc., here):
using System;
using System.Globalization;
using System.Windows.Data;
namespace ViewModelLibrary.CalendarViewModel
{
public class DateTimeToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return ((DateTime)value).ToString((string)parameter, culture);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
}
With this converter in the viewmodel-layer, we can write the XAML for the Silverlight version of our calendar:
<UserControl x:Class="DataBindingSilverlightUI.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ViewModelLibrary.CalendarViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<UserControl.Resources>
<vm:DateTimeToStringConverter x:Key="DateTimeToStringConverter" />
<DataTemplate x:Key="DayTemplate">
<Border Background="DarkGray" Margin="5" CornerRadius="5" BorderThickness="2">
<TextBlock Text="{Binding Path=Day, Converter={StaticResource
DateTimeToStringConverter}, ConverterParameter=dd}"
Margin="10" />
</Border>
</DataTemplate>
<DataTemplate x:Key="WeekTemplate">
<ItemsControl ItemsSource="{Binding Path=Days}"
ItemTemplate="{StaticResource DayTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</UserControl.Resources>
<Grid Background="Black">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Prev." />
<TextBlock Grid.Column="1"
Text="{Binding Path=CurrentMonth, Converter={StaticResource
DateTimeToStringConverter}, ConverterParameter='MMMM yyyy'}"
Foreground="White" HorizontalAlignment="Center" />
<Button Grid.Column="2" Content="Next" />
<Border Grid.Row="1" Grid.ColumnSpan="3" Margin="0,5,0,0"
BorderBrush="LightGray" CornerRadius="5" BorderThickness="2"
Background="White">
<ItemsControl ItemsSource="{Binding Path=Weeks}"
ItemTemplate="{StaticResource WeekTemplate}"/>
</Border>
</Grid>
</Grid>
</UserControl>
Couldn't we have written the Silverlight version of the XAML first and used this code for WPF? We could, in this simple example. In practice, you will find lots and lots of situations where this isn't possible. The experience in my team is that you end up writing most of the XAML twice, one time for Silverlight, and one time for WPF. However, keep in mind that it is quite easy to share a single viewmodel-layer!
I want to demonstrate the use of more complex converters in Silverlight by extending our example a little bit further. As promised before, we are going to display those days that are not in the selected month differently:
In WPF, this really is a piece of cake. Just add a data trigger to the existing data template, and everything is done:
<DataTemplate DataType="{x:Type vm:CalendarDay}">
<Border Name="DayBorder" Background="DarkGray"
Margin="5" CornerRadius="5"
BorderThickness="2">
<TextBlock Name="DayText"
Text="{Binding Path=Day, StringFormat={}{0:dd}}"
Margin="10" />
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsInCurrentMonth}" Value="False">
<Setter Property="Background"
Value="WhiteSmoke" TargetName="DayBorder" />
<Setter Property="Foreground"
Value="LightGray" TargetName="DayText" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
The problem with Silverlight is that it does not know DataTrigger
at all. The solution? Correct, we need a converter. The point that I want to make here is that you should really think about writing converters that are generally usable. It would be possible to write a "BooleanToBorderBackground
" and a "BooleanToTextForeground
" converter here. The problem would be that they can only be used just in this case. A more commonly usable solution would be a converter that implements a kind of "IIF" operation. We pass it a boolean as well as a value for true and one for false.
The problem with converters is that they do not accept more than one input parameter (Binding.ConverterParameter
). One possible workaround for this is to split parameter values using a special character (e.g., ";"), as shown in the following code example:
using System;
using System.Globalization;
using System.Windows.Data;
namespace ViewModelLibrary.CalendarViewModel
{
public class IifConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var possibleReturnValues = parameter.ToString().Split(';');
if (possibleReturnValues.Length != 2)
{
throw new ArgumentException();
}
return ((bool)value) ? possibleReturnValues[0] : possibleReturnValues[1];
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
}
This converter can now be used in Silverlight to get the same effect that we implemented in WPF using a DataTrigger
:
<DataTemplate x:Key="DayTemplate">
<Border Name="DayBorder" BorderThickness="1"
BorderBrush="{Binding Path=IsInMonth, Converter={StaticResource
IIfResourceConverter},
ConverterParameter=TimeframeSelectorSelectedMonthDayBorder;
TimeframeSelectorDayBorder}" CornerRadius="3"
Background="{Binding Path=IsInMonth, Converter={StaticResource IIfResourceConverter},
ConverterParameter=TimeframeSelectorSelectedMonthDayBackground;
TimeframeSelectorDayBackground}"
Width="20" Height="20" Margin="1">
<TextBlock Name="DayTextBlock"
Text="{Binding Path=BeginTime.Day}" FontSize="9"
Foreground="{Binding Path=IsInMonth, Converter={StaticResource
IIfResourceConverter},
ConverterParameter=TimeframeSelectorSelectedMonthDayForeground;
TimeframeSelectorDayForeground}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</DataTemplate>
Splitting parameter values is not a very elegant solution, is it? What if you have to pass more complex objects like images? A better solution (although it is longer to write in XAML) is to implement a helper class that is used to transport multiple parameters into a converter. In our case, we need a helper class that can take an object for the true- and one for the false-case of our IifConverter
:
namespace ViewModelLibrary.CalendarViewModel
{
public sealed class IifConverterArgs
{
public object TrueObject { get; set; }
public object FalseObject { get; set; }
}
}
Of course, we have to change the converter a little bit, too:
using System;
using System.Globalization;
using System.Windows.Data;
namespace ViewModelLibrary.CalendarViewModel
{
public class IifConverterEx : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var iifParameterArgs = parameter as IifConverterArgs;
if ( iifParameterArgs==null )
{
throw new ArgumentException();
}
return ((bool)value) ? iifParameterArgs.TrueObject :
iifParameterArgs.FalseObject;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
}
With this, we can pass multiple parameters to the converter directly in XAML in a more robust way (I keep the old version of the IifConverter
for the TextBlock
's foreground color so you can compare both versions):
<DataTemplate x:Key="DayTemplate">
<Border Margin="5" CornerRadius="5" BorderThickness="2">
<Border.Background>
<Binding Path="IsInCurrentMonth"
Converter="{StaticResource IifConverterEx}">
<Binding.ConverterParameter>
<vm:IifConverterArgs TrueObject="DarkGray" FalseObject="WhiteSmoke" />
</Binding.ConverterParameter>
</Binding>
</Border.Background>
<TextBlock Text="{Binding Path=Day, Converter={StaticResource
DateTimeToStringConverter}, ConverterParameter=dd}"
Foreground="{Binding Path=IsInCurrentMonth,
Converter={StaticResource IifConverter},
ConverterParameter=Black;LightGray}"
Margin="10" />
</Border>
</DataTemplate>
As you have seen, converters are important and powerful tools for data binding, especially in Silverlight. If you start using them, you will soon find yourself in a situation where you want to nest converters (i.e., the result of the first converter should be the input parameter for the second one). Unfortunately, this is not possible, neither in WPF nor in Silverlight. You have to build a third converter that uses the other converters internally.
DLR - Writing the Last Converter that is Ever Written
If you come from ASP.NET or HTML, this is the point where you start missing JavaScript, is it not? MVVM is a nice pattern, and adhering to it brings a lot of advantages. However, there are situations where you need to do only, e.g., a little conversion, and you really do not want to write another converter for that. Well, if you will not tell anyone, I can show you how to enable scripting in XAML again. The solution is called DLR, dynamic language runtime.
If you have not taken a look at DLR, you should! If you ask me, this is the next big thing in developing .NET-based applications. Especially with all the dynamic features of C# 4, dynamic languages will become increasingly popular, powerful, and practicably useable. Integrating it into WPF's or Silverlight's data binding mechanism is surprisingly simple. You need to do four steps:
- Reference the necessary DLLs.
- Configure the DLR in your app.config file (for the WPF version; Silverlight is a little different, but I will not cover the differences in this article).
- Initialize the scripting environment.
- Execute expressions whenever you need to (e.g., in your converter).
Let me make two points clear if we go any further:
- Even if you include scripting in your WPF/Silverlight data binding repertoire, you should not, not, not start to write business logic in your view-layer! This tool is for simple UI-oriented tasks only!
- Be aware that scripting is not the fastes thing on earth. A compiled converter written in C# will always be much faster than a script!
Step 1 - Reference DLLs
Download the necessary DLLs (see DLR Home at CodePlex and IronPython at CodePlex) and reference them in your application.
Step 2 - Add the app.config File
DLR and IronPython need to be configured in your app.config file. It could look something like this:
="1.0" ="utf-8"
<configuration>
<configSections>
<section name="microsoft.scripting"
type="Microsoft.Scripting.Hosting.Configuration.Section,
Microsoft.Scripting, Version=0.9.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</configSections>
<microsoft.scripting>
<languages>
<language names="IronPython;Python;py"
extensions=".py" displayName="IronPython v2.0"
type="IronPython.Runtime.PythonContext, IronPython, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</languages>
</microsoft.scripting>
</configuration>
Steps 3 and 4 - Initialize Scripting Environment and Execute Scripts
The next step is the initialization of the scripting environment. Be aware that this step could take a few moments. Therefore, you should not do it every time your converter is called. In the following example, the initialization code is only executed once.
Now, you are ready to use scripting wherever you need it. Here, I implement a converter that is able to execute IronPython expressions in binding scenarios. Note that the converter passes the source of the data binding as the variable Current
into the expression. This means that a script could access the data bound model/viewmodel objects.
using System.Reflection;
using System.Text;
using System.Windows.Data;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
namespace ViewModelLibrary.CalendarViewModel
{
public sealed class ScriptConverter : IValueConverter
{
private static ScriptRuntime ScriptRuntime;
private static ScriptScope ScriptScope;
private static ScriptEngine ScriptEngine;
static ScriptConverter()
{
ScriptConverter.InitializeScriptingEnvironment();
}
public object Convert(object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
ScriptConverter.ScriptScope.SetVariable("Current", value);
return ScriptConverter.ScriptEngine.CreateScriptSourceFromString(
parameter.ToString(), SourceCodeKind.Expression).Execute(
ScriptConverter.ScriptScope);
}
public object ConvertBack(object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException();
}
private static void InitializeScriptingEnvironment()
{
ScriptConverter.ScriptRuntime = ScriptRuntime.CreateFromConfiguration();
ScriptConverter.ScriptRuntime.LoadAssembly(Assembly.GetExecutingAssembly());
ScriptConverter.ScriptScope = ScriptConverter.ScriptRuntime.CreateScope();
ScriptConverter.ScriptEngine = ScriptConverter.ScriptRuntime.GetEngine("py");
ScriptConverter.ScriptEngine.Runtime.LoadAssembly(typeof(DateTime).Assembly);
var script = new StringBuilder();
script.AppendLine("from System import DateTime, DayOfWeek");
ScriptConverter.ScriptEngine.CreateScriptSourceFromString(script.ToString(),
SourceCodeKind.Statements).Execute(ScriptConverter.ScriptScope);
}
}
}
Now everything is ready. As an example, we will use the script converter to display all Sundays with a red border. The IronPython expression that is needed for that looks like this: "Red" if ( Current.Day.DayOfWeek == DayOfWeek.Sunday ) else "Transparent"
. With the script converter, you can add this expression directly into your XAML code. In practice, you would not hardcode it there - instead, it would probably come from a user customization, a configuration file, or something like this.
<DataTemplate DataType="{x:Type vm:CalendarDay}">
<Border Name="DayBorder" Background="DarkGray"
Margin="5" CornerRadius="5" BorderThickness="2">
<Border.BorderBrush>
<Binding Converter="{StaticResource ScriptConverter}"
ConverterParameter='"Red" if ( Current.Day.DayOfWeek ==
DayOfWeek.Sunday ) else "Transparent"' />
</Border.BorderBrush>
<TextBlock Name="DayText" Text="{Binding Path=Day, StringFormat={}{0:dd}}" Margin="10" />
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsInCurrentMonth}" Value="False">
<Setter Property="Background" Value="WhiteSmoke" TargetName="DayBorder" />
<Setter Property="Foreground" Value="LightGray" TargetName="DayText" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
From Data Bindings to Command Bindings
There is still something missing in our calendar example: The buttons for navigating to the next or previous month don't have any functionality by now. Most developers I know would assign a function in the code-behind file of the window to the Click
event of the button:
<Button Content="Prev." [...] Click="Button_Click" />
private void Button_Click(object sender, RoutedEventArgs e)
{
[...]
}
Although this solution works, it is not the preferred way of solving the underlying problem in conjunction with the MVVM pattern. The viewmodel class already offers everything that is needed for data binding. Given this, we could remove nearly all of the code from the window's code-behind file. Wouldn't it be nice to be able to "bind" the button directly to a method that is provided by the viewmodel-layer?
It turns out that this is possible by using command bindings. UI controls like Button
, MenuItem
, etc. (technically speaking all classes that implement ICommandSource
), offer a property Command
. Command
is of type ICommand
. If you take a closer look at, e.g., Button
, you will see that the property Command
is a dependency property. Hurrah, everything is there when we need to use data binding!
To be able to bind a command to a viewmodel class, this class has to provide a property of type ICommand
. In our case, we need two commands: navigate to next month, navigate to previous month. A possible solution would be to implement two classes that implement ICommand
, one for "next month" and one for "previous month". This is definitively the correct approach if the commands would consist of more or less complex business logic. In our case, the logic is very, very simple. In practice, you will see that the command logic is quite often very simple (just changing a property, calling a method, etc.). Because of this, it is recommended to write a generic implementation of ICommand
that can be used for multiple cases.
You can find different templates for such a generic command class in the web. Here is one that suites our needs in our example. Note that ICommand
's CanExecuteChanged
event is triggered whenever a property changes in the associated object implementing INotifyPropertyChanged
. The implementation shown here works for WPF and Silverlight.
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace ViewModelLibrary.CalendarViewModel
{
public class DelegateCommand<T> : ICommand
{
private readonly Func<T, bool> canExecuteMethod = null;
private readonly Action<T> executeMethod = null;
public DelegateCommand(INotifyPropertyChanged parentObject, Action<T> executeMethod)
: this(parentObject, null, executeMethod)
{
}
public DelegateCommand(INotifyPropertyChanged parentObject,
Func<T, bool> canExecuteMethod, Action<T> executeMethod)
{
this.canExecuteMethod = canExecuteMethod;
this.executeMethod = executeMethod;
parentObject.PropertyChanged += delegate(object sender,
PropertyChangedEventArgs args)
{
if (this.CanExecuteChanged != null)
{
this.CanExecuteChanged(this, args);
}
};
}
public bool CanExecute(object parameter)
{
if (this.canExecuteMethod == null)
{
return true;
}
else
{
return this.canExecuteMethod((T)parameter);
}
}
public void Execute(object parameter)
{
this.executeMethod((T)parameter);
}
public event EventHandler CanExecuteChanged;
}
}
Given this helper class, it is easy to offer two properties with commands for month navigation in the viewmodel-class. For demonstration purposes, it is only possible to select months in the years 2009 and 1020:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Input;
namespace ViewModelLibrary.CalendarViewModel
{
public sealed class Calendar : INotifyPropertyChanged
{
public Calendar()
{
this.PropertyChanged +=
new PropertyChangedEventHandler(OnCalendarPropertyChanged);
this.CurrentMonth = new DateTime(2009, 10, 13);
this.nextMonthCommand = new DelegateCommand<Calendar>(
this,
c => c.CurrentMonth.AddMonths(1).Year < 2010,
delegate(Calendar c) { c.CurrentMonth = c.CurrentMonth.AddMonths(1); });
this.prevMonthCommand = new DelegateCommand<Calendar>(
this,
c => c.CurrentMonth.AddMonths(-1).Year >= 2009,
delegate(Calendar c) { c.CurrentMonth = c.CurrentMonth.AddMonths(-1); });
}
[...]
private ICommand nextMonthCommand;
public ICommand NextMonthCommand
{
get
{
return this.nextMonthCommand;
}
}
private ICommand prevMonthCommand;
public ICommand PrevMonthCommand
{
get
{
return this.prevMonthCommand;
}
}
[...]
}
}
The last step is the command binding in the XAML file. There is really no difference to data bindings except that the binding result is ICommand
. Be aware that only WPF offers command support as shown here, Silverlight does not know the Command
property out of the box (there are implementations of ICommand
for Silverlight out in the web, just Bing/Google for something like "ICommand Silverlight").
<Button Content="Prev."
CommandParameter="{Binding}"
Command="{Binding Path=PrevMonthCommand}" />
[...]
<Button Grid.Column="2" Content="Next"
CommandParameter="{Binding}"
Command="{Binding Path=NextMonthCommand}" />
Important tip: Always specify the command's parameter first and then the command. Otherwise, it can happen that the command gets NULL
for the command parameter.
Async Data Binding
By now, you have seen how we can use data binding together with the MVVM pattern to get a slim code-behind file. We have bound data and commands from the UI-layer to the viewmodel-layer. This works great in "hello world" applications. In practice, things tend to get more complex as soon as it comes to this nasty thing called async.
The two most common scenarios in which we need to do things asynchronously are:
- Execute long running operations in the background while keeping the UI responsive.
- Do something asynchronously because some technical limitation is forcing us to.
Can you guess who is responsible for the "technical limitation" I mentioned in point 2? Correct, Silverlight again. Silverlight cannot access the network synchronously. This means that you have to do async calls whenever you want to perform any operation involving the network. This includes Web Service or REST calls, too. Silverlight gets all its data over the wire so viewmodel-classes in Silverlight are full of async stuff - unfortunately.
WPF makes things much easier. On the one hand, you can mark data bindings as async by using the property Binding.IsAsync
. This covers point 1 in our list mentioned above. Unfortunately, Silverlight does not offer this property. However, Microsoft recommends not using it anyway (see the MSDN documentation of IsAsync
). Therefore, I will not go into the details about it here. WPF also does not force you to use async calls if you need to access the network. You could, for instance, issue a Web Service call directly in the property that the UI binds against. If you are going to do this, you should really think about whether this is a good idea. Probably, the Web Service call will take a while, and because of this, you should consider programming asynchronously even if WPF would allow you to work in a synchronous style.
To demonstrate how you can do data binding asynchronously in WPF and Silverlight, we are going to extend our calendar-example. Our goal is to asynchronously load a list of Flickr photos via Flickr's REST Web Service. The photo list should only include photos that have been taken in the selected month. Therefore the list has to be refreshed whenever the currently selected month changes.
Let's start by extending our viewmodel-layer. We have to do two things:
- Add a property of type
IList<Uri>
in which we offer the list of Flickr photos. The view-layer can bind, e.g., an ItemsControl
control to this list.
- Retrieve the list of photos from Flickr asynchronously whenever the currently selected month changes.
The first step is simple. Here is the code:
private IList<Uri> flickrUriList;
public IList<Uri> FlickrUriList
{
get { return this.flickrUriList; }
set
{
this.flickrUriList = value;
this.OnPropertyChanged("FlickrUriList");
}
}
The second one is harder. The viewmodel-layer has to work for Silverlight and WPF. Therefore, we are forced to work asynchronously. Getting Flickr photos is the simple part. The website offers a set of REST-based APIs. In this case, I just use .NET's WebRequest
class to query Flickr's flickr.photos.search
API call. Before you take a look at the code below, note the following important points:
- We have to use
WebRequest
's BeginGetResponse
method instead of GetResponse
.
- In the async callback method, we cannot use our
OnPropertyChanged
method directly because it uses resources that can only be used from the UI thread. Therefore, we have to indirectly call OnPropertyChanged
through the WPF/Silverlight Dispatcher
object (note that getting the dispatcher is simple in WPF, but tricky in Silverlight; in Silverlight, you have to remember the dispatcher in your App
class).
Here is the complete implementation of the OnCalendarPropertyChanged
method:
private void OnCalendarPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CurrentMonth")
{
RefreshWeekList();
RefreshFlickrPhotoList();
}
}
private void RefreshFlickrPhotoList()
{
this.FlickrUriList = null;
var request = WebRequest.Create(string.Format("http://api.flickr.com/services/" +
"rest/?method=flickr.photos.search&api_key=<insert_your_flickr_api_key_here>" +
"&min_taken_date={0}&max_taken_date={1}",
this.CurrentMonth.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
this.CurrentMonth.AddMonths(1).AddDays(-1).ToString("yyyy-MM-dd",
CultureInfo.InvariantCulture)));
request.BeginGetResponse(delegate(IAsyncResult asyncResult)
{
using (var response = request.EndGetResponse(asyncResult))
{
using (var stream = response.GetResponseStream())
{
using (var reader = new StreamReader(stream))
{
var flickrResult = XDocument.Load(reader);
this.flickrUriList =
(from r in flickrResult.Descendants("photo")
select new Uri(string.Format(CultureInfo.InvariantCulture,
"http://farm{0}.static.flickr.com/{1}/{2}_{3}.jpg",
r.Attribute("farm").Value,
r.Attribute("server").Value,
r.Attribute("id").Value,
r.Attribute("secret").Value), UriKind.Absolute)).ToList();
#if ( SILVERLIGHT )
App.MainPageDispatcher.BeginInvoke(
new Action(() => this.OnPropertyChanged("FlickrUriList")), null);
#else
Application.Current.Dispatcher.BeginInvoke(
new Action(() => this.OnPropertyChanged("FlickrUriList")), null);
#endif
}
}
}
}, null);
}
private void RefreshWeekList()
{
var result = new List<Week>();
DateTime currentDate;
currentDate = (currentDate = this.CurrentMonth.AddDays((-1) *
(this.CurrentMonth.Day - 1)) - this.CurrentMonth.TimeOfDay).AddDays(
(((int)currentDate.DayOfWeek) + 6) % 7 * (-1));
while (currentDate.Month == this.CurrentMonth.Month ||
currentDate.AddDays(6).Month == this.CurrentMonth.Month)
{
result.Add(new Week(currentDate, this.CurrentMonth.Year,
this.CurrentMonth.Month));
currentDate = currentDate.AddDays(7);
}
this.Weeks = result;
}
Given this, adding the list of Flickr photos in XAML is nothing special, just a simple data binding. Note that the following code sample shows the WPF version of the XAML. The viewmodel works for Silverlight and WPF. The XAML has to be changed a little bit for Silverlight because the WrapPanel
panel is not part of Silverlight 3.0 but part of the Silverlight toolkit. You can take a look at the Silverlight implementation in the source code download for this article.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Margin="5">
[...]
</Grid>
<ItemsControl Grid.Column="1" ItemsSource="{Binding Path=FlickrUriList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Path=AbsoluteUri}" Width="75" Margin="5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
An alternative would have been to set the type of the FlickrUriList
property to an ObservableCollection<Uri>
. In this case, the viewmodel-method would not create a completely new list every time the selected month changes. Instead, it would add and remove items from the observable collection. Data binding automatically recognizes changes in observable collections; therefore, the end result would be the same.
RelativeSource Bindings
You want to know what else data binding can do for you? A lot! I would like to close this article with a problem that people ask me about over and over again: Why do RelativeSource
-bindings exist, and how do they work?
Remember, there are different ways in which you can specify the source of a binding:
- Use
ElementName
to refer to a XAML element by its name.
- Use
Source
to refer to an existing object (e.g., an object that lives in the page's resources).
- Last but not least, there is
RelativeSource
.
RelativeSource
enables you to look for the source relative to the destination object (i.e., the object on which the binding is defined). The rule on how the source is looked up is typically defined using the RelativeSource
markup extension:
- Silverlight and WPF
RelativeSource.Mode = TemplatedParent
: Use this mode if your are writing the XAML for a template (e.g., a template for a custom control) and you want to bind to a property from the control's class.
RelativeSource.Mode = Self
: Normally, the binding's Path
is relative to the object's data context. However, if you specify RelativeSource.Mode = Self
, the path is relative to the object on which you define the binding.
- Only WPF
RelativeSource.Mode = PreviousData
: Use this mode in lists to bind to the previous item in the list.
RelativeSource.Mode = FindAncestor
: With this mode, you can look up source objects by walking up the tree of XAML nodes.
I want to demonstrate FindAncestor
with a short example. Remember our calendar example? It could be a good idea to encapsulate the calendar control into a reusable user control. Doing so is quite easy. You just have to add a new user control to your project and copy&paste the calendar's grid into the user control, and you are done:
<UserControl x:Class="DataBindingUI.CalendarControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Prev." CommandParameter="{Binding}"
Command="{Binding Path=PrevMonthCommand}" />
<TextBlock Grid.Column="1"
Text="{Binding Path=CurrentMonth, StringFormat={}{0:MMMM yyyy}}"
Foreground="White" HorizontalAlignment="Center" />
<Button Grid.Column="3" Content="Next"
CommandParameter="{Binding}"
Command="{Binding Path=NextMonthCommand}" />
<Border Grid.Row="1" Grid.ColumnSpan="4" Margin="0,5,0,0"
BorderBrush="LightGray" CornerRadius="5" BorderThickness="2"
Background="White">
<ItemsControl ItemsSource="{Binding Path=Weeks}" />
</Border>
</Grid>
</UserControl>
Note that you do not have to change anything concerning the existing data bindings. The user control is embedded in the main window, and it inherits the data context (i.e., our viewmodel-layer) from there.
So, why do we need anything special here? To understand why there are relative source bindings, we have to take a look at the XAML code that uses the new user control:
<local:CalendarControl />
Imagine you are the user of the user control and in a certain case you want its background to be red. The problem is that the user control's background is hard-coded into its XAML:
<Border [...] Background="White">
In such cases, it is not a good idea to add a property Background
to the viewmodel! You can use the existing Background
property of UserControl
instead:
<local:CalendarControl Background="Red" />
The data binding expression in the user control cannot be just {Binding Path=Background}
because WPF would look for a property called Background
in the viewmodel. Exactly for those cases has Microsoft built relative source bindings. The binding has to look up the binding source by walking up the tree of XAML nodes until it finds a node of type UserControl
. From there, it has to take the Background
property:
<Border [...]
Background="{Binding RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
Conclusion
Data binding is a powerful tool in WPF and Silverlight. Build your applications by following the MVVM pattern, and it will save you a lot of time, you will produce more testable software, and you will live happily ever after ;-)