How to Work with Databases, Business Objects and DataTemplates in Windows Phone 7

In general, you’ll be filling an ItemsControl or ListBox with those vague but ubiquitous entities known as business objects. I’m going to spend the remainder of the chapter focusing on programs that use a database of high school students.


This chapter is taken from book "Programming Windows Phone 7" by Charles Petzold published by Microsoft press. http://www.charlespetzold.com/phone/index.html

Databases and Business Objects: In general, you'll be filling an ItemsControl or ListBox with those vague but ubiquitous entities known as business objects. I'm going to spend the remainder of the chapter focusing on programs that use a database of high school students. In these examples, the database is downloaded from a directory on my web site, but because I want to focus solely on the presentation of this data in this chapter, changes to properties of the Student class will be simulated locally.

Heare is a library project named ElPasoHighSchool that contains several classes to read the XML file from my Web site and deserialize it into .NET objects and also a Student class, it implements INotifyPropertyChanged and has several properties pertaining to the student, including name, sex, a filename referencing the photograph, and a grade point average:

namespace ElPasoHighSchool
{
    public class Student : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        string fullName;
        string firstName;
        string middleName;
        string lastName;
        string sex;
        string photoFilename;
        decimal gradePointAverage; 
        public string FullName
        {
            set
            {
                if (fullName != value)
                {
                    fullName =value;
                    OnPropertyChanged("FullName");
                }
            }
            get
            {
                return fullName;
            }
        }
        .....
        public string Sex
        {
            set
            {
                if (sex != value)
                {
                    sex = value;
                    OnPropertyChanged("Sex");
                }
            }
            get
            {
                return sex;
            }
        } 
        public string PhotoFilename
        {
            set
            {
                if (photoFilename !=value)
                {
                    photoFilename =value;
                    OnPropertyChanged("PhotoFilename");
                }
            }
            get
            {
                return photoFilename;
            }
        }
        public decimal GradePointAverage
        {
            set
            {
                if (gradePointAverage !=value)
                {
                    gradePointAverage =value;
                    OnPropertyChanged("GradePointAverage");
                }
            }
            get
            {
                return gradePointAverage;
            }
        } 
        protected virtual void OnPropertyChanged(string propChanged)
        {
            if (PropertyChanged !=null)
                PropertyChanged(this,new PropertyChangedEventArgs(propChanged));
        }
    }
}

There will be one instance of the Student class for each student. Changes to any of these properties cause a PropertyChanged event to fire. Thus, this class is suitable as a source for data bindings.

The StudentBody class also implements INotifyPropertyChanged:

namespace ElPasoHighSchool
{
    public class StudentBody : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        string school;
        ObservableCollection<Student> students = new ObservableCollection<Student>();
        .....
        protected virtual void OnPropertyChanged(string propChanged)
        {
            if (PropertyChanged !=null)
                PropertyChanged(this,new PropertyChangedEventArgs(propChanged));
        }
    }
}

This class contains a property indicating the name of the school and an ObservableCollection of type Student to store all the Student objects. ObservableCollection is a very popular collection class in Silverlight because it implements the INotifyCollectionChanged interface, which means that it fires a CollectionChanged event whenever an item is added to or removed from the collection.

Before continuing, let's take a look at an excerpt of the student.xml file, which available in source code please download from there:

As you can see, the element tags correspond to properties in the Student and StudentBody classes. I created this file using XML serialization with the XmlSerializer class, and XML deserialization can convert it back into Student and StudentBody objects. That is the function of the StudentBodyPresenter class, which again implements INotifyPropertyChanged:

namespace ElPasoHighSchool
{
    public class StudentBodyPresenter :INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        StudentBody studentBody;
        Random rand = new Random(); 
        public StudentBodyPresenter()
        {
            Uri uri = new Uri("http://www.charlespetzold.com/Students/students.xml");// , UriKind.Relative); 
            WebClient webClient =new WebClient();
            webClient.DownloadStringCompleted += OnDownloadStringCompleted;
            webClient.DownloadStringAsync(uri);
        } 
        .... 
        protected virtual void OnPropertyChanged(string propChanged)
        {
            if (PropertyChanged !=null)
                PropertyChanged(this,new PropertyChangedEventArgs(propChanged));
        } 
        ....
    }
}

