Export (0) Print
Expand All

Displaying Data with Data Binding

Applies to: Windows Phone 7

Published: August 2011

Author: Nick Randolph

WROX Tech Editors for Windows Phone 7 Articles

Wrox Windows Phone 7 Books

This topic contains the following sections.

So far, you’ve seen how to access data from a remote server and how to save and restore data from isolated storage. There is not much point in having this data if you cannot display it. In this section, you look at how to use data binding to display information to the user. Before you go into this, look at an example using a more traditional approach, without data binding.

We’ll begin with a simple class called Participant, which could represent a participant in a sporting club:

public class Participant {
    public Guid Id { get; set; }
    public string Name { get; set; }
    public double Points { get; set; }
    public DateTime MemberSince { get; set; }
}

With the more traditional approach to displaying data, each visual element would be updated with the corresponding piece of data to be displayed. The following code sets the Text property of the TextBlock elements NameText and PointsText and the Value property of the MemberSinceDate DatePicker element (available in the Silverlight Toolkit for Windows Phone via http://codeplex.com).

this.NameText.Text = participant.Name;
this.PointsText.Text = participant.Points.ToString();
this.MemberSinceDate.Value = participant.MemberSince;

As you can see, this approach requires code to explicitly set each of the values that are to be displayed. It also means that if any data is changed, the appropriate visual element needs to be updated. Typically, this block of code would be factored out so that it can be easily called to update all the visual elements.

private void TraditionalClick(object sender, RoutedEventArgs e){
    var participant = CreateParticipant();
    RefreshParticipantData(participant);

    participant.Points += 23.45;
    RefreshParticipantData(participant);
}

private void RefreshParticipantData(Participant participant) {
    this.NameText.Text = participant.Name;
    this.PointsText.Text = participant.Points.ToString();
    this.MemberSinceDate.Value = participant.MemberSince;
}

As you can see, while this improves the layout of the code, making it easier to read and maintain, it means that whenever any property changes on the participant object, all the visual elements need to be updated. On the other hand, if the user modifies the data, changing the MemberSinceDate in the DateTimePicker, this data needs to be manually applied back to the participant object. One of the goals of data binding is to reduce the requirement to update visual elements and to do it in a way that it is declarative, making it easier to read and maintain.

One of the premises behind data binding in WPF, Silverlight, and Silverlight for Windows Phone is that it should be able to be done declaratively. While it is possible to create data bindings in code, it is not the preferred approach and should be avoided where possible.

Conceptually, data binding involves two parties. One is the data source, which is—or has a property that exposes—the data to be displayed. The other party is the target, which is typically a property on a visual element that is to be updated based on the data source. In our example, the data source could be the Name property on the Participant instance. The target would be the Text property of the NameText TextBlock element.

DataContext Property

Any visual element that inherits from the FrameworkElement class exposes the DataContext property, which specifies the data source for any data binding. In our example, you can set the DataContext value for each of the three visual elements:

private void RawDataBindingClick(object sender, RoutedEventArgs e) {
    var participant = CreateParticipant();
    this.RawNameText.DataContext = participant.Name;
    this.RawPointsText.DataContext = participant.Points.ToString();
    this.RawMemberSinceDate.DataContext = participant.MemberSince;
}

You also need to modify the XAML for each of these elements to specify that you want to data bind the appropriate properties. In the following snippet, the Text property on the two TextBlock instances and the Value property on the DatePicker instance have been set to {Binding}. The curly braces indicate that the value specified needs to be evaluated, rather than indicating a literal value. The Binding keyword indicates that the expression is to establish data binding. In this case, the properties are data bound directly to whatever the DataContext property is set to.

<TextBlock x:Name="RawNameText" Text="{Binding}"/>
<TextBlock x:Name="RawPointsText" Text="{Binding}" />
<toolkit:DatePicker x:Name="RawMemberSinceDate" Value="{Binding}" />

Binding Path

Although we’ve introduced the use of data binding, you have not really achieved much because you still have to explicitly set the DataContext property on each of the visual elements, and you need to set it explicitly to the property value on the Participant object. You can improve this by specifying a binding path on each element:

<StackPanel x:Name="ParticipantInfo"> 
     <TextBlock Text="{Binding Path=Name}" />
     <TextBlock Text="{Binding Points}" />
     <toolkit:DatePicker Value="{Binding MemberSince}" />
</StackPanel>

The data binding syntax used in the first TextBlock instance illustrates the full syntax for specifying the binding path. However, this can be abbreviated as shown in the second TextBlock instance and in the DatePicker instance, where the “Path=” entry has been dropped. In essence, the path component indicates the name of the property that the element should be data bound to on the DataContext instance. The Text property on the first TextBlock instance is data bound to the Name property on the DataContext instance. Of course, this means that you need to set the DataContext instance to be the Participant instance, rather than the current Name value of the Participant instance.

Rather than setting the DataContext instance on each of the three elements to be the same Participant instance, you can rely on DataContext inheritance. What this means is that if there is no DataContext instance specified for an element, any binding expression traverses up the XAML tree looking for the first parent element where the DataContext instance has been specified.

private void DataBindingPathClick(object sender, RoutedEventArgs e) {
    var participant = CreateParticipant();
    this.ParticipantInfo.DataContext = participant;
}

The preceding code sets the DataContext instance on the parent StackPanel instance named ParticipantInfo. This cascades down to each of the visual elements, allowing their binding expressions to be evaluated.

Another point to note is that we’ve removed the x:Name attribute from the two TextBlock and the DatePicker elements, because now that you are using data binding you no longer need to refer to them by name. After each page is created, each element that has a name has a corresponding backing variable that has to be connected to the appropriate visual element. Reducing unnecessary x:Name properties can improve the load time of the page.

INotifyPropertyChanged Interface

In the traditional example provided earlier in this section, when a change was made to any of the properties of the underlying Participant object, code had to be executed that would step through and update the appropriate visual elements. This can be achieved with data binding, minimizing both the amount of code that has to be written and the risk that the elements are not updated correctly.

The data binding system cannot automatically intercept when a .NET property is changed. However, by implementing the INotifyPropertyChanged interface, your data object can advise the data binding system when properties are changed. This interface is relatively simple, with only a single event, PropertyChanged. The following code illustrates this event, along with a helper method that makes raising the event much simpler:

public class Participant:INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName){
        if (PropertyChanged != null) {
            PropertyChanged(this, 
                            new PropertyChangedEventArgs(propertyName));
        }
    }
}

