Introduction
If you're either learning WPF or are using WPF extensively, you will without doubt have the need for DataTemplates. DataTemplates allow
the developer to define how and what is bound and rendered in the User Interface from the underlying application objects.
They are, to put it plainly, ubiquitous when it comes to displaying data in a WPF User Interface.
When you have a collection of objects that are to be shown in the User Interface in a logical and common way,
a DataTemplate is the method of choice to define the look and feel of those elements once they are rendered in the UI.
They fuel consistency, and are intrinsically reusable. The demo source code included with this article is very simple,
but if you combine these ideas with other possibilities, such as value converters, you will soon find that you have a very powerful set of UI tools at your disposal.
There are many, many sources of information on The Code Project and the Internet, in general that, can explain the general
DataTemplate principals better than I. The best place to start in order to learn some of the fundamentals of defining and using
DataTemplates will be the MSDN documentation that can be found here: MSDN - DataTemplates.
Inline Templates
DataTemplates are often defined 'inline', meaning that they are constructed within an items control XAML structure in the main 'consuming' XAML file, such as:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Or, they are often contained within the Resources
section of a Window
, Page
, or Control
, such as:
<Window.Resources>
<DataTemplate x:Key="myTaskTemplate">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
Alternatives
One other method is possible which doesn't actually seem to get much press. To be honest, I personally feel the method I'm covering here
is most 'agreeable' in relation to the whole Designer paradigm that WPF has thrown at us. You can use UserControls as DataTemplates!
This is such a neat solution that I sat down to put together this little demo of how this is done so that others can get an easy look at the idea and start using this method.
One point that is continually made about WPF is its ability to allow 'loose coupling' of application logic and User Interface
implementations. By making UserControls that are designed to be used as DataTemplates, you can really take this further and have a clean
separation between the main Window
or Page
XAML and the DataTemplates. Anyway, let's look at some code!!
Using the Code
For this demo, I started off by creating a normal Visual Studio 2008 solution. Then, I defined a DataItem
class to hold some data to be shown in the UI:
public sealed class DataItem
{
public string Name { get; set; }
public int Age { get; set; }
public double Progress { get; set; }
public DataItem(string name, int age, double progress)
{
Name = name;
Age = age;
Progress = progress;
}
}
Now that we have a simple object defined that we want to display in the UI, we need to setup the consumption of this object
and populate it with some data. To do this, I simply defined an ObservableCollection
in the window and created a method that stuffed
in some DataItem
objects into the collection:
ObservableCollection<DataItem> items = new ObservableCollection<DataItem>();
Within the main window code-behind file, I added this method:
void PopulateDataItems()
{
items.Add(new DataItem("Jammer", 32, 10.0));
items.Add(new DataItem("John", 44, 20.0));
items.Add(new DataItem("Jane", 25, 30.0));
items.Add(new DataItem("Robert", 30, 40.0));
items.Add(new DataItem("Jezzer", 50, 50.0));
items.Add(new DataItem("James", 40, 60.0));
items.Add(new DataItem("Rebecca", 25, 70.0));
items.Add(new DataItem("Mark", 35, 80.0));
items.Add(new DataItem("Leah", 20, 90.0));
items.Add(new DataItem("WallE", 700, 100.0));
}
Done! We now have a window code-behind that defines a collection and adds the correct objects to that collection ready
for display in the UI. The next thing to look at is the XAML that uses all of this WPF goodness.
First of all, I created a UserControl (ProgressReporter.xaml) to display the Progress
property of the DataItem
class:
<UserControl
x:Class="UserControlAsDataTemplateDemo.UserControls.ProgressReporter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="ProgressControl"
Height="Auto" Width="Auto">
<Grid>
<ProgressBar x:Name="UIReporter"
Value="{Binding PercentToShow, ElementName=ProgressControl}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Style="{DynamicResource ProgressReporterStyle}" />
</Grid>
</UserControl>
The code-behind for this control also defines a custom DependencyProperty
that the ProgressBar
uses to obtain its value from:
public static DependencyProperty PercentProperty =
DependencyProperty.Register("PercentToShow",
typeof(double), typeof(ProgressReporter));
public double PercentToShow
{
get { return (double)GetValue(PercentProperty); }
set { SetValue(PercentProperty, value); }
}
Next up is the UserControl that is going to be used as the DataTemplate.
<UserControl
x:Class="UserControlAsDataTemplateDemo.UserControls.ItemTemplateControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UserControls="clr-namespace:UserControlAsDataTemplateDemo.UserControls"
Height="Auto" Width="Auto">
<Border BorderThickness="2,2,2,2"
CornerRadius="5,5,5,5"
Background="#FF626262"
BorderBrush="#FFFFAC00"
Grid.ColumnSpan="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Text="{Binding Path=Name}"
TextWrapping="Wrap"
Margin="4,4,4,4"
Grid.ColumnSpan="1"/>
<TextBox HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Text="{Binding Path=Age}"
TextWrapping="Wrap"
Grid.Column="1"
Margin="4,4,4,4"
Grid.ColumnSpan="1"/>
<UserControls:ProgressReporter
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
PercentToShow="{Binding Path=Progress}"
Grid.Column="2" Margin="4,4,4,4" />
</Grid>
</Border>
</UserControl>
The main points to note here are the Binding settings. You can see that the text boxes are bound to the Name
and Age
properties
of the DataItem
class, and that the custom DependencyProperty
called PercentToShow
is used to bind the double
property
Progress
from the DataItem
class to the ProgressReporter
control.
The image below shows the DataTemplate UserControl in Expression Blend:
Now that we have the 'DataTemplate' UserControl defined, the only thing left to do is write the XAML in the MainWindow that will
use this control as a DataTemplate. The XAML for this looks like:
<ListBox x:Name="peopleListBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemContainerStyle="{StaticResource ListBoxItemStretch}"
Foreground="Transparent"
BorderBrush="Transparent"
Background="Transparent"
Grid.ColumnSpan="2">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<UserControls:ItemTemplateControl Margin="4" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So, when you run the application and hit the [Load Data] button, it uses the PopulateDataItems()
method and then binds
the ObservableCollection
'items' to the listbox and renders each DataItem
using our UserControl:
private void btnLoadData_Click(object sender, RoutedEventArgs e)
{
items.Clear();
PopulateDataItems();
peopleListBox.ItemsSource = items;
}
Adding in Dynamic Behaviour using Code-Behind
Once you have defined and used your custom UserControl as a DataTemplate, you will more than likely wish to make them a bit smarter and more WPF'y.
There are a variety of ways to do this through the use of mechanisms like Triggers, DataTriggers, and Value Converters.
One thing that we can now do in addition to this is simply use the C# code-behind file to build in some dynamic behaviour into the DataTemplate.
You will find the complete source for this dynamic example in the included source file at the top of this article.
In order to fully show this dynamic behaviour, I have made a few changes to the data creation methods used in the original sample;
I have randomised the progress value being used in the DataItem
s by doing this:
private void PopulateDataItems()
{
items.Add(new DataItem("Jammer", 32, MakeRandomDouble()));
items.Add(new DataItem("John", 44, MakeRandomDouble()));
items.Add(new DataItem("Jane", 25, MakeRandomDouble()));
items.Add(new DataItem("Robert", 30, MakeRandomDouble()));
items.Add(new DataItem("Jezzer", 50, MakeRandomDouble()));
items.Add(new DataItem("James", 40, MakeRandomDouble()));
items.Add(new DataItem("Rebecca", 25, MakeRandomDouble()));
items.Add(new DataItem("Mark", 35, MakeRandomDouble()));
items.Add(new DataItem("Leah", 20, MakeRandomDouble()));
items.Add(new DataItem("WallE", 700, MakeRandomDouble()));
}
private double MakeRandomDouble()
{
int randomnumber = _random.Next(1, 100);
return (double)randomnumber;
}
To drive the dynamic behaviour that will react to this random data, I have added in code into the Loaded
event of the ItemTemplateControl
:
Loaded="UserControl_Loaded"
The Loaded
event is fired after the data binding has taken place and after Layout has been run on the element.
This means we have access to the actual data values in order to react accordingly to these values and make the changes we want
too just before display in the UI. The code in this event handler is as follows:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
if (Convert.ToInt32(this.AgeTextBox.Text) > 40)
{
SolidColorBrush brush = new SolidColorBrush();
brush.Color = Colors.DarkGray;
MainBorder.Background = brush;
}
if (this.ProgressReporter.PercentToShow > 80.0)
{
SolidColorBrush brush = new SolidColorBrush();
brush.Color = Colors.Lavender;
MainBorder.Background = brush;
}
Animations.Fade(this, 0.0, (ProgressReporter.PercentToShow / 100), 1000);
}
This is all fairly straightforward stuff. One major point to make here is that we have given some controls names using x:Name="";
this allows us to easily access these controls in the associated class. We are reacting to the numeric
value of the AgeTextBox.Text
and the PercentToShow
value of the ProgressReporter
. In each case, we are then creating
a new SolidColorBrush
object and applying that to the background color of the MainBorder
object in order to highlight the control
in the UI based on the bound values. You will also note that we are using an extension method that wraps a DoubleAnimation
called Fade();
.
In order to add in some more WPFness, I have created a new static class called Animations
, that looks like this:
public static class Animations
{
static Animations()
{
}
public static void Fade(this UIElement control,
double sourceOpacity, double targetOpactity, int milliseconds)
{
control.BeginAnimation(UIElement.OpacityProperty,
new DoubleAnimation(sourceOpacity,targetOpactity,
new Duration(TimeSpan.FromMilliseconds(milliseconds))));
}
}
And there we go, we have now added in some pretty cool dynamic behaviour using just the code-behind of a UserControl.
This could be massively expanded on, but would over complicate the example.
It's a Wrap(per)
One of the main benefits of this approach from my point of view is that when designing DataTemplates, it's easy to feel disconnected
from the actual design process. By using a UserControl as a DataTemplate, you are afforded the full design flow of using Expression Blend
to write the XAML for the DataTemplate. Personally, I find that to be a compelling reason to use this approach for most DataTemplate
designs, unless you are using a very basic design combined with predefined styles for any elements within that DataTemplate you may need.
Either way, I hope this little demo has been useful and that you find some uses for this approach. This is merely skimming the potential!
History
- 23 July 2008 - Initial version.
- 08 August 2008 - Added in Dynamic Code examples.