[This topic is pre-release documentation and is subject to change in future releases of Microsoft Silverlight.]

Working with Data Collections in Silverlight

Introduction

Most Silverlight-based applications will be receiving and displaying data. In many cases, this will be a collection of data, such as stock quotes, a list of headlines, or a set of images. In addition to displaying a list of data, you often want to allow the user to select an item in the list and then display details about that item. This topic will show you how to take a set of data, display it, allow item selection, and display details based on that selection

Run View
Language:

Prerequisites (available from the Silverlight download site):

  • Silverlight version 2 Beta 1. 

  • Microsoft Visual Studio 2008.

  • Silverlight Tools Beta 1 for Visual Studio 2008. 

Create a Data Class

The first thing to do when working with data, is create the class that will define each data item. This example is going to create a list of books. So the individual data item is a book. Each book has an ISBN number, a title, a publish date, and a price. Your class should look something like this.

//Additional namespaces needed for data binding
using System.Collections.ObjectModel;
using System.Windows.Data;
using System.ComponentModel;
…
namespace QuickStart_Bookstore
{
...
//Define data class
    public class Book
    {
        public Book()
        {
        }

        public Book(string isbn, string title, 
            DateTime publishdate, double price)
        {
            this.ISBN = isbn;
            this.Title = title;
            this.PublishDate = publishdate;
            this.Price = price;
        }

        //Define the public properties
        public string ISBN { get; set; }
        public string Title { get; set; }
        public DateTime PublishDate { get; set; }
        public double Price { get; set; }

    }
...
}

In this case, you do not need to implement INotifyPropertyChanged. If users were allowed to edit the details of each book, then you would need to implement INotifyPropertyChanged. In this case, only the collection is updated, not the individual items. For more information, see Data Binding.

Create a Collection

Once you've defined your individual data items, you need to create a collection of those items. In this example there is a single collection object. But you could also create a class that inherits from any of the collection types if you wanted multiple instances. Note that if you create your own collection class, you'll need to implement INotifyCollectionChanged unless you derive from ObservableCollection which already implements it.

namespace QuickStart_Bookstore
{
    public partial class Page : UserControl
    {
...
        //Create a collection to store data items
        private ObservableCollection<Book> _AllBooks;
        public ObservableCollection<Book> AllBooks
        {
            get
            {
                if (_AllBooks == null)
                {
                    _AllBooks = new ObservableCollection<Book>();
                    _AllBooks.Add(new Book("3390092284", "All About Dogs",
                        new DateTime(2004, 3, 4), 12.99));

                }
                return _AllBooks;
            }
        }
...
    }
}

Create a Data Template

Data collections are generally displayed as a sequence of items where each item has the same visual display. A DataTemplate is used to achieve this. The DataTemplate tells Silverlight how to display each item in the list. Many of the controls allow you to set a template. This example uses a ListBox control. Once you've defined the DataTemplate, tell the ListBox what that template is by setting the ItemTemplate property. In this case, we've done this using property element syntax. To learn more about XAML syntax, go to the XAML Overview (Silverlight 2).

<!--This is the ListBox for the master list-->
<StackPanel>
    <ListBox x:Name="MyBooks" Margin="5" ItemsSource="{Binding Mode=OneWay}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal" >
                    <TextBlock Text="{Binding ISBN}" Margin="0,0,50,0" />
                <TextBlock Text="{Binding Title}" />
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate> 
    </ListBox>
...
</StackPanel>

This XAML will display the list of books as ISBN numbers and book titles. The StackPanel has horizontal orientation which will put the two TextBlocks side by side. You can learn more about layout by reading the Object Positioning and Layout (Silverlight 2) overview.

You also need to set the ItemsSource for the ListBox. You can do this with data binding. In XAML, set the ItemsSource to be a binding. The Binding Markup Extension (Silverlight 2) overview explains the details of the binding syntax.

Notice that the Text properties on the TextBlock elements inside the DataTemplate are also bound properties. In addition to declaring the property as bound, each TextBlock is given the path to the property on the data object it will be bound to—in this case, IBSN and Title.

Set the Data Context

Now that you have the binding targets, you need to set the binding source. Do this by setting the DataContext for the ListBox to be the collection of books.

namespace QuickStart_Bookstore
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
            //You can add items to your collection
            AllBooks.Add(new Book("4458907683", "Training Your Dog",
                new DateTime(2000, 2, 8), 44.25));
            //Set the data context for the list of books
            MyBooks.DataContext = AllBooks;