When you specify an object that implements INotifyPropertyChanged as a DataContext object, the data binding system registers an event handler for the PropertyChanged event. When this event is raised, the data binding system inspects the PropertyChangedEventArgs parameter to determine the name of the property that has changed. It first attempts to retrieve the new value of this property, before iterating through any binding expressions that might be dependent on that property. If found the new value is applied to that data binding which updates the corresponding visual element.

The following code illustrates how the Points property can be modified to raise the PropertyChanged event when the value changes:

private double points;
public double Points {
    get { return points; }
    set {
        if (Points == value) return;
        points = value;
        this.RaisePropertyChanged("Points");
        this.RaisePropertyChanged("IsGoldMember");
    }
}
        
public bool IsGoldMember {
    get { return Points > 1000000; }
}

Also notice the second call to RaisePropertyChanged, this time specifying the IsGoldMember property name. While the IsGoldMember value has not been explicitly set, that value is dependent on the current Points property value. When the Points property changes, you want to ensure that any visual elements that are data bound either directly to the Points property or via the IsGoldMember property are updated. Raising a second PropertyChanged event, this time with the IsGoldMember property name, ensures that all those elements are updated too.

Value Converters

Occasionally, the value type exposed by a property on the data source will not match the type of property that you want to data bind to on the visual element. For example, you might have an element that indicates whether the participant is a gold member, which you only want to display if that participant actually is a gold member. To achieve this, you need to toggle the Visibility property between Collapsed and Visible, depending on whether the participant is a gold member or not. You need to have a Boolean property on the data source that you want to data bind to a Visibility property on the TextBlock element.

To achieve this, you can use a value converter that intercepts the data binding and allows the translation between the different types of data. A BoolToVisibilityConverter instance can be used to convert either a bool to a Visibility value or a Visibility value to a bool value.

public class BoolToVisibilityConverter:IValueConverter {
    public object Convert(object value, Type targetType, 
                          object parameter, CultureInfo culture) {
        if(value is bool) {
            return ((bool) value) ? Visibility.Visible 
                                  : Visibility.Collapsed;
        }
        return Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, 
                              object parameter, CultureInfo culture) {
        if(value is Visibility) {
            return ((Visibility) value) == Visibility.Visible ? true : false;
        }
        return false;
    }
}

To use this converter in data binding, you need to create an instance of it and then include it in the data binding expression. In the following XAML, an instance of BoolToVisibilityConverter is created as a resource of the parent StackPanel element. It is then included in the data binding expression for the Visibility property on the TextBlock element.

<StackPanel x:Name="ParticipantInfo">
    <StackPanel.Resources>
        <sample:BoolToVisibilityConverter x:Key="BoolToVisibility" />
    </StackPanel.Resources>
    <TextBlock Text="Gold Member"
               Visibility="{Binding IsGoldMember, Converter={StaticResource BoolToVisibility}}" />
</StackPanel>

So far, you have seen how to use data binding to connect your visual elements—your View—with the data you want to display—your Model. This pattern is what is known as Model-View-ViewModel, or just MVVM. While many debate as to exactly what MVVM is, in basic terms it is the use of data binding to link your View and Model via a ViewModel object. At this point, you might be wondering what a ViewModel object is and where it was in the previous data binding example. Well, the truth is that in the previous example you wired the View directly to our Model, which is not recommended as it introduces tight coupling between your user interface and your data model. You should think of the ViewModel object as representing the current state of the View. It’s a regular .NET class with properties that map to properties on the View. Rather than writing code to adjust properties on the visual elements of a page, you create properties on the corresponding ViewModel instance and then data bind them to the appropriate visual element. Adjusting the value of a property on the ViewModel instance results in a change to any visual element data bound to that property. In this way, the business logic and behavior of your application can be dissociated from the View and into the ViewModel instance. Because ViewModel is a regular .NET class, you can write test cases that validate its behavior.

