Lesson 29 - WPF - Templates
In the last lesson, Our own control with DependencyProperties in C# .NET WPF , we programmed an example of using DependencyProperties in WPF in our own UserControl.
In today's C# WPF tutorial, we'll focus on templates to achieve even wider
styling options. We can, for example, display images in ListBox
items, etc. At the same time, we'll show a simple example of individual types of
templates. E.g. our WPF ListBox
with icons will look like this:
Templates
In WPF each control has assigned a default template in its
Template
property. These templates ensure the visual appearance of
a control and its behavior. It contains a set of properties specific to a given
control. These properties can be set using keywords in XAML
or in
Code Behind. This is done by styling or using the already mentioned
template.
Templates vs. styling
We already know a lot about styling. So what do we need some templates for? It might be worth mentioning that changing an appearance of a control using styling has certain limitations. It can only change basic control properties, such as background or text color, control size, alignment, etc. Templates provide us with much wider options. We can use them, for example, to change the control's shape (instead of a rectangular button, for example, we make an oval button).
Types of templates
All user interface controls have some appearance and behavior. An appearance is probably clear to us. In terms of behavior, this could be, for example, a button click or a mouseover event. These are behaviors that are activated in response to a given user action. Therefore, there are 2 types of templates:
ControlTemplate
- Customizes an appearanceDataTemplate
- Adjusts the functionality, i.e. what data a control displays
ControlTemplate
-
The appearance template
The control template defines the control's visual appearance. E.g. the
default appearance of a button can be changed by setting the
Template
dependency property. The custom appearance and behavior
settings are defined in tags:
<ControlTemplate>
and
</ControlTemplate>
Example - An oval button
In the following example we'll make an oval button. Let's create a new WPF project.
Defining an appearance
First we'll define an ellipse, its size, fill and using the
ContentPresenter
class we'll display content
(Content
). We add the code below to the
<Window.Resources>
element, which is a subelement of
<Window>
in the application's main XAML file:
<ControlTemplate x:Key="OvalButton" TargetType="Button"> <Grid> <Ellipse x:Name="Oval" Height="100" Width="150" > <Ellipse.Fill> <LinearGradientBrush StartPoint="0,0.2" EndPoint="0.2,1.4"> <GradientStop Offset="0" Color="Red"/> <GradientStop Offset="1" Color="Orange"/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </ControlTemplate>
The {TemplateBinding}
data binding is used in
<ContentPresenter>
. This allows to get values of other
control's properties. E.g. by binding {TemplateBinding Background}
we'd get control's background settings.
This way we have defined the control's appearance and now we'll set control's
behavior using Triggers
.
Triggers
When hovering the mouse over a control, we'll want to change its color and
when it's pressed, its size. We'll extend our appearance code with a new section
right after </Grid>
In the code <Grid>
is
represented only by a comment, you of course keep the code there:
<ControlTemplate x:Key="OvalButton" TargetType="Button"> <Grid> <!-- Here is our Grid code above... --> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="Oval" Property="Fill"> <Setter.Value> <LinearGradientBrush StartPoint="0,0.2" EndPoint="0.2,1.4"> <GradientStop Offset="0" Color="YellowGreen"/> <GradientStop Offset="1" Color="Gold"/> </LinearGradientBrush> </Setter.Value> </Setter> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="RenderTransform"> <Setter.Value> <ScaleTransform ScaleX="0.8" ScaleY="0.8" CenterX="0" CenterY="0"/> </Setter.Value> </Setter> <Setter Property="RenderTransformOrigin" Value="0.5,0.5" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
We define an appearance change for the IsMouseOver
and
IsPressed
events.
Assigning to the
Template
property
Finally, we'll add a classic button to XAML. In the definition we assign new
values to the Template
property.
<Button Content="Oval Button" Width="150" Template="{StaticResource OvalButton}"/>
The result is as follows:
DataTemplate
The data template defines and determines an appearance and a structure of
data collection. It provides flexibility in formatting and defining data
presentation for any user interface control. It's mostly used in controls with
multi-item data, such as ComboBox
, ListBox
, etc.
In the following example, we'll show how instead of displaying data as text, we can display data as an image and text.
Example - Images in
ListBox
items
To do this, we'll create a class named Person
, which will
contain 3 items:
- Image - of the type
BitmapImage
- First Name - of the type
string
- Last name - of the type
string
The code will be as follows:
public class Person { public BitmapImage Image { get; set; } public string FirstName { get; set; } public string LastName { get; set; } /// <summary> /// The class constructor /// </summary> public Person() { } /// <summary> /// The class constructor with parameters /// </summary> /// <param name="pImage"></param> /// <param name="pFirstName"></param> /// <param name="pLastName"></param> public Person(BitmapImage pImage, string pFirstName, string pLastName) { Image = pImage; FirstName = pFirstName; LastName = pLastName; } }
We then assign the structure to <DataTemplate>
and then
map individual items using the Binding
data binding:
<DataTemplate DataType="{x:Type local:Person}"> <Grid Background="LightGray"> <Grid.RowDefinitions> <RowDefinition Height="25"/> <RowDefinition Height="25"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition Width="20"/> <ColumnDefinition Width="70"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Image Source="{Binding Image}" Grid.RowSpan="2"/> <Label Grid.Column="2" Content="First Name:"/> <TextBox Name="tbxFirstName" Grid.Column="3" Width="50" Text="{Binding FirstName}" HorizontalAlignment="Left" VerticalAlignment="Center"/> <Label Grid.Row="1" Grid.Column="2" Content="Last Name:"/> <TextBox Name="tbxLastName" Width="135" Grid.Row="1" Grid.Column="3" Text="{Binding LastName}" HorizontalAlignment="Left" VerticalAlignment="Center"/> <Border Grid.RowSpan="2" Grid.ColumnSpan="4" BorderBrush="Black" BorderThickness="1"/> </Grid> </DataTemplate>
As the final step, we'll create our <ListBox>
:
<ListBox ItemsSource="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="300" Height="200" BorderBrush="Black"/>
and in Code Behind we'll fill DataContext
with specific
data:
public MainWindow() { InitializeComponent(); persons.Add(new Person { Image = LoadImage("pack://application:,,,/TemplateExample;component/Img/Homer.png"), FirstName = "Homer", LastName = "Simpson" }); persons.Add(new Person { Image = LoadImage("pack://application:,,,/TemplateExample;component/Img/Marge.jpg"), FirstName = "Marge", LastName = "Simpson" }); persons.Add(new Person { Image = LoadImage("pack://application:,,,/TemplateExample;component/Img/Bart.png"), FirstName = "Bart", LastName = "Simpson" }); persons.Add(new Person { Image = LoadImage("pack://application:,,,/TemplateExample;component/Img/Lisa.jpg"), FirstName = "Lisa", LastName = "Simpson" }); this.DataContext = persons; } private BitmapImage LoadImage(string file) { BitmapImage img = new BitmapImage(); img.BeginInit(); img.UriSource = new Uri(file); img.EndInit(); return img; }
And the result:
Scope of templates
Finally, let's talk about the scope of templates. It depends on the template's placement. If we define a template within a control, it applies only to the control. The definition within a window applies to all controls of the window. And when defined within an application, it applies to all controls of the application.
In the next lesson, Animations in C# .NET - Storyboard, Basic & Frame Animations, we'll explain how WPF animations work, and we'll learn how to create basic linear animations and linear frame animations.