You can begin experimenting with this database by opening up a new Silverlight project, making a reference to the ElPasoHighSchool.dll library, and putting an XML namespace declaration in the MainPage.xaml file:

    xmlns:elpaso="clr-namespace:ElPasoHighSchool;assesmbly=ElPasoHighSchool"

You then instantiate this the StudentBodyPresenter class in the Resources collection:

<phone:PhoneApplicationPage.Resources>
    <elpaso:StudentBodyPresenter x:Key="studentBodyPresenter" />
</phone:PhoneApplicationPage.Resources>

You can then put a TextBlock in the content area with a binding to that resource:

<Grid x:Name="ContentPanel"Grid.Row="1" Margin="12,0,12,0">
    <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   Text="{Binding Source={StaticResource studentBodyPresenter},
                                        Path=StudentBody.School}" />
</Grid>

The screen indicates that the program is successfully downloading and deserializing the students.xml file:

seven1.gif

The SelectedItem property of the ListBox is of type Student, so the binding path can reference a property of Student, such as FullName. Now when an item is selected from the ListBox, the TextBlock displays the item's FullName property:

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" DataContext="{Binding Source={StaticResource studentBodyPresenter}, Path=StudentBody}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListBox Grid.Row="0"
                 Name="listBox"
                 ItemsSource="{Binding Students}"
                 DisplayMemberPath="FullName" />
        <TextBlock Grid.Row="1"
                   FontSize="{StaticResource PhoneFontSizeLarge}"
                   HorizontalAlignment="Center"
                   Text="{Binding ElementName=listBox,
                   Path=SelectedItem.FullName}" />
    </Grid>

seven2.gif

Or, replace the TextBlock with an Image element:

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" DataContext="{Binding Source={StaticResource studentBodyPresenter}, Path=StudentBody}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListBox Grid.Row="0"
                 Name="listBox"
                 ItemsSource="{Binding Students}"
                 DisplayMemberPath="FullName" />
        <Image Grid.Row="1"
               HorizontalAlignment="Center"
               Stretch="None"
               Source="{Binding ElementName=listBox,
               Path=SelectedItem.PhotoFilename}" />
    </Grid>

You can now go through the ListBox and select an item to view that student's picture:

seven3.gif

Fun with DataTemplates

For the remainder of this article, I want to switch from the ListBox to the ItemsControl to focus solely on presentation and navigation rather than selection. To play along, you can create a new project, set a reference to the ElPasoHighSchool library, and in the XAML file add an XML namespace declaration for that library and instantiate the StudentBodyPresenter class in the Resources collection as in the previous program, just Replace the DisplayMemberPath with a DataTemplateto provide more extensive information, nicely formatted:

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <ScrollViewer>
            <ItemsControl ItemsSource="{Binding Students}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Border BorderBrush="{StaticResource PhoneAccentBrush}" BorderThickness="1" CornerRadius="12" Margin="2">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="*" />
                                    <RowDefinition Height="*" />
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Source="{Binding PhotoFilename}" Height="120" Width="90"Margin="6" />
                                <StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
                                    <TextBlock Text="{Binding LastName}" />
                                    <TextBlock Text=", " />
                                    <TextBlock Text="{Binding FirstName}" />
                                    <TextBlock Text=", " />
                                    <TextBlock Text="{Binding MiddleName}" />
                                </StackPanel>
                                <StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
                                    <TextBlock Text="Grade Point Average = " />
                                    <TextBlock Text="{Binding GradePointAverage}" />
                                </StackPanel>
                            </Grid>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>

In this template, the height of the individual items is governed by the explicit Height setting on the Image element. To prevent the text from moving to the right as the photos are being loaded, an explicit Width setting is also provided. Here's the result:

