Back to CourseIntermediate7 min read
WPF /

Data Templates

Design how each item looks with DataTemplate. Build rich CoinJar list rows, reuse templates as resources, apply them by type, change the item panel and pick templates with a DataTemplateSelector.

Bishal Biswas

Bishal Biswas

Self Learner

Published on: October 25, 2026
Intermediate Topic

One line of text is not enough

In Binding to Collections the CoinJar list showed each expense with DisplayMemberPath="Summary". That gives one line of plain text per row. A good expense row wants more: a coloured dot for the category, the title in bold, the date in grey underneath, and the amount lined up on the right.

A data template is the answer. It is a small piece of XAML that describes how to draw one data object. You give it to a list, and the list stamps out a copy of the template for every item, each one bound to its own item.

Think of a cookie cutter: you design the shape once, and every cookie comes out the same shape with different sprinkles.

Your first ItemTemplate

Every list control has an ItemTemplate property. Put a DataTemplate inside it:

<ListBox ItemsSource="{Binding Expenses}"
         SelectedItem="{Binding SelectedExpense}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Title}" FontWeight="SemiBold" />
                <TextBlock Text="{Binding Amount, StringFormat=C}" Margin="8,0,0,0" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The key idea: inside a template, the DataContext is the item. {Binding Title} means “the Title of the expense this row is showing”, not the view model. WPF sets this up for every row automatically.

Remove DisplayMemberPath when you add an ItemTemplate. Setting both throws an error, because they both try to decide how an item looks.

A proper expense row

Now let us build a real row with a Grid from The Grid Panel and the converter from Value Converters:

<DataTemplate>
    <Grid Margin="0,4">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />  <!-- dot -->
            <ColumnDefinition Width="*" />     <!-- title and details -->
            <ColumnDefinition Width="Auto" />  <!-- amount -->
        </Grid.ColumnDefinitions>

        <Border Width="12" Height="12" CornerRadius="6" Margin="0,0,10,0"
                VerticalAlignment="Center"
                Background="{Binding Category, Converter={StaticResource CategoryBrush}}" />

        <StackPanel Grid.Column="1">
            <TextBlock Text="{Binding Title}" FontWeight="SemiBold" />
            <TextBlock Foreground="Gray" FontSize="11">
                <Run Text="{Binding Category}" />
                <Run Text=" · " />
                <Run Text="{Binding Date, StringFormat='d MMM yyyy'}" />
            </TextBlock>
        </StackPanel>

        <TextBlock Grid.Column="2" Text="{Binding Amount, StringFormat=C}"
                   VerticalAlignment="Center" FontSize="14" />
    </Grid>
</DataTemplate>

A Run is a piece of text inside a TextBlock. Using several runs lets you bind different parts of one line to different properties.

Make the row fill the width

Run this and the amounts are not lined up on the right. They sit right after the title. Each row is only as wide as its content, so the * column has no spare space to fill.

The fix is one property on the list:

<ListBox ItemsSource="{Binding Expenses}"
         HorizontalContentAlignment="Stretch">

HorizontalContentAlignment="Stretch" tells each row to take the full width of the list. Now the * column grows and the amount column is pushed to the right edge. Nearly everyone hits this the first time.

Templates as reusable resources

A template written inside one ListBox can only be used there. Move it into the window’s resources with an x:Key and you can use it anywhere:

<Window.Resources>
    <conv:CategoryToBrushConverter x:Key="CategoryBrush" />

    <DataTemplate x:Key="ExpenseRowTemplate">
        <!-- the Grid from above -->
    </DataTemplate>
</Window.Resources>

<ListBox ItemsSource="{Binding Expenses}"
         ItemTemplate="{StaticResource ExpenseRowTemplate}" />

Order matters: StaticResource looks for resources defined earlier. Declare the converter before the template that uses it, and both before the controls. You will see more about this in Resources and Resource Dictionaries.

Templates chosen by type

You can also tell WPF: “whenever you need to show an Expense, anywhere, use this template”. Set DataType and leave out x:Key:

<Window xmlns:models="clr-namespace:CoinJar.Models" ...>
    <Window.Resources>
        <DataTemplate DataType="{x:Type models:Expense}">
            <Border Padding="12" Background="WhiteSmoke" CornerRadius="6">
                <StackPanel>
                    <TextBlock Text="{Binding Title}" FontSize="18" FontWeight="SemiBold" />
                    <TextBlock Text="{Binding Amount, StringFormat=C}" FontSize="24" />
                </StackPanel>
            </Border>
        </DataTemplate>
    </Window.Resources>

This is an implicit data template. It applies by itself wherever an Expense is shown as content and no other template was chosen. For example:

<ContentControl Content="{Binding SelectedExpense}" />

A ContentControl is a simple control that shows one piece of content. Give it an Expense and it finds the implicit template and draws the card. Select another row and the card updates. When SelectedExpense is null, the control shows nothing.

If you add x:Key to a template that has DataType, it stops being implicit and is only used where you ask for it by key.

Implicit templates become very powerful in Navigation Between Views, where WPF uses them to show the right screen for each view model.

ContentTemplate for single items

ItemTemplate is for lists. For one piece of content, controls like ContentControl, Button and Label have ContentTemplate:

<ContentControl Content="{Binding SelectedExpense}"
                ContentTemplate="{StaticResource ExpenseRowTemplate}" />

This reuses the row template for a single item. Same template, two places.

Changing the panel with ItemsPanel

A list stacks its items from top to bottom because it uses a StackPanel (actually a faster cousin called VirtualizingStackPanel) inside. You can swap that panel with ItemsPanel. Here is a row of category “chips” that wraps onto new lines:

<ItemsControl ItemsSource="{Binding Categories}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Border Background="{Binding Converter={StaticResource CategoryBrush}}"
                    CornerRadius="10" Padding="10,2" Margin="0,0,6,6">
                <TextBlock Text="{Binding}" Foreground="White" />
            </Border>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Two new things here:

  • ItemsControl is the plainest list control. It shows items but has no selection and no scroll bar. It is perfect for display only lists.
  • {Binding} with no path means “the item itself”. Each item is a plain string like “Food”, so there is no property to name.

ItemTemplate decides how each item looks. ItemsPanel decides how the items are arranged. Later, Control Templates decide how the control itself looks. These three are easy to mix up, so it helps to keep the list in mind.

Picking a template in code: DataTemplateSelector

Sometimes different items need different templates. CoinJar could show big expenses with a warning style. A DataTemplateSelector is a class that looks at each item and returns the template to use:

using System.Windows;
using System.Windows.Controls;
using CoinJar.Models;

namespace CoinJar.Views;

public class ExpenseTemplateSelector : DataTemplateSelector
{
    public DataTemplate? NormalTemplate { get; set; }
    public DataTemplate? LargeTemplate { get; set; }

    public override DataTemplate? SelectTemplate(object item, DependencyObject container)
    {
        if (item is Expense expense && expense.Amount >= 50m)
            return LargeTemplate;
        return NormalTemplate;
    }
}

Declare it in resources, fill in its two templates, and hand it to the list with ItemTemplateSelector:

<views:ExpenseTemplateSelector x:Key="ExpenseSelector"
    NormalTemplate="{StaticResource ExpenseRowTemplate}"
    LargeTemplate="{StaticResource LargeExpenseRowTemplate}" />

<ListBox ItemsSource="{Binding Expenses}"
         ItemTemplateSelector="{StaticResource ExpenseSelector}" />

One limit to know: the selector runs when a row is created. If the user later edits an amount from 20 to 80, the row keeps its old template. For looks that follow live changes, a data trigger inside one template is better. You will learn those in Property, Data and Event Triggers.

Reaching outside the template

Inside a template the DataContext is the item. Sometimes a row needs something from the view model instead, for example a list of categories for a ComboBox inside the row. Climb up with RelativeSource, just like in the previous lessons:

<ComboBox ItemsSource="{Binding DataContext.Categories,
                        RelativeSource={RelativeSource AncestorType=ListBox}}"
          SelectedItem="{Binding Category}" />

The ListBox itself still has the view model as its DataContext, so DataContext.Categories finds the list.

A word about TreeView

For data with levels, such as categories that contain expenses, WPF has HierarchicalDataTemplate. It is a data template with an extra ItemsSource that says where the children are, and a TreeView uses it to draw each level. The ideas are the same as in this lesson, just one level deeper.

Common mistakes

  • Setting both DisplayMemberPath and ItemTemplate. WPF throws an error. Use one.
  • Rows that do not fill the width. Add HorizontalContentAlignment="Stretch" to the list.
  • Bindings in a template that point at the view model. Inside a template, {Binding X} reads from the item. Use RelativeSource to reach outside.
  • An implicit template that is never used. Check the DataType and the xmlns prefix, and make sure there is no x:Key.
  • Using a resource before it is declared. StaticResource only sees resources above it. Move the converter and templates to the top.
  • Very heavy templates in long lists. Every row creates all the elements in the template. Keep rows simple, and read Performance and UI Virtualization before showing thousands of items.

Quick recap

  • A DataTemplate describes how to draw one data object. Inside it, the DataContext is the item.
  • ItemTemplate styles every item of a list. ContentTemplate styles a single piece of content.
  • Add HorizontalContentAlignment="Stretch" so rows fill the list’s width.
  • Keyed templates in Resources can be reused. Templates with DataType and no key apply automatically by type.
  • ItemsPanel changes how items are arranged, for example a WrapPanel.
  • DataTemplateSelector picks a template per item in code, once per row.
  • Next, show many columns of data with sorting and editing in The DataGrid Control.
MainWindow.xaml

<!-- MainWindow.xaml -->
<!-- ExpenseListViewModel, Expense, MainWindow.xaml.cs and the converters stay the same as in the previous two lessons -->
<Window x:Class="CoinJar.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:conv="clr-namespace:CoinJar.Converters"
        xmlns:models="clr-namespace:CoinJar.Models"
        xmlns:views="clr-namespace:CoinJar.Views"
        Title="CoinJar" Width="720" Height="480">
    <Window.Resources>
        <!-- 1. Converters first, so the templates below can use them -->
        <conv:CategoryToBrushConverter x:Key="CategoryBrush" />
        <!-- 2. Normal row: dot, title, category and date, amount on the right -->
        <DataTemplate x:Key="ExpenseRowTemplate">
            <Grid Margin="0,4">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <Border Width="12" Height="12" CornerRadius="6" Margin="0,0,10,0" VerticalAlignment="Center"
                        Background="{Binding Category, Converter={StaticResource CategoryBrush}}" />
                <StackPanel Grid.Column="1">
                    <TextBlock Text="{Binding Title}" FontWeight="SemiBold" />
                    <TextBlock Foreground="Gray" FontSize="11">
                        <Run Text="{Binding Category}" />
                        <Run Text=" · " />
                        <Run Text="{Binding Date, StringFormat='d MMM yyyy'}" />
                    </TextBlock>
                </StackPanel>
                <TextBlock Grid.Column="2" Text="{Binding Amount, StringFormat=C}"
                           VerticalAlignment="Center" FontSize="14" />
            </Grid>
        </DataTemplate>
        <!-- 3. Large row: same idea, with a warning colour -->
        <DataTemplate x:Key="LargeExpenseRowTemplate">
            <Border Background="#FFF4E5" CornerRadius="4" Padding="4">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <StackPanel>
                        <TextBlock Text="{Binding Title}" FontWeight="Bold" />
                        <TextBlock Text="Large expense" Foreground="DarkOrange" FontSize="11" />
                    </StackPanel>
                    <TextBlock Grid.Column="1" Text="{Binding Amount, StringFormat=C}"
                               Foreground="Firebrick" FontWeight="Bold" FontSize="14" VerticalAlignment="Center" />
                </Grid>
            </Border>
        </DataTemplate>
        <!-- 4. The selector picks one of the two templates per row -->
        <views:ExpenseTemplateSelector x:Key="ExpenseSelector"
                                       NormalTemplate="{StaticResource ExpenseRowTemplate}"
                                       LargeTemplate="{StaticResource LargeExpenseRowTemplate}" />
        <!-- 5. Implicit template: used wherever an Expense is shown as content -->
        <DataTemplate DataType="{x:Type models:Expense}">
            <Border Padding="12" Background="WhiteSmoke" CornerRadius="6">
                <StackPanel>
                    <TextBlock Text="{Binding Title}" FontSize="18" FontWeight="SemiBold" />
                    <TextBlock Text="{Binding Amount, StringFormat=C}" FontSize="24" />
                    <TextBlock Text="{Binding Date, StringFormat='dddd d MMMM'}" Foreground="Gray" />
                </StackPanel>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <DockPanel Margin="12">
        <!-- Category chips in a WrapPanel -->
        <ItemsControl DockPanel.Dock="Top" ItemsSource="{Binding Categories}" Margin="0,0,0,8">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border Background="{Binding Converter={StaticResource CategoryBrush}}"
                            CornerRadius="10" Padding="10,2" Margin="0,0,6,6">
                        <TextBlock Text="{Binding}" Foreground="White" />
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Margin="0,8,0,0">
            <Button Content="Add" Click="Add_Click" Padding="12,4" />
            <Button Content="Delete" Click="Delete_Click" Padding="12,4" Margin="8,0,0,0" />
            <TextBlock Text="{Binding Total, StringFormat='Total: {0:C}'}" FontWeight="SemiBold"
                       VerticalAlignment="Center" Margin="16,0,0,0" />
        </StackPanel>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="3*" />
                <ColumnDefinition Width="2*" />
            </Grid.ColumnDefinitions>
            <!-- The list: rows stretch to full width, templates picked per row -->
            <ListBox ItemsSource="{Binding Expenses}"
                     SelectedItem="{Binding SelectedExpense}"
                     HorizontalContentAlignment="Stretch"
                     ItemTemplateSelector="{StaticResource ExpenseSelector}" />
            <!-- The detail side: a card from the implicit template, then the edit form -->
            <StackPanel Grid.Column="1" Margin="12,0,0,0">
                <ContentControl Content="{Binding SelectedExpense}" />
                <StackPanel DataContext="{Binding SelectedExpense}" Margin="0,12,0,0">
                    <TextBlock Text="Title" />
                    <TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}" />
                    <TextBlock Text="Amount" Margin="0,8,0,0" />
                    <TextBox Text="{Binding Amount}" />
                    <TextBlock Text="Category" Margin="0,8,0,0" />
                    <ComboBox ItemsSource="{Binding DataContext.Categories, RelativeSource={RelativeSource AncestorType=Window}}"
                              SelectedItem="{Binding Category}" />
                </StackPanel>
            </StackPanel>
        </Grid>
    </DockPanel>
</Window>
// Views/ExpenseTemplateSelector.cs
using System.Windows;
using System.Windows.Controls;
using CoinJar.Models;
namespace CoinJar.Views;
// Chooses a row template for each expense when the row is created
public class ExpenseTemplateSelector : DataTemplateSelector
{
    public DataTemplate? NormalTemplate { get; set; }
    public DataTemplate? LargeTemplate { get; set; }
    public override DataTemplate? SelectTemplate(object item, DependencyObject container)
    {
        if (item is Expense expense && expense.Amount >= 50m)
            return LargeTemplate;
        return NormalTemplate;
    }
}

Was this explanation helpful?

Your feedback helps us improve the content for everyone.