Click here to Skip to main content
Click here to Skip to main content

Applying Data Templates Dynamically by Type in WP7

, 19 Oct 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
This article presents a way to apply data templates dynamically by type in the Windows Phone 7 platform

Introduction

The article will present a way to dynamically apply data templates by type on the Windows Phone 7 platform. The article will present the current inconvenience with data templates in WP7 and then it will present a solution to this inconvenience. The solution also makes use of the advantages provided by the MVVM pattern. The MVVM framework used in the sample is MVVM Light.

Introducing Data Templates

A data template is a piece of XAML markup that is used to specify the way a data bound object will be displayed in the UI. In WPF, Silverlight and WP7 data templates can be applied on 2 different types of controls. They can be applied to content controls through the ContentTemplate property or they can be applied to items controls through the ItemTemplate property. In the case of the items controls, the data template will be used to display each item in the collection of elements that has been supplied through the ItemsSource property.

If, for example we have a list of strings that we want to display in a UI ListBox control, all we need to do is set that control’s ItemsSource property. The ListBox will automatically know how to display strings and we get what we want. The XAML code for this can be seen in the listing below.

 <Grid Grid.Row="1" x:Name="ContentGrid">
       <ListBox ItemsSource="{Binding Path=Items}"
                ></ListBox>
 </Grid> 

The code above will have the following effect when run on the phone.

normalList.png

Things get a little more complicated if you have a list of complex objects and you need to display some of the properties of each object. This is where data templates are very useful. Say that we had a list of Customer objects that we wanted to display. Let’s also say that for each Customer we want to display the first and last names. To do this, we will need a data template. The data template we will use can be seen in the listing below:

 <phone:PhoneApplicationPage.Resources>
        <DataTemplate x:Key="custTemplate">
            <StackPanel >
                <TextBlock Text="{Binding LastName}"
                           FontSize="30" Foreground="Red"></TextBlock>
                <TextBlock Text="{Binding FirstName}"
                           FontSize="25" Foreground="Green"
                           Margin="10,0,0,0"></TextBlock>
            </StackPanel>
        </DataTemplate>
    </phone:PhoneApplicationPage.Resources>

The first thing to note in the listing above is that the data template is added as a page resource. Another thing to note is that this data template specifies that the last name will be displayed on top of the first name and that it will be displayed with a larger font size and also with a different color. Like I said before, in the case of an items control, the data template will be applied to the ItemTemplate property of the items control. This can be seen in the listing below.

<ListBox ItemsSource="{Binding Path=Items}" Grid.Row="1" Margin="5"
                 ItemTemplate="{StaticResource custTemplate}">
        </ListBox> 

The effect of this code can be seen in the image below:

simpleDataTemplate.png

Data Templates in WPF vs WP7

Unlike in the related platforms (WPF and Silverlight), data templates in WP7 are very limited. In WPF and Silverlight, data templates are very flexible. They can be defined in place or as resources. They can also be applied manually or automatically by type. On these platforms, data templates also support triggers. An example of a relatively complex data template written for WPF can be seen in the listing below:

<Window.Resources>
   <DataTemplate DataType="{x:Type loc:Employee}">
     <StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding LastName}"/>
        <TextBlock Text="{Binding FirstName}"/>                                           
</StackPanel>
<TextBlock Text="{Binding Path=Salary}" x:Name="sal">
</TextBlock>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Salary}"  Value="90000">
<Setter TargetName="sal" Property="Foreground" Value="Red"/>
<Setter TargetName="sal" Property="FontWeight" Value="Bold"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>    
</Window.Resources> 

The first thing to notice here is that the data template is added as a resource. This allows the data template to be defined once and be used in multiple places. The second thing to notice is that the template only sets the DataType property. There is nothing here about the Key property. This allows the framework to automatically apply this template anywhere there is a need to display the objects for which it is defined. The listing below presents the ListBox that will be used to display the elements:

<ListBox ItemsSource="{Binding Path=Items}"
                 HorizontalContentAlignment="Stretch"
                 ></ListBox>

As you can see, there is no mention here of a data template. The data template will be applied automatically. This can be seen in the image below. You can see that only the entries with a salary of 9000 have the red foreground.

complexDataTemplate.png

In the WP7 platform, data templates aren’t so advanced. In WP7, we can define data templates in place or as resources. But this is about it. There are no triggers and there is no possibility of applying the data templates automatically by type. This means that when adding data templates as resources, we must specify the x:Key property. An example of such a data template can be seen in the listing below:

 <phone:PhoneApplicationPage.Resources>
        <DataTemplate x:Key="custTemplate">
            <StackPanel>
                <TextBlock Text="{Binding LastName}"/>
                <TextBlock Text="{Binding FirstName}"
                           Margin="10,0,0,0"/>
            </StackPanel>
        </DataTemplate>
    </phone:PhoneApplicationPage.Resources> 

To apply this template to a list box, we need to set the ItemTemplate property by using the StaticResource markup expression. This can be seen in the listing below:

<ListBox ItemsSource="{Binding Path=Items}"
 ItemTemplate="{StaticResource custTemplate}">
</ListBox> 

The Problem

The lack of options in the WP7 data templates severely limits a developer’s capabilities. In a real world application, there is often the need to display data of different types in the same control, be it a content control or an items control. Since the data that will be displayed is selected at runtime, not being able to dynamically change the data templates presents a problem. Let’s say for example that we have a list of options and based on the selected option, a different object will be displayed in another control. This is clearly a case where dynamically applying data templates will solve the problem immediately. But since WP7 doesn’t natively support this option for data templates, this scenario is somewhat difficult to solve.

The Solution

The solution to the data template problem is to find a way to return a data template based on the type of the selected item and to apply that data template to the control that is to display that item. The data template is retrieved by using the DataTemplateSelector class. This is a static class that has a single static method. The code for this class can be seen in the listing below:

 public static class DataTemplateSelector
    {
        public static DataTemplate GetTemplate(ViewModelBase param)
        {
            Type t = param.GetType();
            return App.Current.Resources[t.Name] as DataTemplate;
        }
    } 

As you can see from the code above, there is a single static method. The method will get the selected item as an argument, will retrieve the item type and based on the type name, it will return the appropriate data template. The solution also takes advantage of the convention over configuration design paradigm. The only convention here is that the data templates must have keys with the same name as the type name of the type they need to display. An example of this can be seen in the listing below:

 <DataTemplate x:Key="FirstViewModel">
            <v:FirstView></v:FirstView>
        </DataTemplate> 

As you can see, this data template’s key is “FirstViewModel”. This means that this data template will be used to display view models of type FirstViewModel. This is what the GetTemplate() method does. Based on the type of the argument, it returns a data template that has the same key as the name of the type. In the listing above, the FirstView is the user control used for display.

The data template retrieved by this method will be data bound to the ContentTemplate property of the ContentControl that will be used to display the object. This can be seen in the listing below:

 <ContentControl Content="{Binding SelectedItem}"
                            ContentTemplate="{Binding Path=SelectedTemplate}"
                                HorizontalContentAlignment="Stretch"
                                VerticalContentAlignment="Stretch"></ContentControl> 

In the listing above, you can see that both the content and the template are bound to properties in the view model. The SelectedItem property represents the element that is currently selected and that needs to be displayed and the SelectedTemplate is the corresponding data template that was retrieved with the GetTemplate() method. This property that gets the data template can be seen in the listing below:

 public DataTemplate SelectedTemplate
        {
            get 
            {
                if (selItem == null)
                    return null;
                return DataTemplateSelector.GetTemplate(selItem);
            }
        } 

Even though putting UI related code in the ViewModel is in most cases not a good idea, I think it is allowable in this case. This is because there is no actual UI stuff in the property. The templates are defined in XAML and also the property doesn’t actually use UI code that is specifically related to a particular view. It uses a general data template. The last thing to look at is how does the data template changes based on the selected item. This is resolved by the SelectedItem property. The code for this property can be seen in the listing below:

 SelectableViewModel selItem;
        public SelectableViewModel SelectedItem
        {
            get { return selItem; }
            set 
            {
                if (selItem != value)
                {
                    selItem = value;
                    RaisePropertyChanged("SelectedItem");
                    RaisePropertyChanged("SelectedTemplate");
                }
            }
        } 

As you can see from the code above, besides announcing that the SelectedItem property has changed, the code also announces that the SelectedTemplate property has changed. This makes the framework reread the property that exposes the data template. When the new data template is applied, the item is displayed correctly.

The Test Application

To test the solution, I built a simple tabbed UI. Based on the selected tab, the page will show a different user control. Because there is no TabControl in WP7, I will use a combination of a ListBox and a ContentControl. The important part of the page UI can be seen in the listing below:

 <ContentControl Content="{Binding SelectedItem}"
                            ContentTemplate="{Binding Path=SelectedTemplate}"
                                HorizontalContentAlignment="Stretch"
                                VerticalContentAlignment="Stretch"></ContentControl>
            <ListBox ItemsSource="{Binding Path=Items}" Grid.Row="1" Margin="5"
                     SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Path=DisplayName}" FontSize="30"
                                   FontWeight="Bold" Margin="5"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal"
                                    HorizontalAlignment="Center"></StackPanel>
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
            </ListBox> 

You can see from the code above that we have a ContentControl at the top of the page. This content control will be used to display the data based on the selected item in a list. The Content property is data bound to the SelectedItem property in the view model. This will be changed every time a new item is selected from the list. The ContentTemplate property is also data bound, but this time to the SelectedTemplate view model property. This will set the corresponding data template.

Below the content control is a list box. This list box will be used to present the tabs. The tabs will have a horizontal alignment. The tabs names will be bound to the DisplayName property of the items. Also when the selected item in the list box changes, so will the item that is displayed by the content control above.

The image below presents the main screen of the test application:

mainPageUI.png

The mappings between the view models and the views for the 3 options can be seen in the XAML below:

 <DataTemplate x:Key="FirstViewModel">
            <v:FirstView></v:FirstView>
        </DataTemplate>
        <DataTemplate x:Key="SecondViewModel">
            <v:SecondView></v:SecondView>
        </DataTemplate>
        <DataTemplate x:Key="ThirdViewModel">
            <v:ThirdView></v:ThirdView>
        </DataTemplate> 

Removing the DataTemplate from the ViewModel

In order to remove the data template from the view model, the content control that is used to display the content must be changed. One of the options is to derive another control from the content control and override the OnContentChanged() method. This method gets called every time the Content property of the control changes. In my opinion, this is a good place to put the data template selection code. The new control class can be seen in the code below:

    public class DynamicContentControl:ContentControl
    {
        protected override void 
            OnContentChanged(object oldContent, object newContent)
        {
            base.OnContentChanged(oldContent, newContent);
            this.ContentTemplate = 
                DataTemplateSelector.GetTemplate(newContent as ViewModelBase);
        }        
    }  

As you can see from the code above, I first call the base class implementation, then I use the DataTemplateSelector class to set the ContentTemplate property based on the type of the new content.

Using this new control in XAML is pretty straightforward. The code can be seen below:

<loc:DynamicContentControl Content="{Binding SelectedItem}"
                                HorizontalContentAlignment="Stretch"
                                VerticalContentAlignment="Stretch" /> 

As you can see, I' m not binding the ContentTemplate property anymore. Also the SelectedTemplate property from the ViewModel has been removed.

This is it. Hope you like the article. Please feel free to post comments and suggestions.

History

  • Added on September 27, 2010

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Florin Badea
Software Developer
Romania Romania
No Biography provided

Comments and Discussions

 
Questionsimple solution Pinmemberkarolbrandys18-Jul-13 0:33 
AnswerRe: simple solution PinmemberFlorin Badea18-Jul-13 1:20 
GeneralMy vote of 5 PinmvpKanasz Robert21-Sep-12 1:36 
GeneralRe: My vote of 5 PinmemberFlorin Badea22-Sep-12 19:17 
GeneralMy vote of 5 Pinmembervideni15-Jun-12 22:14 
GeneralUI-only way to do it Pinmemberaghiutza18-Oct-10 23:07 
GeneralRe: UI-only way to do it PinmemberBadea Florin18-Oct-10 23:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 19 Oct 2010
Article Copyright 2010 by Florin Badea
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid