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

WPF UserControl == DataTemplate!!!

, 8 Aug 2008
Rate this:
Please Sign up or sign in to vote.
Demos how to use a WPF UserControl as a DataTemplate.

UserControlAsDataTemplate-M.png

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:

/// <summary>
/// A simple class holding a few items of data in various formats
/// </summary>
public sealed class DataItem
{
    public string Name { get; set; }
    public int Age { get; set; }
    public double Progress { get; set; }

    /// <summary>
    /// A parameterised constructor
    /// </summary>
    /// <param name="name"></param>
    /// <param name="age"></param>
    /// <param name="progress"></param>
    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>

UserControl-ProgressReporter.png

The code-behind for this control also defines a custom DependencyProperty that the ProgressBar uses to obtain its value from:

/// <summary>
/// The dependency property of the ProgressReporter
/// UserControl to display in the GUI
/// </summary>
public static DependencyProperty PercentProperty =
          DependencyProperty.Register("PercentToShow",
          typeof(double), typeof(ProgressReporter));

/// <summary>
/// The PercentToShow value
/// </summary>
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:

ItemControltemplate.png

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)
{
    // Reset the Items Collection
    items.Clear();

    // Populate the Items Collection 
    PopulateDataItems();

    // Bind the items Collection to the List Box
    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 DataItems 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;
    }

    // An extension method to create and start an animation
    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.

UserControlDynamicScreenShot.png

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.

License

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

Share

About the Author

Jammer
Chief Technology Officer JamSoft
United Kingdom United Kingdom
Developer and en
Follow on   Twitter

Comments and Discussions

 
QuestionMislabeled post PinmemberPhillip Givens25-Sep-12 18:21 
AnswerRe: Mislabeled post PinmemberJammer26-Sep-12 23:15 
You'll have to explain that one Phillip ...
 
Jammer

GeneralMore than once this helped me out! PinmemberFonzerelli18-Nov-11 22:50 
GeneralRe: More than once this helped me out! PinmemberJammer26-Sep-12 23:15 
GeneralMy vote of 5 PinmemberXanblax31-Jan-11 1:12 
GeneralRe: My vote of 5 PinmemberJammer26-Sep-12 23:16 
Generalalternative text Pinmembermbthe2nd24-Sep-09 6:34 
GeneralRe: alternative text PinmemberJammer25-Sep-09 8:34 
GeneralNice tip! PinmemberSevenate12-Jun-09 4:12 
GeneralRe: Nice tip! PinmemberJammer12-Jun-09 8:48 
QuestionDependencyProperty of UserControl child in databound UserControl not getting updates PinmemberKim Svedmark8-Jun-09 23:21 
AnswerRe: DependencyProperty of UserControl child in databound UserControl not getting updates PinmemberJammer12-Jun-09 8:47 
GeneralWow Super 5/5 Pinmemberprasad0217-Feb-09 16:11 
GeneralUsing ElementName binding inside a DataTemplate Pinmemberchaiguy13372-Dec-08 11:16 
GeneralRe: Using ElementName binding inside a DataTemplate PinmemberJammer2-Dec-08 12:28 
GeneralRe: Using ElementName binding inside a DataTemplate Pinmemberchaiguy13372-Dec-08 12:46 
GeneralCodeBehind DataTemplate PinmemberRNEELY31-Jul-08 0:29 
GeneralRe: CodeBehind DataTemplate PinmemberJammer31-Jul-08 10:33 
GeneralRe: CodeBehind DataTemplate PinmemberJammer11-Aug-08 4:35 
QuestionWhy ProgressReporter User Control? PinmemberRNEELY31-Jul-08 0:15 
AnswerRe: Why ProgressReporter User Control? PinmemberJammer31-Jul-08 10:27 
GeneralGood start man PinmvpSacha Barber25-Jul-08 1:06 
GeneralRe: Good start man PinmemberJammer25-Jul-08 2:06 
GeneralRe: Good start man PinmvpSacha Barber25-Jul-08 2:27 
GeneralRe: Good start man PinmemberJammer25-Jul-08 6:11 
GeneralRe: Good start man PinmvpSacha Barber25-Jul-08 21:11 
GeneralRe: Good start man PinmemberJammer25-Jul-08 22:44 
GeneralRe: Good start man PinmvpSacha Barber26-Jul-08 1:23 
GeneralRe: Good start man PinmemberJammer26-Jul-08 4:03 
GeneralRe: Good start man PinmvpSacha Barber26-Jul-08 23:21 
GeneralGreat PinmemberWes Aday24-Jul-08 8:59 
GeneralRe: Great PinmemberJammer24-Jul-08 10:37 
GeneralExcellent! Pinmemberindyfromoz24-Jul-08 1:15 
GeneralRe: Excellent! PinmemberJammer24-Jul-08 1:38 
GeneralRe: Excellent! PinmemberJammer20-Nov-08 23:16 
GeneralCongratulations on a good first article. PinmvpPete O'Hanlon23-Jul-08 22:59 
GeneralRe: Congratulations on a good first article. PinmemberJammer23-Jul-08 23:23 

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
Web04 | 2.8.140827.1 | Last Updated 8 Aug 2008
Article Copyright 2008 by Jammer
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid