Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF: Customize your Application with Styles and Control Templates (Part 1 of 2)

0.00/5 (No votes)
5 Jun 2010 1  
Part one of a two-part tutorial on how to customize your WPF application using Styles and Control Templates

Introduction

Historically, since the days of Visual Basic to present day Windows Forms, if we wanted to create a custom interface for our application, we were limited to changing certain standard properties of controls such as the background color, border width or font size. If we wanted to go further, our only option was to create a new control, inheriting from an existing control and override the drawing method to implement our own logic of drawing.

This is still possible in WPF, but it is not necessary, because WPF gives us an extensibility model based on templates and styles, allowing us to perform complex changes and redefinitions of the visual appearance of a control, without having to create a new one.

Our Goal

Here you can view the source from which we begin:

initial appearance
[View larger image]

And our goal is to create the following:

final appearance
[View larger image]

As you can see, there is a big difference in the appearance, it is much more visually appealing, so that our users will feel more comfortable and our application will possess an exclusive and unique appearance. So let's get to work, but first, we shall review some basic concepts about Control Templates and Styles in WPF.

A Bit of Theory

WPF gives us several ways to customize our controls:

  • Rich Content
  • Styles
  • Data Templates
  • Control Templates

In this series of articles, I will focus on Styles and Control Templates.

Styles

A Style object is a collection of values representing properties for the specified control. We may automatically assign all the controls of the same type, such as a Button, certain properties such as background color, font, etc. This way all our controls of this type will get these features.

<Style TargetType="{x:Type Button}">
    <Setter Property="Background" Value="Red"></Setter>
    <Setter Property="Foreground" Value="White"></Setter>
</Style>

You could set a style name and apply it manually to the controls that you want using the Style property:

<Style x:Key="OwnStyle" TargetType="{x:Type Button}">
    <Setter Property="Background" Value="Red"></Setter>
    <Setter Property="Foreground" Value="White"></Setter>
</Style>
<Button Style="{StaticResource OwnStyle}"></Button>

Control Templates

Many controls in WPF use templates to define their structure and appearance. Their functionality remains intact, but their appearance is changed, and this gives us unprecedented control over customization of the appearance. This allows us to choose a control functionality to perform a task, regardless of the fact that their appearance does not conform to what we want, as this can be completely redefined. This is a simple example of a Control Template for a button:

<ControlTemplate TargetType="{x:Type Button}">
    <Border Name="fondoboton" BorderBrush="DarkGray" BorderThickness="5" 
            CornerRadius="10,0,10,0"
            Background="LightGray">
        <ContentPresenter Name="contenido"
                          Content="{Binding Path=Content,
                          RelativeSource={RelativeSource TemplatedParent}}">
        </ContentPresenter>
    </Border>
</ControlTemplate>

The result of this template if you put a button in our project would be this (remember that this is only a extremely basic example):

sample button

As you can see, the control lost its appearance, it has been superseded by that which we have indicated, but keeping all the functionality of a button.

Defining the Style of our Application

An important step in defining the style of our application is that it maintains a visual consistency across the different screens, so that the user has a sense of familiarity with our application but is faced with new screens.

Another important aspect is the simplicity of implementation. In an application with hundreds of controls and dozens of screens, we can not tweak each control to adjust visual parameters, that must be done automatically. For this WPF gives us the ResourceDictionary, an XAML file where we include all our code for styles, and include it throughout the application. Thus, any changes we make in the style or template of a control will automatically apply to all the controls in our application.

The first thing we do in our application is to add a new element of type ResourceDictionary, in my case the name is BlackCrystal.xaml. This is a new XAML file, and when included in our project, it should appear like this:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</ResourceDictionary> 

Once we have our ResourceDictionary added, it's time to tell our application to make use of it. We can do this by editing Application.xaml to point to the path of the new dictionary of resources that we want to use. It should look like this:

<Application x:Class="Application"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary Source="BlackCrystal.xaml"></ResourceDictionary>
    </Application.Resources>
</Application>

We just added a new node of type Application.Resources and within the same, created a new ResourceDictionary, indicating the path (source) from which to load this dictionary.
With this, any style or template that we create in our ResourceDictionary will be immediately available throughout our application.

Common Resources

Something important to keep in mind when designing the look and feel of our application is to provide it with a uniform appearance - same colors, same geometry and same animations. If we define these parameters directly in each of the styles and templates, it will make maintaining them a pain, taking dozens of places to go to in order to change a single style. To fix this, the first thing we do is to define in our resource dictionary all the colors and animations we want to use. The example I created, Black Crystal, used a total of 9 different gradients and two animations that apply to all the controls. These gradients are of LinearGradient and RadialGradient type, and animations are DoubleAnimations.

Here you can see an example of LinearGradient, that is used to be apply to the background of the windows, applying a shades of gray gradient:

<LinearGradientBrush x:Key="DarkBackground" StartPoint="0,0" EndPoint="1,1">
    <GradientStop Offset="0" Color="#FF333344"></GradientStop>
    <GradientStop Offset="1" Color="#FF666677"></GradientStop>
</LinearGradientBrush> 

The XAML code is very simple. The first line defines the object type (LinearGradientBrush). We assign a key to use the resource created from anywhere, and indicate the point of beginning and end. These points are specified with a pair of numbers ranging from 0 to 1, (0, 0) indicating the upper left corner and (1, 1) the lower right corner. The first number in each pair indicates the point on the X axis and the second number indicates the point on the Y axis. In this way, from (0, 0) to (1, 1) indicates that our gradient traverses the diagonal from top left to bottom right corner. A value of (0, 0) to (1, 0) would indicate that our gradient begin at the upper left corner and end at the upper right corner, which would have a gradient aspect of horizontal left to right. Similarly, a value of (0, 0) to (0,1) would create a vertical gradient effect.

The GradientStop objects indicate the color that was used in each part of the gradient. A gradient can have as many GradientStops as you want. The property Offset indicates the position of color in the gradient, where 0 is the beginning and 1 the end. We can use any value between numbers as 0.2, 0.35 or 0.98. Finally, Color indicates the color to use at that point of the gradient. It can be a color predefined by the system, such as White, Purple or Black, or an ARGB color value where A = Alpha (transparency), R = Red, G = Green and B = Blue.

The object RadialGradientBrush is very similar to LinearGradientBrush and consists basically of the same objects, as shown:

<RadialGradientBrush x:Key="GlowFX" GradientOrigin=".5,1" Center=".5,1">
    <GradientStop Offset="0" Color="#990000FF"></GradientStop>
    <GradientStop Offset=".5" Color="#660000DD"></GradientStop>
    <GradientStop Offset="1" Color="#33000000"></GradientStop>
</RadialGradientBrush>

The biggest difference here is that we no longer have StartPoint and EndPoint properties, instead of these we have GradientOrigin and Center. GradientOrigin defines the point from which the gradient starts, and Center defines the point which will be the center of the outer circle that defines the gradient. In this example, these two properties have identical values but could be different without any problem.

To apply these resources to control properties, we only need to bind to a static resource and specify the name of our resource, like this:

<Button Content="Hola"
        Background="{StaticResource GlowFX}">
</Button> 

Finally we have the animations. In my case I used it to give an effect when hovering the mouse over controls, or when receiving focus to controls using the Tab key, as below:

<Storyboard x:Key="GlowOut">
    <DoubleAnimation x:Name="AnimGlowOut" BeginTime="00:00:00"
                     Storyboard.TargetName="GlowRectangle"
                     Duration="00:00:00.250" From="1" To="0"
                     Storyboard.TargetProperty="Opacity">
    </DoubleAnimation></Storyboard>
</Storyboard>

As you can see it is not a complicated XAML. First we define an object StoryBoard and give a name that is used to invoke it from other objects. This Storyboard contains our animation. In WPF there are many different types of animation. The first part of the name indicates the type of value that is able to run, in our case we use a value of type Double, but we can also find BooleanAnimation, CharAnimation, ColorAnimation and many more types.

The properties defined are very simple. Name is the name applied to our animation. BeginTime indicates the time after invoking the storyboard at which the animation starts. Duration is the total time taken for our animation to run. TargetName indicates the object on which the StoryBoard executes. TargetProperty applies the animation to the property indicated. Finally, From and To show the initial value and final value applied to this animation.

In this case the animation will be applied to an object called GlowRectangle, and the Opacity property will change gradually in the course of 250 milliseconds to go from 1 (fully opaque) to 0 (fully transparent).

The application of animation is done through event triggers of a control. In my next article, I will explain in more details of this, but it will be something like this:

<Button Name="GlowRectangle" Content="Hi Animation">
    <Button.Triggers>
        <EventTrigger RoutedEvent="MouseLeave">
            <EventTrigger.Actions>
                <BeginStoryboard Name="{StaticResource GlowOut}">
                </BeginStoryboard>
            </EventTrigger.Actions>
        </EventTrigger>
    </Button.Triggers>
</Button>

End of Part 1

I will leave a list of all gradients and animations used in this project, so you can start playing with them and understand their use. In the second part of this article, I shall come directly to getting our hands dirty creating Styles and Control Templates.

I hope you find it useful and you like it. See you soon!

<!-- DARK BACKGROUND -->
<LinearGradientBrush x:Key="DarkBackground" StartPoint="0,0" EndPoint="1,1">
  <GradientStop Offset="0" Color="#FF333344"></GradientStop>
  <GradientStop Offset="1" Color="#FF666677"></GradientStop>
</LinearGradientBrush>

<!-- GLASS EFFECT -->
<LinearGradientBrush x:Key="GlassFX" StartPoint=".5,0" EndPoint=".5,.5">
  <GradientStop Offset="1" Color="#33DDDDDD"></GradientStop>
  <GradientStop Offset="1" Color="#33000000"></GradientStop>
</LinearGradientBrush>
<LinearGradientBrush x:Key="GlassFXDisabled" StartPoint=".5,0" EndPoint=".5,.5">
  <GradientStop Offset="1" Color="#33BBBBBB"></GradientStop>
  <GradientStop Offset="1" Color="#11000000"></GradientStop>
</LinearGradientBrush>

<!-- GLOW EFFECT -->
<RadialGradientBrush x:Key="GlowFX" GradientOrigin=".5,1" Center=".5,1">
  <GradientStop Offset="0" Color="#990000FF"></GradientStop>
  <GradientStop Offset=".5" Color="#660000DD"></GradientStop>
  <GradientStop Offset="1" Color="#33000000"></GradientStop>
</RadialGradientBrush>
<RadialGradientBrush x:Key="GlowFXPressed" GradientOrigin=".5,1" Center=".5,1">
  <GradientStop Offset="0" Color="#660000CC"></GradientStop>
  <GradientStop Offset="1.2" Color="#33FFFFFF"></GradientStop>
</RadialGradientBrush>
<LinearGradientBrush x:Key="GlowFXRowPressed">
  <GradientStop Offset="0" Color="#660000FF"></GradientStop>
  <GradientStop Offset=".7" Color="#660000AA"></GradientStop>
  <GradientStop Offset="1" Color="#66000000"></GradientStop>
</LinearGradientBrush>
<LinearGradientBrush x:Key="GlowFXProgress">
  <GradientStop Offset="0" Color="#660099FF"></GradientStop>
  <GradientStop Offset=".99" Color="#660022AA"></GradientStop>
  <GradientStop Offset="1" Color="#00000000"></GradientStop>
</LinearGradientBrush>
<LinearGradientBrush x:Key="GlowFXProgressAnimated" MappingMode="RelativeToBoundingBox">
  <GradientStop Offset="0" Color="#00000000"></GradientStop>
  <GradientStop Offset=".50" Color="#660099FF"></GradientStop>
  <GradientStop Offset="1" Color="#00000000"></GradientStop>
</LinearGradientBrush>
<LinearGradientBrush x:Key="GlowFXTabSelected" StartPoint=".5,1" EndPoint=".5,0">
  <GradientStop Offset="0" Color="#33DDDDDD"></GradientStop>
  <GradientStop Offset="1" Color="#332222FF"></GradientStop>
</LinearGradientBrush>

<!-- GLOW ANIMATION -->
<Storyboard x:Key="GlowOut">
  <DoubleAnimation x:Name="AnimGlowOut" BeginTime="00:00:00" 
                   Storyboard.TargetName="GlowRectangle"
                   Duration="00:00:00.250" 
                   From="1" To="0"
                   Storyboard.TargetProperty="Opacity">
  </DoubleAnimation>
</Storyboard>
<Storyboard x:Key="GlowIn">
  <DoubleAnimation x:Name="AnimGlow" 
                   BeginTime="00:00:00" 
                   Storyboard.TargetName="GlowRectangle"
                   Duration="00:00:00.250" 
                   From="0" To="1" 
                   Storyboard.TargetProperty="Opacity">
  </DoubleAnimation>
</Storyboard> 

History

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here