...
        }

    }
...
}

All the children of the ListBox will have the same DataContext, unless a different DataContext is set further down in the tree or is overridden using the Source property.

Build and run your sample. Now you should have a sample that will display a list of items in a collection. The next section shows how to select an item in the list and display its details.

Adding the Details View

To display the details about an item when it is selected, you need to create the UI for that view. First add a Rectangle to serve as a dividing line under the ListBox. Below the line, add a StackPanel that contains the UI elements needed to display the details. This example displays the ISBN number, the title, the date published and the price.

<StackPanel>
    <!--Visual division between the list and the details-->
    <Rectangle HorizontalAlignment="Left" Width="400" Height="2" 
        Fill="Red" Margin="0,10,0,10"/>
    
    <!--The UI for the details view-->
    <StackPanel x:Name="BookDetails">
        <TextBlock Text="{Binding ISBN, Mode=OneWay}" />
        <TextBlock Text="{Binding Title, Mode=OneWay}" />

        <TextBlock Text="{Binding PublishDate, Mode=OneWay}"/>
        <TextBlock Text="{Binding Price, Mode=OneWay}" />
    </StackPanel>

</StackPanel>

Notice that each Text property is bound to a data source. For the details view, you want to bind to an individual item in the list, not the whole collection. Initially, set this to be the first item in the list.

namespace QuickStart_Bookstore
{
    public partial class Page : UserControl
    {
        public Page()
        {
...
            //Set the initial data context for the details view
            BookDetails.DataContext = AllBooks[0];
        }
...
    }
...
}

Changing the View

To change the details view whenever the user selects a different item, catch the SelectionChanged event and reset the DataContext to the item selected. First declare the handler for the SelectionChanged event in XAML.

<ListBox x:Name="MyBooks" Margin="5" ItemsSource="{Binding Mode=OneWay}"
    SelectionChanged="MyBooks_SelectionChanged">
...
</ListBox>

Then in the code, determine which item was selected and set DataContext of the BookDetailsStackPanel to be that item.

//SelectionChanged event handler to update the details view when 
//the user changes the selection
    private void MyBooks_SelectionChanged(object sender, 
        SelectionChangedEventArgs e)
    {
        ListBox lb = (ListBox)sender;
        BookDetails.DataContext = lb.SelectedItem;
    }

Now you should have a sample where you can display your collection, make selections, and view details based on that selection.

Create the Converters

If you want to format the strings that you are displaying in the details view, you can use a converter. For example, both the published date and the price could be displayed a bit differently.

For the date, suppose you want to display it as MM/YYYY. Then you need to use a converter to change the format before the binding engine passes the data to the target UI. The converter is a class that derives from IValueConverter.

//Create a converter to display the date in a different format
    public class DateToString : IValueConverter
    {

        #region IValueConverter Members

        public object Convert(object value, Type targetType, 
            object parameter, System.Globalization.CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            string s = date.Month.ToString()+"/"+date.Year.ToString();
            return s;
            
        }

        public object ConvertBack(object value, Type targetType, 
            object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

For the price converter, you can add a currency symbol based on the culture info.

//Create a converter to display the price as currency
    public class ToCurrency : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, 
             object parameter, System.Globalization.CultureInfo culture)
        {
            double amount = (double)value;
            string s = culture.NumberFormat.CurrencySymbol + 
                amount.ToString();
            return s;
        }

        public object ConvertBack(object value, Type targetType, 
            object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

You only need to implement the Convert(Object, Type, Object, CultureInfo) method since this is OneWay binding. If it were TwoWay binding, then you would need to implement ConvertBack(Object, Type, Object, CultureInfo) as well.

You also need to create instances of the converter classes and tell the bindings to use the converters in XAML.

You can create an instance of a class in XAML by doing the following:

<UserControl.Resources>
    <local:DateToString x:Key="DateConverter"/>
    <local:ToCurrency x:Key="PriceConverter"/>
</UserControl.Resources>

You define the local namespace on the UserControl.

<UserControl x:Class="QuickStart_Bookstore.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:QuickStart_Bookstore;assembly=QuickStart_Bookstore"
    >
...
</UserControl>

To tell the binding to use the converter, set the Converter for each binding in XAML.

<TextBlock Text="{Binding PublishDate, Mode=OneWay,
    Converter={StaticResource DateConverter}}"/>
<TextBlock Text="{Binding Price, Mode=OneWay,
    Converter={StaticResource PriceConverter}}" />