Separating Model, View, and ViewModel

Now that we’ve covered the theoretical discussion of what MVVM is, take a more practical look at how to use MVVM. The first step is to create a separate class, our View Model, which represents the current visual state of the page. In this case, you are using a page called MVVMPage, so create a corresponding View Model class called MVVMPageViewModel. Because you want changes to properties on a View Model to update properties on visual elements via data binding, the View Model needs to implement the INotifyPropertyChanged interface discussed earlier. You should end up with a class similar to the following:

public class MVVMPageViewModel: INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged(string propertyName) {
        if (PropertyChanged != null) {
            PropertyChanged(this, 
                            new PropertyChangedEventArgs(propertyName));
        }
    }
    private Visibility randomTextVisibility;
    public Visibility RandomTextVisibility {
        get { return randomTextVisibility; }
        private set {
            if (RandomTextVisibility == value) return;
            randomTextVisibility = value;
            this.RaisePropertyChanged("RandomTextVisibility");
        }
    }
}

In this example, we’ve included a property, RandomTextVisibility, which you use to toggle whether or not a simple TextBlock element is visible. To wire this up using data binding, the first thing you need is to set the DataContext value for the page. Previously you saw how to do this in code after the page has loaded or when the user clicks a button. In this case, you want the View Model to be wired up as the DataContext value for the page as soon as the page has been created. To do this, you can set the DataContext property in the XAML to a new instance of MVVMPageViewModel, as shown in the following code:

<phone:PhoneApplicationPage 
    x:Class="WindowsPhoneData.MVVMPage"
    … 
    xmlns:local="clr-namespace:WindowsPhoneData" >
    <phone:PhoneApplicationPage.DataContext>
        <local:MVVMPageViewModel />
    </phone:PhoneApplicationPage.DataContext>

If you switch over to Expression Blend (you can right-click the project node in Solution Explorer and click Open in Expression Blend as a shortcut) and open the MVVMPage, notice that the MVVMPageViewModel class appears in the Data window, as shown in Figure 9.

Figure 9: Data Window Showing Data Context

Referenced Image

Setting up a data binding between the RandomTextVisibility property and the Visibility property of a TextBlock element on the page can be done a number of ways. One way is to drag the RandomTextVisibility property from the Data window onto the element you want to data bind to. Unfortunately, this actually binds the property to the Text property on the TextBlock element. However, if you hold down the Shift key while you drag it across, when you release the mouse button you are prompted to select which property on the destination element you want to data bind to. In this case, you want to data bind to the Visibility property so that you can toggle whether or not the element is visible. This results in the following XAML being wired up for the TextBlock element:

<TextBlock Text="Text that will be toggled" 
           Visibility="{Binding RandomTextVisibility}" />

If you need to go back and adjust this later, you can simply open the Properties window, and click the box next to the Visibility property. By default, this box has a transparent center with a white border; a solid yellow box indicates that the property has been set to use data binding. After clicking the box, select DataBinding to open the CreateDataBinding window shown in the right of Figure 10. This dialog can be used to create or adjust more complex binding expressions, including those that use value converters.

Figure 10: The Create Data Binding Window

Referenced Image

To complete this example, you need to wire up a button that can be used to toggle the RandomTextVisibility property:

private void ToggleVisibility(object sender, RoutedEventArgs e) {
    (this.DataContext as MVVMPageViewModel).ToggleVisibility();
}

The following code calls a ToggleVisibility method on the View Model, rather than setting the RandomTextVisibility value explicitly:

public void ToggleVisibility() {
    this.RandomTextVisibility = 
       this.RandomTextVisibility == Visibility.Visible ? Visibility.Collapsed          
                                                       : Visibility.Visible;
}

When writing code, you should be mindful of the separation of concerns. The View, which is made up of both the XAML and its corresponding code-behind file, should handle only the display of the visual elements. Meanwhile, the View Model should contain any code that would result in a change to either the Model or properties on the View. This ensures that any logic can be thoroughly tested, as well as allowing designers and developers to work on the same application independently. Unfortunately, sometimes a little bit of an overlap exists, where event handler code is placed in the code behind file of the XAML page. This is unavoidable, and as long as all the event handler does is call a method on the View Model, it does not pose too much of an issue.

Design Time vs. Runtime Data

Next, extend your page a little to include a list of Participant objects. Before you can wire up the View, which will be a ListBox control that contains a row for each participant, you need to extend the View Model to contain a collection for holding the Participant instances.

