1. Problem
You need to bind various properties on UI objects in Silverlight to application data available in managed code.
2. Solution
Use the Binding markup extension to specify the appropriate data bindings in XAML or use the FrameworkElement.SetBinding() method to do the same in the codebehind.
3. How It Works
The Binding markup extension or the FrameworkElement.SetBinding()
method can be used to bind properties on instances of application data
types to dependency properties defined on any type inheriting from FrameworkElement. The application data type properties are called the source properties for the binding, and the dependency properties are called the target properties.
3.1. Binding Expression
XAML exposes several markup extensions, one of which is Binding.
A markup extension provides an extension to the default XAML namespaces
and is used as a keyword within a set of curly braces in your XAML
document. The syntax for using the Binding extension to bind some data to a property is as follows:
The entire Binding statement within the curly braces, including all the property settings, is collectively called a binding expression.
The targetPropertyName points to a dependency property on the XAML element. The sourcePropertyPath
is the complete, qualified path to a property in the data source object
hierarchy. The dot notation can be used to indicate properties
belonging to type instances that are nested within other instances. The
Binding extension also
exposes several properties that can be set to impact various behaviors
of the binding, such as the direction of the data flow, input
validation behavior, or conversion of values. We will discuss most of
the properties in subsequent recipes.
3.2. Dependency Properties
Dependency properties are
unique to the Silverlight runtime and have some differences compared
with standard .NET properties. One of the major differences is that a
target property needs to be a dependency property to allow data
binding. You can read about dependency properties in the Silverlight SDK documentation.
3.3. Associating the Data Source
The data source is a CLR object, and the source properties are public properties with at least public get accessors defined. The data source can be set as the DataContext
on the element at the level where the binding is being set or on a
containing element, thus making it automatically available as the DataContext to all children elements. The following is an example of using the DataContext, where a CLR type declared as a resource named CLRDS_Company is being used as the data source on the Grid and the Text property on a contained TextBlock is being bound to the Street property on the data source object:
DataContext="{StaticResource CLRDS_Company}">
The data source can also be set as the Source
property on the binding itself. This may be necessary if the data for
the elements in your UI came from different data sources and just
providing a higher-level DataContext was not enough. A sample of the syntax for that would look like the following, where the Source can be set to a different data source than the one defined in the data context:
In either case, you can define the data source by referencing a CLR type as a resource in your XAML, as shown here:
The local: prefix is a custom namespace defined to bring in the Company type. Both custom namespace mapping and the StaticResource extension used to reference such resources in XAML.
4. The Code
The CLR object model shown in Listing 1 is being used as the application data source for this sample.
Listing 1. Application Data Classes
using System.Collections.Generic;
namespace Recipe4_1 { public class Employee { public string FirstName { get; set; } public string LastName { get; set; } public long PhoneNum { get; set; } } public class Company { public string Name { get; set; } public string Street { get; set; } public string City { get; set; } public string State { get; set; } public int ZipCode { get; set; } public List Employees { get; set; }
public Company() { this.Name = "Woodgrove Bank"; this.Street = "555 Wall Street"; this.City = "New York"; this.State = "NY"; this.ZipCode = 10005; this.Employees = new List();
this.Employees.Add( new Employee { FirstName = "Joe", LastName = "Duffin", PhoneNum = 2125551212 }); this.Employees.Add( new Employee
{ FirstName = "Alex", LastName = "Bleeker", PhoneNum = 7185551212 });
//rest of the initialization code omitted for brevity this.Employees.Add(new Employee { FirstName = "Nelly", LastName = "Myers", PhoneNum = 7325551212 }); this.Employees.Add(new Employee { FirstName = "Marcus", LastName = "Bernard", PhoneNum = 7325551414 }); this.Employees.Add(new Employee { FirstName = "Juliette", LastName = "Bernard", PhoneNum = 7325551414 }); this.Employees.Add(new Employee { FirstName = "Cory", LastName = "Winston", PhoneNum = 9085551414 }); this.Employees.Add(new Employee { FirstName = "Randall", LastName = "Valetta", PhoneNum = 2015551414 });
this.Employees.Add(new Employee { FirstName = "Maurice", LastName = "Dutronc", PhoneNum = 3635551414 }); this.Employees.Add(new Employee {
FirstName = "Nathan", LastName = "Adams", PhoneNum = 3635551414 }); this.Employees.Add(new Employee { FirstName = "Harold", LastName = "Anthony", PhoneNum = 3745551414 }); this.Employees.Add(new Employee { FirstName = "Paul", LastName = "Gomez", PhoneNum = 3415551414 });
this.Employees.Add(new Employee { FirstName = "Martha", LastName = "Willard", PhoneNum = 4795551414 }); this.Employees.Add(new Employee { FirstName = "Barry", LastName = "Driver", PhoneNum = 4165551414 }); this.Employees.Add(new Employee { FirstName = "Peter", LastName = "Martinson", PhoneNum = 4165551414 }); this.Employees.Add(new Employee { FirstName = "Mike", LastName = "Dempsey", PhoneNum = 4165551656 }); } } }
|
The XAML page shown in Listing 2 declares an instance of the Company class to use as a data source.
Listing 2. XAML for the Page
<UserControl x:Class="Recipe4_1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:Recipe4_1"
Width="400" Height="300" >
<UserControl.Resources>
<local:Company x:Key="CLRDS_Company" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White"
DataContext="{StaticResource CLRDS_Company}" Margin="8,8,8,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.38*"/>
<ColumnDefinition Width="0.032*"/>
<ColumnDefinition Width="0.238*"/>
<ColumnDefinition Width="0.028*"/>
<ColumnDefinition Width="0.322*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.103*"/>
<RowDefinition Height="0.114*"/>
<RowDefinition Height="0.783*"/>
</Grid.RowDefinitions>
<TextBlock Grid.ColumnSpan="5" x:Name="tbxCompanyName"/>
<TextBlock Text="{Binding Street}" Grid.ColumnSpan="1" Grid.Row="1" />
<TextBlock Text="," Grid.Column="1" Grid.Row="1" />
<TextBlock Text="{Binding City}" Grid.Column="2"
Grid.ColumnSpan="1" Grid.Row="1" />
<TextBlock Text="," Grid.Column="3" Grid.ColumnSpan="1"
Grid.Row="1" Grid.RowSpan="1"/>
<StackPanel Margin="0,0,0,8" Orientation="Horizontal"
Grid.Column="4" Grid.ColumnSpan="1"
Grid.Row="1" Grid.RowSpan="1">
<TextBlock Margin="0,0,5,0" Text="{Binding State}" />
<TextBlock Text="{Binding Zip}"/>
</StackPanel>
<ListBox x:Name="lbxEmployees" Grid.RowSpan="1" Grid.Row="2"
Grid.ColumnSpan="5" ItemsSource="{Binding Employees}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding FirstName}"
Margin="0,0,5,0" />
<TextBlock Grid.Column="1" Text="{Binding LastName}"
Margin="0,0,5,0"/>
<TextBlock Grid.Column="2" Text="{Binding PhoneNum}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
To bind the data to the UI, you set the DataContext property on the top-level grid named LayoutRoot. As discussed earlier, the DataContext
is made available to all contained controls inside the grid so that any
binding expression automatically uses it as the binding source, thus
negating the need to specify the source explicitly in the binding
statement. So for each of the binding expressions in the UI for the
controls contained immediately within the grid, you simply specify the
name of the property in the Company type to which you want to bind the control's property.
You use a ListBox to display the Employees collection in the Company instance. You set the ItemTemplate for the ListBox to a DataTemplate that defines how each item in the ListBox is displayed. The DataTemplate
is discussed in greater detail later in this chapter, but for now,
think of it as a way to package the UI representation of a specific
data type. By binding the ItemsSource property of the ListBox to the Employees property on the Company instance, you effectively provide an instance of the Employee class as the data source for each item in the ListBox. In the DataTemplate, you can then bind properties of individual elements to properties on the Employee class to display employee data. The page output for the application is shown in Figure 1.
Note that the Company type being referenced as the CLRDS_Company
resource will also need to have a default constructor defined to be
referenced in XAML this way. If you do not have a default constructor,
you can instantiate the type and set the DataContext in code like so:
LayoutRoot.DataContext = new Company(SomeParameter);
You can also create and set bindings in code if you need to. To do so, create and initialize an instance of the Binding type, and then use the SetBinding() method on the FrameworkElement type to associate it with a specific DependencyProperty, as shown in Listing 3.
Listing 3. Creating a Binding in Code
using System.Windows.Controls; using System.Windows.Data;
namespace Recipe4_1 { public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); //In case you want to set the datacontext in code... //LayoutRoot.DataContext = new Company(); //create a new Binding Binding CompanyNameBinding = new Binding("Name"); //set properties on the Binding as needed CompanyNameBinding.Mode = BindingMode.OneWay; //apply the Binding to the DependencyProperty of //choice on the appropriate object tbxCompanyName.SetBinding(TextBlock.TextProperty, CompanyNameBinding);
} } }
|
Before you apply the Binding, you can also set various properties on the Binding to control its behavior. The BindingMode setting in Listing 3 is one such property. BindingMode controls the direction of data flow in the Binding, and the OneWay setting stipulates that data only flow from the source to the target in this case.
To utilize the code in Listing 3,
you will need to name the element that is targeted by the binding in
XAML appropriately so that it becomes accessible to you in code. In the
following snippet, you see how to name the TextBlock tbxCompanyName in Listing 2 so that you can refer to it in code.
DataContext="{StaticResource CLRDS_Company}">