Get up to 80 % extra points for free! More info:

Lesson 29 - WPF - Templates

In the last lesson, Our own control with DependencyProperties in C# .NET WPF , we programmed an example of using DependencyPro­perties 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:

Form Applications in C# .NET WPF

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 appearance
  • DataTemplate - 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:

Form Applications in C# .NET WPF

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:

Form Applications in C# .NET WPF

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.


 

Previous article
Our own control with DependencyProperties in C# .NET WPF
All articles in this section
Form Applications in C# .NET WPF
Skip article
(not recommended)
Animations in C# .NET - Storyboard, Basic & Frame Animations
Article has been written for you by Filip Smolík
Avatar
User rating:
No one has rated this quite yet, be the first one!
Activities