Chapter 5. Data Binding and Silverlight List Controls

The previous chapter focused on the form controls contained in Silverlight. In this chapter, you will look at two controls that are made to display lists of data: the ListBox and DataGrid. These controls are typically bound to data through a technique known as data binding, which I'll explore first.

Data Binding

Through data binding, UI elements (called targets) are "bound" to data from a data source (called the source), as illustrated in Figure 5-1. When the data sources change, the UI elements bound to those data sources update automatically to reflect the changes. The data can come from different types of sources, and the target can be just about any UI element, including standard Silverlight controls.

Data binding in Silverlight

Figure 5.1. Data binding in Silverlight

Data binding simplifies application development. Since changes are reflected automatically, you do not need to manually update the UI elements. Also, by using data binding, you are able to separate the UI from the data in your application, which allows for a cleaner UI and easier maintenance.

The Binding Class

Data binding in Silverlight is accomplished by using the Binding class. The Binding class has two components—the source and target—and a property that defines the way the two are bound, called the binding mode. The source is the data that is to be bound, the target is a property of the control that the data is to be bound to, and the mode defines how the data is passed between the source and the target (one-way, one-time, or two-way). You'll see how this works in the upcoming exercise.

To define the binding of a control's property, you use XAML markup extensions, such as {Binding <path>}. For example, to bind the Text property of a TextBox to a data source's FirstName element, you would use the following XAML:

<TextBox Text="{Binding FirstName }"  />

Try It Out: Simple Data Binding in Silverlight

To help explain data binding in Silverlight, let's build a very simple application. The application will include a Book object that contains two properties: Title and ISBN. These properties will be bound to two TextBox controls. Figure 5-2 shows the end result of the example.

Simple data binding example

Figure 5.2. Simple data binding example

  1. Create a new Silverlight application in Visual Studio 2010. Name the project BasicDataBinding, and allow Visual Studio to create a Web application project to host your application.

  2. Edit the MainPage.xaml file to define two columns and six grid rows. Place a TextBlock in each row in column 1 and a TextBox in each row in column 2. Also add some margins and some alignment assignments to improve the layout. The code for the page follows:

    <Grid x:Name="LayoutRoot" Background="White">
    
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
    
        <TextBlock Text="Book Title"
            VerticalAlignment="Center"
            Margin="5" />
        <TextBlock Text="ISBN-13"
            VerticalAlignment="Center"
            Margin="5"
            Grid.Row="1" />
    
        <TextBox Text="{Binding Title}"
            Height="24"
            Margin="5"
            Grid.Column="1" />
        <TextBox Text="{Binding ISBN}"
            Height="24"
            Margin="5"
            Grid.Column="1" Grid.Row="1" />
    
        <TextBlock Text="Book Title"
            VerticalAlignment="Center"
            Margin="5"
            Grid.Row="2" />
        <TextBlock Text="ISBN-13"
            VerticalAlignment="Center"
            Margin="5"
            Grid.Row="3" />
    <TextBox Text="{Binding Title}"
            Height="24"
            Margin="5"
            Grid.Column="1" Grid.Row="2" />
        <TextBox Text="{Binding ISBN}"
            Height="24"
            Margin="5"
            Grid.Column="1" Grid.Row="3" />
    
    </Grid>
  3. Next, edit the code behind, MainPage.xaml.cs. Add a Loaded event handler for the application, which will fire when the application is loaded by the client. This is accomplished with the following source code:

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Page_Loaded);
        }
        void Page_Loaded(object sender, RoutedEventArgs e)
        {
    
        }
    }

    Now you need to add a class to define a Book object. Below the MainPage class, add the following class definition:

    namespace BasicDataBinding
    {
        public partial class MainPage : UserControl
        {
            public MainPage()
            {
                InitializeComponent();
                this.Loaded += new RoutedEventHandler(Page_Loaded);
            }
    
            void Page_Loaded(object sender, RoutedEventArgs e)
            {
    
            }
        }
    public class Book
        {
            public string Title { get; set; }
            public string ISBN { get; set; }
        }
    }
  4. Now that you have Book defined, you need to create an instance of Book and set it to the LayoutRoot's DataContext, as follows:

    void Page_Loaded(object sender, RoutedEventArgs e)
    {
        Book b = new Book()
        {
            Title = "Beginning Silverlight 4: From Novice to Professional",
            ISBN = "978-1430229889"
        };
    
        this.LayoutRoot.DataContext = b;
    }

    When you set up binding definitions for different controls, the controls do not know where they are going to get their data. The DataContext property sets the data context for a control that is participating in data binding. The DataContext property can be set directly on the control. If a given control does not have a DataContext property specified, it will look to its parent for its data context. The nice thing about this model is that if you look above in the XAML for the page, you will see little indication of where the controls are getting their data. This provides an extreme level of code separation, allowing designers to design XAML UIs and developers to work alongside the designers, defining the specifics of how the controls are bound to their data sources.

  5. At this point, you can go ahead and start debugging the application. If all goes well, you will see the four text boxes populated with the data from the Book's instance (see Figure 5-2).

  6. With the application running, change the book title in the first text box to just "Beginning Silverlight," by removing the "From Novice to Professional."

    You might expect that, since the third text box is bound to the same data, it will automatically update to reflect this change. However, a couple of things need to be done to get this type of two-way binding to work.

    One problem is that, currently, the Book class does not support notifying bound clients of changes to its properties. In other words, when a property changes in Book, the class will not notify the TextBox instances that are bound to the class of the change. You could take care of this by creating a change event for each property. This is far from ideal; fortunately, there is an interface that a class can implement that handles this for you. This interface is known as INotifyPropertyChanged. Let's use it.

  7. Modify the Book class definition to inherit from INotifyPropertyChanged. Notice that when you inherit from INotifyPropertyChanged, you need to add using System.ComponentModel. Luckily, Visual Studio will help you with this, as shown in Figure 5-3.

    Visual Studio assists when you need to add the System.ComponentModel namespace.

    Figure 5.3. Visual Studio assists when you need to add the System.ComponentModel namespace.

    Next, you can let Visual Studio do some more work for you. After adding the using System.ComponentModel statement, right-click INotifyPropertyChanged and select Implement Interface

    Visual Studio assists when you need to add the System.ComponentModel namespace.
    Visual Studio also assists in implementing the INotifiyPropertyChanged interface.

    Figure 5.4. Visual Studio also assists in implementing the INotifiyPropertyChanged interface.

    Now Visual Studio has added a new public event to your class:

    public class Book : INotifyPropertyChanged
    {
        public string Title { get; set; }
        public string ISBN { get; set; }
    
        public event PropertyChangedEventHandler PropertyChanged;
    }
  8. Next, you need to create a convenience method that will fire the PropertyChanged event. Call it FirePropertyChanged, as shown in the following code:

    public class Book : INotifyPropertyChanged
    {
        public string Title { get; set; }
        public string ISBN { get; set; }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        void FirePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
    PropertyChanged(this,
                    new PropertyChangedEventArgs(property));
            }
        }
    }
  9. Now you need to extend the simplified properties by adding private members and full get/set definitions to define the get and set operations, as shown in the following code. The get is just like a normal get operation, where you simply return the internal member value. For the set, you first set the internal member value, and then call the FirePropertyChanged method, passing it the name of the property.

    public class Book : INotifyPropertyChanged
    {
        private string _title;
        private string _isbn;
    
        public string Title
        {
            get
            {
                return _title;
            }
            set
            {
                _title = value;
                FirePropertyChanged("Title");
            }
        }
    
        public string ISBN
        {
            get
            {
                return _isbn;
            }
            set
            {
                _isbn = value;
                FirePropertyChanged("ISBN");
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    void FirePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(property));
            }
        }
    }

    With this completed, your class is set up to notify bound clients of changes to the Title and ISBN properties. But you still need to take one more step. By default, when you bind a source to a target, the BindingMode is set to OneWay binding, which means that the source will send the data to the target, but the target will not send data changes back to the source. In order to get the target to update the source, you need to implement two-way (TwoWay) binding.

    Note

    Earlier, I mentioned that there are three options for BindingMode. The third option is OneTime binding. In this mode, the values are sent to the target control property when the object is set to the DataContext. However, the values of the target property are not updated when the source value changes.

  10. To change to two-way binding, add the Mode=TwoWay parameter when defining the {Binding} on a control, as follows:

    <TextBlock Text="Book Title"
        VerticalAlignment="Center"
        Margin="5" />
    <TextBlock Text="ISBN-13"
        VerticalAlignment="Center"
        Margin="5"
        Grid.Row="1" />
    
    <TextBox Text="{Binding Title, Mode=TwoWay}"
        Height="24"
        Margin="5"
        Grid.Column="1" />
    <TextBox Text="{Binding ISBN, Mode=TwoWay}"
        Height="24"
        Margin="5"
        Grid.Column="1" Grid.Row="1" />
    
    <TextBlock Text="Book Title"
        VerticalAlignment="Center"
        Margin="5"
        Grid.Row="2" />
    <TextBlock Text="ISBN-13"
    VerticalAlignment="Center"
        Margin="5"
        Grid.Row="3" />
    
    <TextBox Text="{Binding Title, Mode=TwoWay}"
        Height="24"
        Margin="5"
        Grid.Column="1" Grid.Row="2" />
    <TextBox Text="{Binding ISBN, Mode=TwoWay}"
        Height="24"
        Margin="5"
        Grid.Column="1" Grid.Row="3" />
  11. Rebuild and run your application. Update any of the fields, and leave the focus on the control. You'll see that the two-way binding is triggered, and the corresponding field is also updated, as shown in Figure 5-5.

    Two-way binding in action

    Figure 5.5. Two-way binding in action

Congratulations! You have just created a Silverlight application that allows for two-way data binding. We will now move on to look at data binding lists of data to the two list controls provided in Silverlight: DataGrid and ListBox.

Element to Element Binding

In addition to binding to data, elements can be bound directly to other elements, which can significantly improve the readability and efficiency of your code. The syntax for binding to an element is very similar to binding to a data item, the only difference is that in the binding an ElementName is specified, which is very much like setting the ItemsSource to the Element. As an example, if you wanted to bind the IsEnabled property of a control to a checkbox's IsChecked property. Assuming the checkbox is named EnableButton, the binding syntax would be the following.

IsEnabled="{Binding IsChecked, Mode=OneWay, ElementName=EnableButton}"

Notice that the binding is the same as it would be when binding to a data source, except that we have added the ElementName=EnableButton. Let's try this out in an exercise.

Note

Prior to Silverlight 4, in order for an element to support element binding it had to derive from the FrameworkElement class. This unfortunately left out a number of objects that would ideally support element binding. In Silverlight 4, any object that derives from the DependencyObject now supports element binding. This opens up many new binding opportunities in Silverlight directly through XAML, such as supporting transformations.

Try It Out: Element to Element Binding

To help explain element to element binding in Silverlight, let's build a very simple application. The application will include a button and a checkbox. When the checkbox is checked, the button is enabled, when the checkbox is unchecked, the button is disabled. Let's get started.

  1. Create a new Silverlight application in Visual Studio 2010. Name the project ElementBinding, and allow Visual Studio to create a Web application project to host your application.

  2. Edit the MainPage.xaml file to add a StackPanel to the root Grid. Place a ToggleButton and CheckBox named EnableButton within that StackPanel so the ToggleButton appears above the CheckBox. Add a margin of 20 on the StackPanel and 5 on the ToggleButton and CheckBox to add some spacing between the controls. The code for the page follows:

    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel Margin="20">
    
            <ToggleButton Margin="5" Content="Click to Toggle" />
    
            <CheckBox
                x:Name="EnableButton" IsChecked="true"
                Margin="5" Content="Enable Button" />
    
        </StackPanel>
    </Grid>
  3. Next, we need to bind the ToggleButton's IsEnabled property to the CheckBox's IsChecked property. We will do this with one way binding as described earlier in this chapter, and we will set the ElementName to EnableButton, which is the name we gave our CheckBox. The updated source code should now look like the following:

    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel Margin="20">
    
            <ToggleButton
                Margin="5" Content="Click to Toggle"
                IsEnabled="{Binding IsChecked, Mode=OneWay,
                ElementName=EnableButton}" />
    
            <CheckBox
                x:Name="EnableButton" IsChecked="true"
                Margin="5" Content="Enable Button" />
    
        </StackPanel>
    </Grid>
  4. That is it! No coding is required for this demo. Run the sample and will see that the ToggleButton is enabled, as shown in Figure 5-6.

    Element binding example with ToggleButton enabled

    Figure 5.6. Element binding example with ToggleButton enabled

  5. Now uncheck the Enable Button checkbox and you will see that the ToggleButton is no longer enabled, as shown in Figure 5-7.

    Element binding example with ToggleButton disabled

    Figure 5.7. Element binding example with ToggleButton disabled

The DataGrid Control

The data grid type of control has been around for ages and has been the primary choice for developers who need to display large amounts of data. The DataGrid control provided by Silverlight is not just a standard data grid, however. It contains a great deal of rich user functionality that, in the past, has been present only in third-party data grid components. For example, the Silverlight DataGrid handles resizing and reordering of grid columns.

Figure 5-8 shows an example of a very simple DataGrid, where the columns were automatically generated. Notice how the column titled Male is a check box. The DataGrid control has built-in intelligence to automatically show Boolean data types as check box cells.

A simple DataGrid example

Figure 5.8. A simple DataGrid example

Try It Out: Building a Simple DataGrid