seven4.gif

The DataTemplate Bar Chart

With a combination of a DataTemplate and an ItemsPanelTemplate, you can make a ListBox or ItemsControl look like no other ListBox or ItemsControl you've ever seen.

Let's create a new project, the GpaBarChart project shows one approach. It has the StudentBodyPresenter and two converters I mentioned defined as resources:

    <phone:PhoneApplicationPage.Resources>
        <elpaso:StudentBodyPresenter x:Key="studentBodyPresenter" />
        <petzold:MultiplyConverter x:Key="multiply" />
        <petzold:ValueToBrushConverter x:Key="valueToBrush"
                                       Criterion="1"
                                       GreaterThanBrush="{StaticResource PhoneAccentBrush}"
                                       EqualToBrush="{StaticResource PhoneAccentBrush}"
                                       LessThanBrush
="Red" />
    </phone:PhoneApplicationPage.Resources>

Most of the content area you've already seen but I also added a Border with the name "studentDisplay" floating near the top. This Border includes a couple TextBlock elements with their Text properties bound to the properties FullName and GradePointAverage under the assumption that the DataContext of this Border is an object of type Student. That's not normally the case, so the Border has its Visibility property initialized to Collapsed:

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"
              DataContext="{Binding Source={StaticResource studentBodyPresenter},
                                    Path
=StudentBody}">
           
           
<Border x:Name="studentDisplay"
                    BorderBrush="{StaticResource PhoneForegroundBrush}"
                    BorderThickness="{StaticResource PhoneBorderThickness}"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Top"
                    Margin="24"
                    Padding="12"
                    CornerRadius="24"
                    Visibility
="Collapsed">
                <StackPanel>
                    <TextBlock Text="{Binding FullName}"
                               HorizontalAlignment="Center" />
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="GPA = " />
                        <TextBlock Text="{Binding GradePointAverage}" />
                    </StackPanel>   
               
</StackPanel>
            </Border>           
           
<ItemsControl ItemsSource="{Binding Students}"
                          VerticalAlignment="Bottom">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Rectangle Fill="{Binding GradePointAverage,
                                                  Converter={StaticResource valueToBrush}}"                                   
                                   Height="{Binding GradePointAverage,
                                                    Converter={StaticResource multiply},
                                                    ConverterParameter=50}"
                                   VerticalAlignment="Bottom"
                                   Margin
="1 0" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate> 
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <petzold:UniformStack Orientation="Horizontal" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </Grid>

The code-behind file fills in the missing logic. The page processes the Touch.FrameReported event. When the element directly behind the primary touch point is a Rectangle, the event handler obtains the DataContext of that Rectangle. That is an object of type Student. That object is then set to the DataContext of the Border. The TouchAction property is used to turn the Visibility on and off:

namespace GpaBarChart
{
    public partial class MainPage : PhoneApplicationPage
    {
        public MainPage()
        {
            InitializeComponent();
            Touch.FrameReported += OnTouchFrameReported;
        } 
        void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
        {
            TouchPoint touchPoint = args.GetPrimaryTouchPoint(this); 
            if (touchPoint !=null && touchPoint.Action ==TouchAction.Down)
                args.SuspendMousePromotionUntilTouchUp(); 
            if (touchPoint !=null && touchPoint.TouchDevice.DirectlyOveris Rectangle)
            {
                Rectangle rectangle = (touchPoint.TouchDevice.DirectlyOveras Rectangle); 
                // This DataContext is an object of type Student
                object dataContext = rectangle.DataContext;
                studentDisplay.DataContext = dataContext;
                if (touchPoint.Action ==TouchAction.Down)
                    studentDisplay.Visibility = Visibility.Visible; 
                else if (touchPoint.Action ==TouchAction.Up)
                    studentDisplay.Visibility = Visibility.Collapsed;
            }
        }
    }
}

As you run your fingers across the bars, you can see the student that each bar represents:

seven5.gif