public class MVVMPageViewModel: INotifyPropertyChanged {
    public ObservableCollection<Participant> Participants 
        { get; private set; } 
    public MVVMPageViewModel() {
        Participants = new ObservableCollection<Participant>();
    }

The Participants property is of type ObservableCollection<Participant>, which is a specialized generic collection. Each time a Participant item is added, removed, or changed within this collection, it raises a CollectionChanged event (part of the INotifyCollectionChanged interface). If an instance of this collection is data bound to a visual element such as a ListBox control, the corresponding element is updated according to this event. For example, when an item is added to the collection, a new item is added to the data bound ListBox control.

It’s more than likely that the list of Participant objects is going to come from a call to a remote service. To refactor this logic so that it can potentially be used from anywhere in the application, you place it in a helper class called RunTimeRepository, and as you saw with the WebClient object, the design requires an asynchronous method, which, when completed, raises a corresponding completed event.

public class RunTimeRepository {
    public event EventHandler<ParameterEventArgs<Participant[]>> CurrentParticipantsCompleted;
    public void CurrentParticipantsAsync() { ... }
}

When a new instance of MVVMPageViewModel is created, it needs to call the CurrentParticipantsAsync method to populate the Participants collection.

public MVVMPageViewModel() {
    Participants = new ObservableCollection<Participant>();
    var repository = new RunTimeRepository();
    repository.CurrentParticipantsCompleted +=
        repository_CurrentParticipantsCompleted;
    repository.CurrentParticipantsAsync();
}
void repository_CurrentParticipantsCompleted(object sender, 
         ParameterEventArgs<Participant[]> e) {
    Array.ForEach(e.Parameter,(p)=>Participants.Add(p));
}

Next, design the layout of the ListBox control in Expression Blend. Unfortunately, if you switch over to Expression Blend now, you may see an exception raised if the service request fails. Even if you were to add appropriate exception handling, you would not see any data being displayed when you start to lay out the ListBox control at design time.

An elegant solution to this problem involves refactoring the RunTimeRepository object into an interface, IRepository. You can then create an alternate DesignTimeRepository object that can be used at design time.

public interface IRepository {
    event EventHandler<ParameterEventArgs<Participant[]>> CurrentParticipantsCompleted;

    void CurrentParticipantsAsync();
}

public class RunTimeRepository :IRepository { … }
public class DesignTimeRepository : IRepository {
    public event EventHandler<ParameterEventArgs<Participant[]>> CurrentParticipantsCompleted;

    public void CurrentParticipantsAsync() {
        if (CurrentParticipantsCompleted != null) {
            CurrentParticipantsCompleted(this,
                new ParameterEventArgs<Participant[]>
                    (new[] {
                     new Participant() {Name = "Bob Jones"},
                     new Participant() {Name = "Freddy Smith"}
                     }));
        }
    }
}

In the MVVMPageViewModel constructor, you need to add logic for selecting the appropriate IRepository implementation.

public MVVMPageViewModel() {
    Participants = new ObservableCollection<Participant>();
    IRepository repository;
    if(DesignerProperties.IsInDesignTool) {
        repository = new DesignTimeRepository();
    }
    else {
        repository = new RunTimeRepository();
    }
    repository.CurrentParticipantsCompleted +=
              repository_CurrentParticipantsCompleted;
    repository.CurrentParticipantsAsync();
}

Now when you switch to Expression Blend, you can see that any exceptions have disappeared and that you can see the Participants list in the Data window for the MVVMPage element. (If not, you might need to force a build to refresh the data binding context of the page.) From the Assets window, locate and drag a ListBox control onto the design surface. Position and resize the ListBox control so that it takes up the available space. From the Data window, expand out the Participants node, shown in Figure 11.

Figure 11: Participants Node in the Data Window

Referenced Image

Drag the Name element onto the ListBox control. This not only sets the Participants property to be the ItemsSource value for the list, it also creates a template that contains a TextBlock element data bound to the Name property of each item in the list.

<ListBox ItemsSource="{Binding Participants}" >
    <ListBox.Resources>
        <DataTemplate x:Key="ParticipantTemplate">
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
    </ListBox.Resources>
    <ListBox.ItemTemplate>
        <StaticResource ResourceKey="ParticipantTemplate"/>
    </ListBox.ItemTemplate>
</ListBox>

In the preceding code, the DataTemplate element has been converted from a page resource (where Expression Blend creates it by default) to a resource belonging only to this list.

What you might have noticed in doing this is that the ListBox control is populated with the two designer Participant objects created by the DesignTimeRepository instance. If you now run the project, you can see that the full list of participants is retrieved from the remote server and displayed in the ListBox control. By maintaining consistency between the two repository implementations, you can continue to have design time data with which to work, which is then replaced by real data loaded by the RunTimeRepository object at runtime.

Repository Pattern

One of the weaknesses of the current implementation is that it is left up to the individual View Model to create the correct IRepository implementation. It also means that each View Model has to create a new instance of the IRepository implementation, which limits the amount of caching that can be provided between View Model instances.

A solution to these issues would be to have a single IRepository instance that can be shared among the View Model instances. This is a challenge because the current method of creating the View Model in XAML does not provide a way to supply an IRepository interface to the View Model. One option is to create the IRepository instance as an application resource and, on each View Model, expose a Repository property that can be set when it is created. In the following code example, an instance of the RunTimeRepository is created as an Application resource. This is subsequently referenced by name when setting the Repository property of MVVMPageViewModel that is created as the DataContext element.

<Application 
    x:Class="WindowsPhoneData.App"
    ...
    xmlns:local="clr-namespace:WindowsPhoneData">
    <Application.Resources>
        <local:RunTimeRepository x:Key="Repository" />
    </Application.Resources>

<phone:PhoneApplicationPage 
    x:Class="WindowsPhoneData.MVVMPage" 
    ... >
    <phone:PhoneApplicationPage.DataContext>
        <WindowsPhoneData:MVVMPageViewModel 
            Repository="{StaticResource Repository}"/>
    </phone:PhoneApplicationPage.DataContext>

The downside of this approach is that you need to specify which of the IRepository implementations you want to include in the App.xaml file. In addition, because the IRepository instance is not available until after the Repository property value has been evaluated, the code that was previously in the constructor needs to be invoked behind the Repository property setter. As you can imagine, this rapidly becomes quite messy and hard to maintain.

An alternative, much more elegant approach addresses not only these challenges but also provides a good mechanism for controlling the life cycle of the View Models. You need to create a new class called ViewModelLocator. This class has two jobs: first to decide which IRepository implementation to create an instance of and, second, to create instances of the View Models on request.

public class ViewModelLocator{
    public IRepository Repository { get; private set; }

    public ViewModelLocator(){
        if (DesignerProperties.IsInDesignTool){
            Repository = new DesignTimeRepository();
        }
        else{
            Repository = new RunTimeRepository();
        }
    }

    public MVVMPageViewModel MVVMPageViewModel{
        get {
            return new MVVMPageViewModel(this.Repository);
        }
    }
}

As you can see from the MVVMPageViewModel property, which creates a new instance of the MVVMPageViewModel class, you need to modify the constructor of the View Model to accept an IRepository instance as a parameter:

public MVVMPageViewModel(IRepository repository) {
    Participants = new ObservableCollection<Participant>();
    repository.CurrentParticipantsCompleted +=
        repository_CurrentParticipantsCompleted;
    repository.CurrentParticipantsAsync();
}

Rather than create a RunTimeRepository instance as an Application resource, you need to create a ViewModelLocator instance:

<Application.Resources>
    <local:ViewModelLocator x:Key="Locator" />
</Application.Resources>

Finally, within the MVVMPage instance, you need to adjust the DataContext syntax so that it accesses the MVVMPageViewModel property on the Locator object.

<phone:PhoneApplicationPage.DataContext>
    <Binding Path="MVVMPageViewModel"
             Source="{StaticResource Locator}" />
</phone:PhoneApplicationPage.DataContext>

And there you have it; not only do you have data when the application runs, you also have data that you can work with at design time, and you have a simple model for creating and managing View Models within your application.

In the previous example, you saw how the ItemsSource property of a ListBox control could be data bound to an ObservableCollection<Participant> instance. The name of each participant appears in the list, but what if you want to display more information about a participant? This section explains how you can extend various controls by changing their templates.

ListBox Control

The ListBox control appears to be relatively simple. However, you’ll see that by changing a couple of the templates, you can radically change the way it appears. Begin by looking at the ItemTemplate instance. To start with, return to our MVVMPage, this time starting with a new ListBox control. Locate the ListBox control in the Assets window, and drag it onto the page. Adjust the layout to ensure that the ListBox control takes up the full width and at least half the height of the page.

Next, open the Data window and drag the Participants node across onto the ListBox control, as shown in Figure 12. As the dialog indicates, this data binds the Participants collection to the ItemsSource property on the ListBox control.

Figure 12: Setting Up Data Binding

Referenced Image

What the dialog does not tell you is that by dragging the whole Participants node across, not only does it set up the binding for the ItemsSource property, but it also creates visual elements for each property on the Participant class. Right-click the ListBox control, and select Edit Additional TemplatesEdit Generated Items (ItemTemplate)Edit Current (illustrated in Figure 13).

Figure 13: Edit Item Template

Referenced Image

In the context menu shown in Figure 13, you can see two menu items that relate to templates. The first, Edit Template, is for editing the template for the entire ListBox control. In this case, you want to edit only the template that controls how each of the items is displayed. This is the ItemTemplate element, which is listed under Edit Additional Templates. By default, when you create a ListBox control on the page, it has no ItemTemplate property and no way of knowing how to display items. However, when you dragged the Participants collection onto the ListBox control earlier, Expression Blend was clever enough to go ahead and create a new template, add elements to it to best represent the public properties on the Participant class, and set the new template as the ItemTemplate property for the ListBox control.

When you click Edit Current, you can see that the Objects and Timeline window changes to show only the elements that make up the template you are currently editing. In this case, it should be made up of a StackPanel element, with a CheckBox element and three TextBlock elements nested inside. Switching to split layout, you can see that the XAML for this DataTemplate element is as follows:

<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="ParticipantTemplate">
<StackPanel>
<CheckBox IsChecked="{Binding IsGoldMember, Mode=OneWay}"/>
<TextBlock Text="{Binding MemberSince}"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Points}"/>
</StackPanel>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>

By default, Expression Blend has made this template into a page resource so that it can potentially be reused across multiple ListBox controls. The DataTemplate element can then be referenced by the ListBox control later in the page by its Key, ParticipantTemplate, as shown in the following XAML for the ListBox control.

<ListBox Height="401" ItemTemplate="{StaticResource ParticipantTemplate}" 
                      ItemsSource="{Binding Participants}" />

Earlier in this article, you actually saw an alternative way to define a DataTemplate element, which was to nest it as a ListBox resource (shown again here for your reference). In this case, the ListBox control is the only control that can make use of the template.

<ListBox ItemsSource="{Binding Participants}" >
    <ListBox.Resources>
        <DataTemplate x:Key="ParticipantTemplate">
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
    </ListBox.Resources>
    <ListBox.ItemTemplate>
        <StaticResource ResourceKey="ParticipantTemplate"/>
    </ListBox.ItemTemplate>
</ListBox>

Take a closer look at the way that data binding has been set up within the DataTemplate element. You’ll notice that each of the elements (except for the StackPanel element) within the template have a property that is data bound using the {Binding} syntax. What’s interesting is that there is no reference to a DataContext instance. Earlier you saw that the DataContext instance does not need to be explicitly set on every item. In these cases, the DataContext instance is inherited from the parent elements. Following this rule, the TextBlock element that is data bound to the Name property inherits the DataContext instance from the StackPanel instance, which inherits from the DataTemplate instance. At this point, it becomes unclear as to where the DataTemplate instance gets its DataContext reference. Clearly, it cannot be from its parent element, because PhoneApplicationPage.Resources is simply just a dictionary of resources. It also cannot simply inherit the DataContext instance of the ListBox control where the template is used, because this would set the DataContext instance of each item in the list to be a collection of Participant objects.

You have to remember that the DataTemplate instance is like a cookie-cutter. For each item in the list, the DataTemplate instance is used to create a set of elements to represent the item on the screen. As such, the DataContext instance for each set of elements is the list item itself. When designing the template for a list, you can think of the DataContext instance for each of the elements as being set to one of the items that is in the list. For example, in this case, the DataContext instance for the StackPanel instance and for each element nested within it is a Participant instance.

To complete this example, tidy up the layout of the Participant information as follows:

<DataTemplate x:Key="ParticipantTemplate">
    <StackPanel Orientation="Horizontal">
        <CheckBox IsChecked="{Binding IsGoldMember, Mode=OneWay}"/>
        <StackPanel>
            <TextBlock Text="{Binding Name}" 
                       Style="{StaticResource PhoneTextLargeStyle}"/>
            <TextBlock Text="{Binding Points}" 
                       Style="{StaticResource PhoneTextSmallStyle}"/>
        </StackPanel>
    </StackPanel>
</DataTemplate>

Panorama Control

The next control to investigate is the Panorama control. A great example of the visual effect possible with the Panorama control is the People hub on the device. In fact, each one of the six main hubs on a Windows Phone device makes use of the panoramic view to aggregate data from multiple different sources.

In theory, the panorama is made up of any number of PanoramaItem elements. In reality, you should aim for three to five items. If you have only two items, the interface feels weird because the user is effectively just switching back and forth between two views. With more than five items, you not only run into performance problems around load time and memory consumption, you also run the risk of confusing the user.

Continue working with the MVVMPage so that you already have the design time data to work with. You need to remove or just comment out all elements that are currently on the page. Then, from the Assets window in Expression Blend, locate the Panorama control and drag it onto the design area. If you cannot locate the Panorama control in the Assets window, you might need to add a reference to Microsoft.Phone.Controls to your project.

When you drop the Panorama control onto the design area, it most likely appears in the middle of the page, similar to the left image of Figure 14. The Panorama control is designed to use the full page area, so you should both remove the system tray at the top of the screen and resize the control to take up the full page. In the Objects and Timeline window, select the PhoneApplicationPage node. Then in the Properties window, uncheck the Show SystemTray box. Next, right-click the [Panorama] node in the Objects and Timeline window and select Auto SizeFill. This should expand the Panorama control to take up the full screen, as in the right image of Figure 14.

Figure 14: Arranging the Panorama

Referenced Image

When designing the panorama, you should think of it as being an elongated view that is four to six times wider than the screen. The screen effectively acts as a viewport that slides over the top. The panorama is made up of a number of PanoramaItem elements, and the user, when panning the screen to left or right, continues to cycle through the items. Despite being cyclic, the panorama has a definite beginning, where the title and item headers are aligned along their left edge (as in the right image of Figure 14). To encourage the user to explore and reveal more content, notice that a small sliver of the next PanoramaItem element appears at the edge of the screen (see the right edge of the right image in Figure 14).

There are essentially three styles of PanoramaItem layouts. The simplest is to position the content so that it appears within the area allocated to the PanoramaItem element (first image, Figure 15). One of the most common layouts is the vertical view, easily achieved by adding a ListBox control into the PanoramaItem element (second image, Figure 15). The third layout option is a horizontal list, quite often mirroring the tile effect of both the Start screen and the People hub (third image, Figure 15).

Figure 15: Full Panorama

Referenced Image

As you are probably already familiar with basic control layout, and now data binding with a ListBox control, you should be comfortable with the first two layout options. Look at how you can take the humble ListBox control and convert it into a series of tiles.

You want the tiles to be able to expand to fill an area that is wider than the screen. To do this, you must first configure the PanoramaItem element that is going to house the ListBox control. In Expression Blend, select the PanoramaItem element that you want to use in the Objects and Timeline window, and change the Orientation property to Horizontal. This permits the width of the PanoramaItem element to expand to accommodate its contents.

Next, drag a ListBox control onto the PanoramaItem element; right-click the newly created ListBox control, and select Auto SizeFill. Wire up the ItemsSource property by dragging the Participants collection from the Data window onto the ListBox control. Do not worry about styling the layout for the time being because we’ll come back to that in a minute.

Currently, the ListBox control is still configured to list items in a vertical list. The first adjustment is to the ListBox control itself; expand out the Layout region of the Properties window and change the VerticalScrollBarVisibility property to Disabled. This disables vertical scrolling on the ListBox control.

The next step involves changing the ItemsPanel template for the ListBox control. This template is used to position each of the items in the list, as distinct from how each of the items is presented, which is defined by the ItemsTemplate element. The default ItemsPanel template is a virtualized StackPanel object that has built in optimizations for handling large lists of items. In most cases, do not customize the ItemsPanel template, because you might inadvertently affect the performance of the ListBox control. In this case, you want to show only a limited number of tiles and you need to change this template to use a WrapPanel control instead of a StackPanel control.

If you have not already downloaded the Silverlight toolkit for Windows Phone, you now need to do so because it contains the WrapPanel control. Right-click the ListBox control, and select Edit Additional TemplatesEdit Layout of Items (ItemsPanel)Create Empty. Delete the StackPanel element that appears in the Objects and Timeline window, and replace it with a WrapPanel element. Also, change the Orientation property of the WrapPanel control to Vertical. The Orientation property determines whether the WrapPanel control scrolls items left-to-right and then top-to-bottom (Horizontal), or top-to-bottom and then left-to-right (Vertical). If you want to scroll items horizontally, you need to set the MaxWidth property on the WrapPanel control; otherwise, it continues tiling items horizontally without wrapping.

The resulting PanoramaItem XAML is as follows:

<controls:PanoramaItem Header="horizontal" Orientation="Horizontal">
    <Grid>
        <ListBox ItemsSource="{Binding Participants}" 
                 ScrollViewer.VerticalScrollBarVisibility="Disabled">
            <ListBox.Resources>
                <ItemsPanelTemplate x:Key="HorizontalListItemPanelTemplate">
                    <toolkit:WrapPanel Orientation="Vertical"/>
                </ItemsPanelTemplate>
                <DataTemplate x:Key="WrapItemTemplate">
                    <StackPanel Width="270">
                        <TextBlock Text="{Binding Name}" 
                        Style="{StaticResource PhoneTextExtraLargeStyle}" 
                        HorizontalAlignment="Center"/>
                        <TextBlock Text="{Binding MemberSince}" 
                                   HorizontalAlignment="Center" 
                                   VerticalAlignment="Top"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.Resources>
            <ListBox.ItemTemplate>
                <StaticResource ResourceKey="WrapItemTemplate"/>
            </ListBox.ItemTemplate>
            <ListBox.ItemsPanel>
                <StaticResource 
                 ResourceKey="HorizontalListItemPanelTemplate"/>
            </ListBox.ItemsPanel>
        </ListBox>
    </Grid>
</controls:PanoramaItem>

This should give you a layout similar to Figure 16, which shows the design surface in Expression Blend.

Figure 16: Elongated Panorama Item

Referenced Image

Pivot Control

The Pivot control is often a source of confusion. While it does have some similar characteristics to the Panorama control, in that the user can swipe left to right to go between items, it is distinctly different. You should think about the Panorama control as being one single elongated screen with a background that scrolls as the user scrolls left to right. The Pivot control, on the other hand, should be viewed almost like the legacy tab control from Windows Forms development. Each tab reveals a different set of data, and the background remains constant across all tabs.

Even when you understand the technical and usability differences between the two controls, it can still be difficult to work out which control should be used for a given scenario. A good rule of thumb is that the Panorama control is the best way to display data aggregated from a number of sources (similar to what the Hubs do in the core Windows Phone platform). On the other hand, the Pivot control should be used to display different views of the same set of data. This might be different pieces of data relating to a given item (for example, details, what’s new, and messages for a contact), or it might be different ways to order a list (for example, property listings could be sorted by list date, suburb, price, and so on).

One of the most powerful features of the Pivot control is that you can actually data bind the entire layout, including the number and layout of each PivotItem element, as well as the header. In this case, you can again use the MVVMPage, but this time sort the participants a number of different ways and present each of these within a different PivotItem element. Of course, you are going to have to start by sorting the participants into a number of collections that you can data bind to:

public class SortedParticipants
{
    public string Name { get; set; }
    public ObservableCollection<Participant> Participants 
           { get; private set; }
}

public SortedParticipants[] SortedParticipantLists { get; private set; } 
public MVVMPageViewModel(IRepository repository)
{
    Participants = new ObservableCollection<Participant>();

    SortedParticipantLists = new []{
 new SortedParticipants(){Name="Name", Participants = new ObservableCollection<Participant>()}, 
 new SortedParticipants(){Name="Name Desc", Participants = new ObservableCollection<Participant>()}, 
 new SortedParticipants(){Name="Since", Participants = new ObservableCollection<Participant>()}, 
 new SortedParticipants(){Name="Since Desc", Participants = new ObservableCollection<Participant>()}                                        
       };

    repository.CurrentParticipantsCompleted +=
              repository_CurrentParticipantsCompleted;
    repository.CurrentParticipantsAsync();
}

void repository_CurrentParticipantsCompleted(object sender, 
     ParameterEventArgs<Participant[]> e) {
    Array.ForEach(e.Parameter.OrderBy(p=>p.Name).ToArray(),
                  p=>SortedParticipantLists[0].Participants.Add(p) );
    Array.ForEach(e.Parameter.OrderByDescending(p => p.Name).ToArray(), 
                  p => SortedParticipantLists[1]. Participants.Add(p));
    Array.ForEach(e.Parameter.OrderBy(p => p.MemberSince).ToArray(), 
                  p => SortedParticipantLists[2]. Participants.Add(p));
    Array.ForEach(
          e.Parameter.OrderByDescending(p => p.MemberSince).ToArray(), 
          p => SortedParticipantLists[3]. Participants.Add(p));
}

Return to Expression Blend and rework the interface for the MVVMPage, this time with the Pivot control. Start by removing the Panorama control from the previous section. Locate the Pivot control in the Assets window, and drag it onto the design surface. If necessary, reset the layout so that it takes up the full screen. (You can easily do this by right-clicking the control in the Objects and Timeline window and selecting Auto SizeFill.)

The Pivot control you’ve created already has two PivotItem elements within it. Rather than simply add two more PivotItem controls, which would give you one for each sort order you just created, use data binding to create the PivotItem controls automatically. To do this, select the box next to the ItemsSource property for the Pivot control. This brings up the Create Data Binding dialog box shown at the right in Figure 17, where you can select the SortedParticipantLists node and click OK.

Figure 17: Data Binding the Pivot Control

Referenced Image

At this point, you might think that you’ve done the wrong thing because both the header and the pivot items themselves are displaying WindowsPhoneData.MVVMPageViewModel+SortedParticipant. This is actually correct; the text that is being displayed is simply the type name that each item is being data bound to. You now need to adjust the templates to get the participants to display correctly.

You need to adjust two templates. The first is for the header, which displays the sort order for each Participant list. Right-click the Pivot control, and select Edit Additional TemplatesEdit HeaderTemplateCreate Empty. Give the new template a name, and click OK to create the new template. Delete the Grid element that is created by default and replace it with a TextBlock element. Unfortunately the HeaderTemplate element for the Pivot control is slightly broken in the design experience for Expression Blend, which means you cannot use the designer to wire up the data binding. Instead, you need to adjust the XAML to set up the data binding by hand.

<DataTemplate x:Key="SortedHeaderTemplate">
    <TextBlock TextWrapping="Wrap" Text="{Binding Name}"/>
</DataTemplate>

The other template that you need to modify is the body of the PivotItem element. Right-click the Pivot control, and select Edit Additional TemplatesEdit Generated Items (ItemTemplate)Create Empty. Remember that at this point you’re configuring the template for each SortedPariticipants object, which has a Participants property that contains the sorted list of participants. Replace the default Grid element with a ListBox element. Select the ListBox element, and click the box next to the ItemsSource property. In the Create Data Binding window, select the Participants node and click OK. The only thing left to do is to adjust the ItemTemplate element for the ListBox control to control how the Participant objects are displayed. Right-click the ListBox control, and select Edit Additional TemplateEdit Generated Items (ItemTemplate)Apply Resource. You should see any of the templates you created earlier. Select the one that gives you the best layout for a Participant object.

When you run this example, you should be able to scroll between the items within the Pivot control and see the Participant objects sorted in different orders, such as in Figure 18.

Figure 18: The Pivot in Action

Referenced Image

The final XAML for the Pivot control should resemble the following:

<controls:Pivot Title="pivot" ItemsSource="{Binding SortedParticipantLists}">
    <controls:Pivot.Resources>
        <DataTemplate x:Key="SortedHeaderTemplate">
            <TextBlock TextWrapping="Wrap" Text="{Binding Name}"/>
        </DataTemplate>
        <DataTemplate x:Key="PivotItemTemplate">
            <ListBox 
             ItemTemplate="{StaticResource ParticipantSimpleTemplate}" 
             ItemsSource="{Binding Participants}"/>
        </DataTemplate>
    </controls:Pivot.Resources>
    <controls:Pivot.ItemTemplate>
        <StaticResource ResourceKey="PivotItemTemplate"/>
    </controls:Pivot.ItemTemplate>
    <controls:Pivot.HeaderTemplate>
        <StaticResource ResourceKey="SortedHeaderTemplate"/>
    </controls:Pivot.HeaderTemplate>
</controls:Pivot>

Previous Article: Storing Data with Isolated Storage and LINQ to SQL

Continue on to the Next Article: Saving Data to the Web

Show:
© 2014 Microsoft