<< Introduction To Data Binding for Windows and Windows Phone (part 1)
In the first part of this series we introduced the concept of data binding and how it can be used to associated an attribute on a visual element with a property on a data entity. The question we left unanswered is “why should we use data binding?”. There are countless answers to this question and rather than bore you with them I want to illustrate some of neat things you can do with a combination of data binding and the designer experience in both Visual Studio and Blend.
Example Requirements: Display a simple list of people; each person is represented by a Name and an Age; the list should display both Name and Age; when the user selects an item in the list, a MessageBox should be displayed indicating which person was selected.
We’re going to need a Person class:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
and an array of people:
var people = new[]
{
new Person {Name = "Fred", Age = 29},
new Person {Name = "Matilda", Age = 13},
new Person {Name = "Jane", Age = 33}
};
We’ll create a ListBox on our page (we’ve added the SelectionChanged event handler at this point too so we don’t need to come back later…)
<Grid>
<ListBox x_Name="PeopleList"
SelectionChanged="PersonSelectionChanged" />
</Grid>
Let’s start off doing this the hard way, without data binding. We need to add each person in the people array into the ListBox:
foreach (var person in people)
{
PeopleList.Items.Add(person);
}
Which displays as the following – we have the right number of rows in the ListBox but not the desired look.
A quick and dirty solution is to add DisplayMemberPath="Name" to the ListBox declaration. This yields a minor improvement where we can at least see the person’s name.
In order to style the list any further for each person we need to create a ListBoxItem and child elements to represent the visual layout we want.
var textStyle = Application.Current.Resources["PhoneTextNormalStyle"] as Style;
foreach (var person in people)
{
var lbi = new ListBoxItem();
var sp = new StackPanel {
Orientation = System.Windows.Controls.Orientation.Horizontal };
sp.Children.Add(new TextBlock {
Text = person.Age.ToString(),
Style = textStyle });
sp.Children.Add(new TextBlock {
Text = person.Name,
Style = textStyle });
lbi.Content = sp;
PeopleList.Items.Add(lbi);
}
The structure for each person in the list is a horizontal StackPanel containing two TextBlock elements, displaying the Age and Name of the person. We’ve used one of the built in styles, PhoneTextNormalStyle, to try to improve the appearance of the TextBlocks.
Now, lets complete the example by writing the event handler which is going to display the name of the person that the user has selected in the list:
private void PersonSelectionChanged(object sender,
SelectionChangedEventArgs e) {
var lst = sender as ListBox;
var lbi = lst.SelectedItem as ListBoxItem;
.... some nasty code to get the Name out....
At this point we figure we have a problem – we can get the Name and Age values by looking for the appropriate TextBlocks and extracting the Text property. However, this doesn’t give us a reference to the Person object…. in fact the original person objects are now out of scope and no longer exist. We need a way to pass the Person object along for the ride. Let’s simply set the Tag property on the ListBoxItem (ie lbi.Tag = person; ). Completing the code we have:
private void PersonSelectionChanged(object sender,
SelectionChangedEventArgs e)
{
var lst = sender as ListBox;
var lbi = lst.SelectedItem as ListBoxItem;
var person = lbi.Tag as Person;
MessageBox.Show(person.Name);
}
This is an incredibly simple, contrived, example and yet we’ve had to write quite a lot of code and do some hacky things in order to pass data around. Now imagine the scenario that the requirements change and that the order of the Age and Name should be reversed, or a vertical StackPanel should be used, or another property is added to the Person class which needs to be displayed. In this example it wouldn’t be that hard to find the line(s) of code to change but you can imagine that in a large complex system it would be a nightmare to find the write code, visualise what the item will look like and make the appropriate change. Wouldn’t it be nice if there was a design tool to help?
The good news is that by using data binding you:
a) get to reduce and simplify the code you write
b) get a design experience for constructing the user experience
At this point I’m going to step into Blend as I prefer its design experience over the limited designer in Visual Studio. Whilst most of the tool windows and the design surface are now shared between the products, there are still some design aspects that Blend has which Visual Studio doesn’t (such as being able to design Visual States). I also find that by switching tools I’m making a mental shift from developer mode to designer mode.
Open your solution in Blend.
The first thing we’re going to do is to create some sample data with the same shape as the data we need to display. When I say shape I’m referring to the object hierarchy and the properties on the classes. In this example we have an array of Person objects, each one with an Age (Number) and Name (String). So, we need to create design time data which has a collection of entities, each with an attribute called Age which is a Number, and an attribute called Name which is a String.
From the Data windows, select New Sample Data
Give your sample data a name, and hit OK (we’ll come back to the “enable sample data” option a little later).
In the Data window you can now adjust the names of the properties: Change Collection to PersonCollection, Property1 to Name and Property 2 to Age, and change the Type of Age to Number.
Drag the PersonCollection node from the Data window across onto the PeopleList node on the Obejcts and Timeline window (note the help prompt that appears indicating what’s about to happen).
When you let the mouse go, you’ll see a new ListBox appear on the screen full of the sample data you just created.
Now all you need to do is to change the layout according to the requirements. Right-click the PeopleList node in the Objects and Timeline window and select Edit Additional Template; Edit Generated Items (ItemTemplate); Edit Current.
You’re now in template editing mode where you can modify the layout of each item in the list (you can think of the ItemTemplate being a cookie cutter or a mould for each item that will appear in the list).
Select the StackPanel and change the orientation to Horizontal via the Properties window.
Right-click on each TextBlock in turn and select Edit Style; Apply Resource; PhoneTextNormalStyle
Your layout, at design time, should look very similar to the desired output.
You can run the application to verify how it appears but you’ll notice that it is still displaying the design time data. We need to firstly disable design time data, at runtime, and secondly we need to actually wire up the real data.
To disable design time data at runtime, from the Data window, click the icon alongside the PeopleDataSource and uncheck the Enable When Running Application. Now when you run the application you won’t see the design time data.
In order to wire up data we need to take a closer look at what Blend did for us when we dragged across the design time data. Looking at the XAML you’ll see that the ItemTemplate and ItemsSource attributes have been specified. In actual fact, what you may not have realised is that you have just been adjusting the ItemTemplate attribute value when modifying the layout for each item.
<Grid>
<ListBox x_Name="PeopleList"
SelectionChanged="PersonSelectionChanged"
ItemTemplate="{StaticResource PersonCollectionItemTemplate}"
ItemsSource="{Binding PersonCollection}" />
</Grid>
It’s the ItemsSource property that we’re particularly interested in – this is what determines where the items in the ListBox are going to be sourced from. This has been data bound to the PersonCollection property on the current DataContext (recall this from my previous post).
There is no DataContext explicitly set on the ListBox. So, the simplest solution to wiring up real data is to set the DataContext on the ListBox to be an entity that has a PersonCollection property which returns the Person entities which should be added to the ListBox. For this we’ll create a MainPageData class (this is kind of the prelude to a ViewModel which we’ll discuss in the context of the MVVM design pattern).
public class MainPageData
{
public Person[] PersonCollection { get; set; }
}
Now all we need to do is to create an instance of our MainPageData and set it as the DataContext on the ListBox.
PeopleList.DataContext = new MainPageData
{PersonCollection = people};
Furthermore, our event handler code can be simplified because the SelectedItem property now returns the actual data item (ie a Person) instead of a generic ListBoxItem.
private void PersonSelectionChanged
(object sender, SelectionChangedEventArgs e)
{
var lst = sender as ListBox;
var person = lst.SelectedItem as Person;
MessageBox.Show(person.Name);
}
You might be thinking “how is our data being wired up to the layout we defined in the designer for each item in the list?” The answer is in the ItemTemplate attribute of the ListBox. This references a static resource, PersonCollectionItemTemplate, which is defined elsewhere on the same page:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x_Key="PersonCollectionItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Age}"
Style="{StaticResource PhoneTextNormalStyle}" />
<TextBlock Text="{Binding Name}"
Style="{StaticResource PhoneTextNormalStyle}" />
</StackPanel>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
Here we can again see that the structure of each item in the list is a horizontal StackPanel with two TextBlock elements with Style set to PhoneTextNormalStyle. You’ll also notice that the Text attribute for each TextBlock has been data bound to the Age and Name properties respectively. Whilst no DataContext is explicitly defined for either TextBlock, nor the StackPanel, the DataContext is implicitly set to be the item in the list that is being rendered. In other words, when a Person object is being displayed in the list, this template is used to stamp out the appropriate visual elements. The DataContext for each element in the template is set to the Person object, and the data binding expression establishes an association between the Text attributes and the Age and Name properties on the Person object.
The upshot is that we have significant less C# code for wiring up the ListBox data and, other than setting the DataContext on the ListBox, it doesn’t manipulate the user interface. We also have a design time experience which will allow us to easily go back and tweak the layout of the ListBox items at any time in the future.
Hopefully you’ve started to see the power of data binding. This is just the tip of the iceberg and we’ll go into more detail in coming posts.