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:
[View larger image]
And our goal is to create the following:
[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):
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 DoubleAnimation
s.
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 GradientStop
s 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!
<!---->
<LinearGradientBrush x:Key="DarkBackground" StartPoint="0,0" EndPoint="1,1">
<GradientStop Offset="0" Color="#FF333344"></GradientStop>
<GradientStop Offset="1" Color="#FF666677"></GradientStop>
</LinearGradientBrush>
<!---->
<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>
<!---->
<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>
<!---->
<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