Let's run through a simple DataGrid example.

  1. Create a new Silverlight application in Visual Studio 2010. Name the project SimpleDataGrid, and have Visual Studio create a hosting web application for you.

  2. Add the DataGrid to your application. To do this, simply add the DataGrid to the root Grid in your XAML, and set the Margin property to 10 to get some spacing around the grid. In addition, give the DataGrid the name grid. Note that, by default, the Grid's AutoGenerateColumns property is set to true. If you were going to define the columns manually, you would want to set this property to false. However, since you want the grid to create the columns automatically, you can simply omit the property. The DataGrid definition follows:

    <Grid x:Name="LayoutRoot" Background="White">
        <sdk:DataGrid Name="grid" Margin="10" />
    </Grid>

    Note

    Why use <sdk:DataGrid>? As discussed in Chapter 4, the DataGrid is contained in an assembly called System.Windows.Controls.Data, which is not added to Silverlight applications by default. This way, if your application does not need any of the extended controls, the file size of your Silverlight application can be smaller. However, in order to add a DataGrid to your application, you need to reference the new assembly and add an xmlns reference to the assembly in the UserControl definition.

    xmlns:sdk=http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk

    As you might expect by now, Visual Studio can do all the work for you. To use this functionality in Visual Studio, drag the DataGrid control from the Toolbox to add it to your application (or simply double-click on the DataGrid in the Toolbox). Visual Studio will add a new xmlns reference in the UserControl at the top of the .xaml page called data.

  3. Next, build the class that will be bound to the DataGrid. Call the class GridData for simplicity, and give it three properties: Name (string), Age (int), and Male (Boolean). Also for simplicity, create a static method that will return an ObservableCollection (requires adding a using clause for System.Collections.ObjectModel) containing some sample data that will be bound to the grid. In addition, define the class directly in the MainPage.xaml.cs file. This is not really a good idea in the real world, but for the sake of an example, it will work just fine. Ideally, you will want to define your classes in separate files or even in completely separate projects and assemblies. The code for the GridData class follows:

    namespace SimpleDataGrid
    {
        public partial class MainPage : UserControl
        {
            public MainPage()
            {
                InitializeComponent();
            }
        }
    
        public class GridData
        {
            public string Name { get; set; }
            public int Age { get; set; }
            public bool Male { get; set; }
    
            public static ObservableCollection<GridData> GetData()
            {
                ObservableCollection<GridData> data =
                    new ObservableCollection<GridData>();
    data.Add(new GridData()
                {
                    Name = "John Doe",
                    Age = 30,
                    Male = true
                });
    
                data.Add(new GridData()
                {
                    Name = "Jane Doe",
                    Age = 32,
                    Male = false
                });
    
                data.Add(new GridData()
                {
                    Name = "Jason Smith",
                    Age = 54,
                    Male = true
                });
    
                data.Add(new GridData()
                {
                    Name = "Kayli Jayne",
                    Age = 25,
                    Male = false
                });
    
                return data;
            }
        }
    }

    Note

    When you are binding a collection of data to a DataGrid or ListBox, you may be tempted to use the List generic class. The problem with using the List class is that it does not have built-in change notifications for the collection. In order to bind a DataGrid and ListBox to dynamic data that will be updated, you should use the ObservableCollection generic class. The ObservableCollection class represents a collection of dynamic data that provides built-in notification when items in the collection are added, removed, or refreshed.

  4. Now that you have the XAML and the class defined, you can wire them up. To do this, first create an event handler for the Loaded event of the page, as follows:

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Page_Loaded);
        }
    
        void Page_Loaded(object sender, RoutedEventArgs e)
        {
    
        }
    }
  5. When the page is loaded, you want to call GetData() from the GridData class and bind that to the DataGrid's ItemsSource property, as follows:

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Page_Loaded);
        }
    
        void Page_Loaded(object sender, RoutedEventArgs e)
        {
            this.grid.ItemsSource = GridData.GetData();
        }
    }
  6. Build and run the application. If all is well, you should see the DataGrid displayed (see Figure 5-8).

Let's take a few moments and play around with this DataGrid to explore some of its features. First of all, if you click any of the column headers, you will notice that sorting is automatically available, as shown in Figure 5-9.

Next, if you place your cursor at the edge of one of the columns, you can use the mouse to click and drag the column's edge to resize the column, as shown in Figure 5-10. Again, this functionality is provided for free with the DataGrid's rich client-side functionality.

Finally, if you click and hold the mouse on one of the column headers, then drag it left or right to another column header's edge, you will see the column header move and a dark gray border appear between the columns. For instance, click and drag the Male column to the left of the Age column, as shown in Figure 5-11. When a dark-gray border shows up between the two columns, release the mouse, and you will see that the Male column now appears to the left of the Age column in the DataGrid.

Sorting in the DataGrid

Figure 5.9. Sorting in the DataGrid

Resizing columns in a DataGrid

Figure 5.10. Resizing columns in a DataGrid

Column reordering in action

Figure 5.11. Column reordering in action

You'll agree that this is pretty nice out-of-the-box functionality for simply defining a DataGrid with this code:

<data:DataGrid x:Name="grid" Margin="10" />

Now that you have implemented a simple DataGrid example, let's explore some of the additional options available.

The Columns Collection

In the previous example, you allowed the DataGrid to automatically generate columns based on the data to which it was bound. This is not a new concept—it has been around in data grid components since the initial release of ASP.NET. But what if you want to have some additional control over the columns that are created in your DataGrid? What if you want to add a column that contains some more complex information, such as an image? You can do this by first setting the AutoGenerateColumns property on the grid to false. Then you need to generate the columns manually.

Columns are defined in a DataGrid using the Columns collection. The following is an example of setting the Columns collection in XAML. Notice that it sets the AutogenerateColumns property to False. If you neglect to do this, you will get all of the autogenerated columns in addition to the columns you define within the Columns collection.

<my:DataGrid x:Name="grid" Margin="10" AutoGenerateColumns="False">
    <my:DataGrid.Columns>

    </my:DataGrid.Columns>
</my:DataGrid>

You can place three types of columns within a Columns collection: a text column (DataGridTextColumn), a check box column (DataGridCheckBoxColumn), and a template column (DataGridTemplateColumn). All of the column types inherit from type DataGridColumn. A number of notable properties apply to all three column types, as shown in Table 5-1.

Table 5.1. DataGridColumn Properties

Property

Description

CanUserReorder

Turns on and off the ability for the user to drag columns to reorder them

CanUserResize

Turns on or off the ability for the user to resize the column's width with the mouse

DisplayIndex

Determines the order in which the column appears in the DataGrid

Header

Defines the content of the column's header

IsReadOnly

Determines if the column can be edited by the user

MaxWidth

Sets the maximum column width in pixels

MinWidth

Sets the minimum column width in pixels

Visibility

Determines whether or not the column will be visible to the user

Width

Sets the width of the column, or can be set to automatic sizing mode

DataGridTextColumn

The DataGridTextColumn defines a column in your grid for plain text. This is the equivalent to BoundColumn in the ASP.NET DataGrid. The primary properties that can be set for a DataGridTextColumn are the Header, which defines the text that will be displayed in the columns header, and the DisplayMemberBinding property, which defines the property in the data source bound to the column.

The following example defines a text column with the header Name and is bound to the data source's Name property.

<my:DataGrid x:Name="grid" Margin="10" AutoGenerateColumns="False">
    <my:DataGrid.Columns>
        <my:DataGridTextColumn
                Header="Name"
                DisplayMemberBinding="{Binding Name}" />
            </my:DataGrid.Columns>
</my:DataGrid>

DataGridCheckBoxColumn

As you would expect, the DataGridCheckBoxColumn contains a check box. If you have data that you want to display as a check box in your grid, this is the control to use. Here is an example of the DataGridCheckBoxColumn that contains the header Male? and is bound to the data source's Male property:

<my:DataGrid x:Name="grid" Margin="10" AutoGenerateColumns="False">
    <my:DataGrid.Columns>
        <my:DataGridCheckBoxColumn
                Header="Male?"
                DisplayMemberBinding="{Binding Male}" />
            </my:DataGrid.Columns>
</my:DataGrid>

DataGridTemplateColumn

If you want data in your grid column that is not plain text and is not a check box, the DataGridTemplateColumn provides a way for you to define the content for your column. The DataGridTemplateColumn contains a CellTemplate and CellEditingTemplate, which determine what content is displayed, depending on whether the grid is in normal view mode or in editing mode.

Note that while you get features such as automatic sorting in the other types of DataGrid columns, that is not true of the DataGridTemplateColumn. These columns will need to have additional logic in place to allow for sorting.

Let's consider an example that has two fields: FirstName and LastName. Suppose that when you are in normal view mode, you want the data to be displayed side by side in TextBlock controls. However, when the user is editing the column, you want to display two TextBox controls that allow the user to edit the FirstName and LastName columns independently.

<my:DataGridTemplateColumn Header="Name">
    <my:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
           <StackPanel Orientation="Horizontal">
               <TextBlock Padding="5,0,5,0"
                   Text="{Binding FirstName}"/>
               <TextBlock Text="{Binding LastName}"/>
           </StackPanel>
        </DataTemplate>
    </my:DataGridTemplateColumn.CellTemplate>
    <my:DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
<TextBox Padding="5,0,5,0"
                    Text="{Binding FirstName}"/>
                <TextBox Text="{Binding LastName}"/>
            </StackPanel>
        </DataTemplate>
    </my:DataGridTemplateColumn.CellEditingTemplate>
</my:DataGridTemplateColumn>

Now that we have covered the basics of manually defining the grids in a DataGrid, let's try it out.

Try It Out: Building a DataGrid with Custom Columns

I thought it would be fun to build a DataGrid that contains a list of starting hands in poker. If you have ever watched poker on TV, you most likely heard the players refer to things like "pocket rockets" and "cowboys." These are simply nicknames they have given to starting hands. The DataGrid you are going to build in this example will look like Figure 5-12.

DataGrid with custom columns

Figure 5.12. DataGrid with custom columns

  1. Create a new Silverlight application called DataGridCustomColumns. Allow Visual Studio to create a Web Site project to host the application.

  2. After the project is loaded, right-click the DataGridCustomColumns project and select Add New Item. In the Add New Item dialog box, select Code File for the template, and name the class StartingHands.cs, as shown in Figure 5-13. Click the Add button to add the class to the project.

    Adding a new class to the Silverlight project

    Figure 5.13. Adding a new class to the Silverlight project

  3. Now define the StartingHands class. The class will contain four properties: Nickname (string), Notes (string), Card1 (string), and Card2 (string). Also create a static method in the class called GetHands(), which returns an ObservableCollection of StartingHands instances. The code follows:

    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Collections.ObjectModel;
    
    namespace DataGridCustomColumns
    {
        public class StartingHands
        {
            public string Nickname { get; set; }
            public string Notes { get; set; }
            public string Card1 { get; set; }
            public string Card2 { get; set; }
    
            public static ObservableCollection<StartingHands> GetHands()
            {
                ObservableCollection<StartingHands> hands =
                new ObservableCollection<StartingHands>();
    
                hands.Add(
                    new StartingHands()
                    {
                        Nickname = "Big Slick",
                        Notes = "Also referred to as Anna Kournikova.",
                        Card1 = "As",
                        Card2 = "Ks"
                    });
    
                hands.Add(
                new StartingHands()
                {
                    Nickname = "Pocket Rockets",
                    Notes = "Also referred to as Bullets.",
                    Card1 = "As",
                    Card2 = "Ad"
                });
    
                hands.Add(
                    new StartingHands()
                    {
                        Nickname = "Blackjack",
                        Notes = "The casino game blackjack.",
                        Card1 = "As",
                        Card2 = "Js"
                    });
    
                hands.Add(
                    new StartingHands()
                    {
                        Nickname = "Cowboys",
                        Notes = "Also referred to as King Kong",
                        Card1 = "Ks",
                        Card2 = "Kd"
                    });
    hands.Add(
                    new StartingHands()
                    {
                        Nickname = "Doyle Brunson",
                        Notes = "Named after poker great Doyle Brunson",
                        Card1 = "Ts",
                        Card2 = "2s"
                    });
    
    
                return hands;
            }
        }
    }
  4. Now that the class is built, in the MainPage.xaml file, change the width of the UserControl to be 500 and add a DataGrid named grdData to the root Grid by double-clicking the DataGrid control in the Toolbox. Add a 15-pixel margin around the DataGrid for some spacing, and set the AutoGenerateColumns property to False. The code follows:

    <Grid x:Name="LayoutRoot" Background="White">
        <sdk:DataGrid AutoGenerateColumns="False" Name="grdData" />
    </Grid>
  5. Next, define the columns in the DataGrid. To do this, add the DataGrid.Columns collection, as follows:

    <Grid x:Name="LayoutRoot" Background="White">
        <sdk:DataGrid AutoGenerateColumns="False" Name="grdData">
            <sdk:DataGrid.Columns>
    
            </sdk:DataGrid.Columns>
        </sdk:DataGrid>
    </Grid>

    Referring back to Figure 5-12, the first column in the Grid contains the two cards in the hand. To build this, you use a DataGridTemplateColumn. Within the DataGridTemplateColumn, add a CellTemplate containing a Grid with two columns, each containing a Border, Rectangle, and TextBlock, which will overlap each other. Bind the two TextBlock controls to the Card1 and Card2 properties from the data source. Enter the following code:

    <sdk:DataGrid AutoGenerateColumns="False" Name="grdData">
        <sdk:DataGrid.Columns>
            <sdk:DataGridTemplateColumn Header="Hand">
                <sdk:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
    <ColumnDefinition />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Border
                                Margin="2" CornerRadius="4"
                                BorderBrush="Black" BorderThickness="1" />
                            <Rectangle
                                Margin="4" Fill="White" Grid.Column="0" />
                            <Border
                                Margin="2" CornerRadius="4" BorderBrush="Black"
                                BorderThickness="1" Grid.Column="1" />
                            <Rectangl
                                Margin="4" Fill="White" Grid.Column="1" />
                            <TextBlock
                                Text="{Binding Card1}" HorizontalAlignment="Center"
                                VerticalAlignment="Center" Grid.Column="0" />
                            <TextBlock
                                Text="{Binding Card2}" HorizontalAlignment="Center"
                                VerticalAlignment="Center" Grid.Column="1" />
                        </Grid>
                    </DataTemplate>
                </sdk:DataGridTemplateColumn.CellTemplate>
            </sdk:DataGridTemplateColumn>
        </sdk:DataGrid.Columns>
    </sdk:DataGrid>

    Again, referring back to Figure 5-12, the next two columns contain the nickname of the starting hand and notes about the starting hand. To implement this, use two DataGridTextColumn columns. Set the Headers of the columns to Nickname and Notes accordingly.

    <sdk:DataGrid AutoGenerateColumns="False" Name="grdData">
        <sdk:DataGrid.Columns>
            <sdk:DataGridTemplateColumn Header="Hand">
                <sdk:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Border
                                Margin="2" CornerRadius="4"
                                BorderBrush="Black" BorderThickness="1" />
                            <Rectangle
                                Margin="4" Fill="White" Grid.Column="0" />
                            <Border
                                Margin="2" CornerRadius="4" BorderBrush="Black"
                                BorderThickness="1" Grid.Column="1" />
                            <Rectangle
                                Margin="4" Fill="White" Grid.Column="1" />
    <TextBlock
                                Text="{Binding Card1}" HorizontalAlignment="Center"
                                VerticalAlignment="Center" Grid.Column="0" />
                            <TextBlock
                                Text="{Binding Card2}" HorizontalAlignment="Center"
                                VerticalAlignment="Center" Grid.Column="1" />
                        </Grid>
                    </DataTemplate>
                </sdk:DataGridTemplateColumn.CellTemplate>
            </sdk:DataGridTemplateColumn>
            <sdk:DataGridTextColumn
                Header="Nickname"
                Binding="{Binding Nickname}" />
            <sdk:DataGridTextColumn
                Header="Notes"
                Binding="{Binding Notes}" />
        </sdk:DataGrid.Columns>
    </sdk:DataGrid>
  6. Finally, wire up the controls to the data source. To do this, navigate to the MainPage.xaml.cs file and add an event handler to the Page Loaded event. Within that Loaded event, simply set the DataGrid's ItemsSource property equal to the return value of the StartingHands.GetHands() static method. Here's the code:

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Page_Loaded);
        }
    
        void Page_Loaded(object sender, RoutedEventArgs e)
        {
            this.grdData.ItemsSource = StartingHands.GetHands();
        }
    }
  7. Compile and run your application. If all goes well, your application should appear, as shown earlier in Figure 5-12.

This completes our DataGrid with custom columns example. Naturally, in a real-world application, you would be getting the data for these hands from an external data source, such as a web service or an XML file. We will be looking at that in Chapter 6. Now, let's take a look at the ListBox control.

The ListBox Control

In the past, the list box type of control has been considered one of the common controls in programming—no more special than a drop-down list. However, in Silverlight, this has all changed. The ListBox is perhaps one of the most flexible controls used to display lists of data. In fact, referring back to ASP.NET controls, the Silverlight ListBox is more a cousin to the DataList control than the ASP.NET ListBox control. Let's take a peek at this powerful control.

Default and Custom ListBox Items

If we wire up the ListBox to our Person data from our earlier DataGrid example, you will see that, by default, the ListBox really is just a standard ListBox.

<ListBox Margin="10" x:Name="list" DisplayMemberPath="Name" />

One additional property you may have noticed in this ListBox definition is DisplayMemberPath. If you are defining a simple text-based ListBox, the ListBox needs to know which data member to display. Since the Person class contains three properties (Name, Age, and Male), we need to tell it that we want the Name to be displayed. Figure 5-14 shows the results.

A simple default ListBox

Figure 5.14. A simple default ListBox

However, the ListBox control can contain much more than plain text. In fact, if you define a custom ItemTemplate for the ListBox, you can present the items in a more interesting way. Here's an example using the same Person data:

<ListBox Margin="10" x:Name="list">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Margin="5" Orientation="Vertical">
                <TextBlock
                    FontSize="17"
                    FontWeight="Bold"
                    Text="{Binding Name}" />
                <StackPanel Margin="5,0,0,0" Orientation="Horizontal">
                    <TextBlock Text="Age: " />
                    <TextBlock Text="{Binding Age}" />
                    <TextBlock Text=", Male: " />
                    <TextBlock Text="{Binding Male}" />
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Figure 5-15 shows how this custom ListBox appears in a browser.

A customized ListBox example

Figure 5.15. A customized ListBox example

Try It Out: Building a ListBox with Custom Content

Let's take the same data that displayed poker starting hands from the previous exercise and see what type of cool ListBox you can build with it. Figure 5-16 shows the custom ListBox you'll create in this exercise.

The customized ListBox application

Figure 5.16. The customized ListBox application

  1. Start out by creating a new Silverlight application called ListBoxCustom and allow Visual Studio to create a hosting web site.

  2. You will use the same class that you built in the earlier DataGrid exercise. Right-click the Silverlight project, choose Add Existing Item, and browse to StartingHands.cs to add that class to the project.

  3. When you add the existing StartingHands.cs class, it is in a different namespace than your current project. You can reference that namespace by adding a using statement at the top of your Silverlight application, or you can just change the namespace, as follows:

    namespace ListBoxCustom
    {
        public class StartingHands
        {
            public string Nickname { get; set; }
            public string Notes { get; set; }
    public string Card1 { get; set; }
            public string Card2 { get; set; }
    
            ...
        }
    }
  4. Next, you need to define the ListBox's ItemTemplate. The ItemTemplate will contain a horizontal-oriented StackPanel including the grid to display the two cards. It will also include a nested vertical-oriented StackPanel that will contain two TextBlock controls to display the Nickname and Notes data. Here is the code:

    <Grid x:Name="LayoutRoot" Background="White">
        <ListBox Margin="10" x:Name="list">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Margin="5" Orientation="Horizontal">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
    
                            <Border
                                Margin="2" CornerRadius="4"
                                BorderBrush="Black" BorderThickness="1" />
                            <Rectangle Margin="4" Fill="White"
                                Grid.Column="0" Width="20" />
                            <Border
                                Margin="2" CornerRadius="4" BorderBrush="Black"
                                BorderThickness="1" Grid.Column="1" />
                            <Rectangle Margin="4" Fill="White"
                                Grid.Column="1" Width="20" />
                            <TextBlock
                                Text="{Binding Card1}" HorizontalAlignment="Center"
                                VerticalAlignment="Center" Grid.Column="0" />
                            <TextBlock
                                Text="{Binding Card2}" HorizontalAlignment="Center"
                                VerticalAlignment="Center" Grid.Column="1" />
                        </Grid>
    
                        <StackPanel Orientation="Vertical">
                            <TextBlock
                                Text="{Binding Nickname}"
                                FontSize="16"
                                FontWeight="Bold" />
                            <TextBlock
                                Text="{Binding Notes}" />
                        </StackPanel>
    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
  5. The only thing left to do is to wire up the ListBox to the data source. To do this, navigate to the page.xaml.cs code behind, and add an event handler for the Page Loaded event. Then, within that Loaded event handler, add the following code to set the ListBox's ItemsSource to the return value from the StartingHands.GetHands() method, as you did earlier in the DataGrid example.

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Page_Loaded);
        }
    
        void Page_Loaded(object sender, RoutedEventArgs e)
        {
            list.ItemsSource = StartingHands.GetHands();
        }
    }
  6. Run the application. If all goes well, you will see the ListBox shown in Figure 5-16.

As you can see, the ListBox control's flexibility lets developers display lists of data in some very cool ways.

New Data Binding Features in Silverlight 4

Earlier you read that the base type required for element binding was changed to the DependencyObject class. In addition to this, there are a number of new features regarding data binding in Silverlight 4, three of which I will discuss in this section. All three come in the form of new binding extensions. First I will discuss the string formatting, then I will discuss the extensions TargetNullValue and FallBackValue.

Data Binding and String Formatting

In prior versions of Silverlight, in order to perform string formatting while data binding you would need to write a converter that would format the data during binding. In Silverlight 4 you are now able to format the data directly in XAML. This greatly simplifies the process of creating bindings that require some formatting of the data. Adding string formatting is as simple as adding a StringFormat extension in the XAML markup of the data binding. The StringFormat extension supports the same formatting options as the String's Format method.

Consider the following XAML. There are four text boxes displayed, all bound to the same property in code behind. The difference, however, is that each TextBox change displays the data differently based on the binding's StringFormat extension. The first TextBox shows the raw data, the second formats the data to three decimal places, the third shows the value in scientific notation, and the fourth shows the data as currency. The result is shown in Figure 5-17.

<Grid x:Name="LayoutRoot" Background="White">
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <TextBox
        Margin="5"
        Grid.Row="0"
        Text="{Binding DecimalValue}" />
    <TextBox
        Margin="5"
        Grid.Row="1"
        Text="{Binding DecimalValue, StringFormat='##.###'}" />
    <TextBox
        Margin="5"
        Grid.Row="2"
        Text="{Binding DateValue, StringFormat='MMM dd, yyyy'}" />
    <TextBox
        Margin="5"
        Grid.Row="3"
        Text="{Binding DecimalValue, StringFormat='c'}" />
</Grid>
The customized ListBox application

Figure 5.17. The customized ListBox application

As you can see from this example, you can easily change the format of data directly through the XAML.

TargetNullValue and FallBackValue Extensions

Two other extensions have been added to the base binding class in Silverlight 4: the TargetNullValue and FallBackValue extensions. These extensions allow you to specify data that will be displayed in the case when the data being bound to the control is not as expected. TargetNullValue provides a value for the binding that should be used if the data value being bound is null. An example of using this extension is shown here:

<TextBox Text="{Binding ValueName, TargetNullValue='Value is Null'}" />

FallBackValue provides a value to be used if the bound data is missing or if there was a mismatch that occurred. An example of using the FallBackValue extension is shown here:

<TextBox Text="{Binding ValueName, FallbackValue='Value Not Found'}" />

Summary

In this chapter, you looked at how to bind lists of data to Silverlight controls. Then you focused on two controls typically bound to data: the DataGrid control and the ListBox control. You saw how these controls are flexible and can show data in unique ways. However, in all of these examples, the classes contained static data. In real-world examples, the data that you will normally list in a DataGrid or ListBox will be coming from some external data source, such as an XML file or a web service. In the next chapter, you will look at how to get data from these external data sources and how to use that data to bind to your Silverlight applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset