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 string
s 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 string
s 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.
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:
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.
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:
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