Table of Contents
Perhaps the most interesting and most important feature for any WPF application is Styling.
Styling means defining styles for controls, and store in reusable ResourceDictionaries
and hence forth, it could be used later on by calling its name. Styles in WPF could be well compared
with CSS styles. They are both similar in most of the cases, while the former extends the
feature allowing most of the features which WPF have. In this article I will discuss mostly
how you could use Style
in your WPF application to enhance the Rich experience
of your UI.
Before we go on further with styles, lets jot down what we
have already discussed so far.
So basically if you have already gone through the articles in the series,
you must have already know most of the things on how you could apply your
styles, how could you define style objects etc. In this article I
will discuss how you could write better styles for your application.
WPF exposes a property Style
for every Control. If you look into the
object Hierarchy, the Style is basically a property which exposes an object of Style
in FrameworkElement
. So each object can associate it and define custom
setters to manipulate the basic look and feel of a control.
Clearly, the above diagram shows the association of Style
in
FrameworkElement
and from the object hierarchy every control
somehow inherits from FrameworkElement
and hence style will be available to it.
Style is also a WPF object which is inherited form DispatcherObject
which helps in setting different properties of your UI Element.
Before we move further into Styles lets talk about Themes.
Theme is totally different from Styles. Themes are defined at OS level,
or more precisely a Theme can take part of delivering styles all over the Desktop while Styles
are restricted to the contextual area of a WPF window. WPF are capable of retrieving
the color scheme which is defined in OS level. Say for instance, if you do not define style
for your application, the elements in the screen will automatically get styles from external
environment. Say for instance, in XP if you change the theme to something else
you would see that the buttons, TextBox
on your WPF window will change its color instantly.
You can even set the Theme which the application would use
programmatically
[^]
from your code.
Every control defines a ControlTemplate
. A ControlTemplate
defines the overall
structure of the control. As I have already told you, say for instance you have a Button
.
Button is a control that is made up of more than one control. It would have a ContentPresenter
which writes the Text
over the control, it would have a Rectangle
which keeps the boundary of the Button
etc. So Template is a special property associated with a Control which specifies how the control will look like structurally. We can
easily define our Template
and change the overall structure of a control.
Templates are basically of 2 types :
- ControlTemplate
- DataTemplate
ControlTemplate
defines the structure of the Control
.
It means say for instance, you define the ControlTemplate
for a
ComboBox
. So from ControlTemplate
you can easily change the
ToggleButton
associated with the ComboBox
which opens the
DropDown
, you can change the structure of the TextBox
, the
Popup
etc. So ControlTemplate
allows you to change the
overall structure of the Control.
Each control is made up of Data. Say for instance a ItemsControl
contains
a number of Data Element which builds the items inside the Popup. The
DataTemplate
could be associated with ItemsTemplate
and will build up the
Data Block for the ComboBox
.
So, you should always remember, ControlTemplate
defines the whole Control while the DataTemplate
defines each individual Data Element.
Normally a style is an unique object which is used to style WPF controls. Each WPF
element contains a number of Dependency Properties. A dependency property defines the basic
behavior and look of the control in UI. Styles maintains a collection of Setters
which enumerates a DependencyProperty
with its value.
Thus you can say a style is a collection of DependencyProperty
settings which when applied on a Target will change the behavior of it.
Let us suppose you are going to style a TextBox
.
<TextBox Text="This is a TextBox without Styles"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CharacterCasing="Lower"
FlowDirection="RightToLeft"
FontSize="20"
FontWeight="UltraBlack"
Width="400"
Height="40">
<TextBox.Background>
<LinearGradientBrush>
<GradientStop Color="Cyan" Offset="0.0"/>
<GradientStop Color="Yellow" Offset="0.5"/>
<GradientStop Color="Red" Offset="1.0"/>
</LinearGradientBrush>
</TextBox.Background>
<TextBox.Foreground>
<SolidColorBrush Color="Black"/>
</TextBox.Foreground>
<TextBox.Effect>
<DropShadowEffect BlurRadius="40" Color="Maroon"
Direction="50" Opacity="0.5"/>
</TextBox.Effect>
</TextBox>
So I have just designed a TextBox
in the above code. The XAML looks straight forward, where I have configured different properties of the TextBox
control to create my stylish TextBox
. But looking at the code, you might wonder how difficult it would be if you need to redo the same thing again and again for every TextBox you define in your application. This is what the problem is. So WPF comes with an alternative with style
. A style
is an object that holds this behaviors into a collection of Setters
. So lets redefine the same with Styles
.
<TextBox>
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="This is a TextBox with Styles"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="CharacterCasing" Value="Lower"/>
<Setter Property="FlowDirection" Value="RightToLeft"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="UltraBlack"/>
<Setter Property="Width" Value="400"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush>
<GradientStop Color="Cyan" Offset="0.0"/>
<GradientStop Color="Yellow" Offset="0.5"/>
<GradientStop Color="Red" Offset="1.0"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="Black"/>
</Setter.Value>
</Setter>
<Setter Property="Effect" >
<Setter.Value>
<DropShadowEffect BlurRadius="40"
Color="Maroon" Direction="50" Opacity="0.5"/>
</Setter.Value>
</Setter>
</Style>
</TextBox.Style>
</TextBox>
So you can see, I have defined the Style
inside the TextBox
and the textbox looks almost the same. The Setters allows you to enumerate all the properties for the TextBox
and produced a style inside it whose TargetType
is set to {x:Type Button}
Now how this style
can be made reusable for many controls ? Yes, this might be your first question that arose in your mind. Yes, if you have read my previous articles, you should already know the use of ResourceDictionaries
. So in our case I will shift the style to Resource section for the Window and reuse the code just by calling the Resource key from the Textbox
.
<Grid>
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type TextBox}" x:Key="MyTextBoxStyle">
<Setter Property="Text" Value="This is a TextBox with Styles"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="CharacterCasing" Value="Lower"/>
<Setter Property="FlowDirection" Value="RightToLeft"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="UltraBlack"/>
<Setter Property="Width" Value="400"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Margin" Value="0,20,0,10" />
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush>
<GradientStop Color="Cyan" Offset="0.0"/>
<GradientStop Color="Yellow" Offset="0.5"/>
<GradientStop Color="Red" Offset="1.0"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="Black"/>
</Setter.Value>
</Setter>
<Setter Property="Effect" >
<Setter.Value>
<DropShadowEffect BlurRadius="40"
Color="Maroon" Direction="50" Opacity="0.5"/>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Style="{StaticResource MyTextBoxStyle}" Grid.Row="0" />
<TextBox Style="{StaticResource MyTextBoxStyle}" Grid.Row="1"
Text="The Style is modified here"
FlowDirection="LeftToRight"/>
</Grid>
So here I have shifted the Style into Resource section and used MyTextBoxStyle
key to refer for each TextBox
i defined. Notably, the style of both the textboxes remains same, while you can see I have also overridden certain settings in the control itself and it works the same. I have modified the Text
of the 2nd TextBox to "The Style is modified here" and also made the FlowDirection
to LeftToRight
.
Another important thing, that you should always keep into mind, that if you do not define the Key element for the Style in Resource section, it will automatically be applied to all the TextBox
you define.
<Style TargetType="{x:Type TextBox}">
</Style>
Say the style
you define does not contain any Key. So all the TextBoxes
will automatically apply the style when appeared. You can eventually use
<TextBox Style="{x:Null}"/>
to revert the style.
The styling of WPF controls is made up with the help of a class called Style
. The style object exposes few properties which help you to define various behavior. Lets look into the properties:
- Resources : It holds the reference for the
ResourceDictionary
where the Style is defined.
- Setters : It is a collection which holds all the
DependencyProperty
configuration for the whole control.
- TargetType :
TargetType
defines the type of the control for which the Style can be applied. So based on the TargetType
the Style setters are defined to. So if you define a style for TextBox
you cannot use Content as property Setter
.
- BasedOn : This is used to allow
Style
inheritance. You can use an existing style key to inherit all the properties to a new Style
.
- Triggers : A collection of Setters which would be applied based on certain conditions.
Using those properties you can define your own styles.
WPF controls can have two type of styles associated with it. A control can have a style defined in the application and applied to its Style
property. If your control is using a Style
to define its look and feel or basically your control has set an object of Style into its Style property, then it is using an Explicit
Style.
On the other hand, if your control takes the style from external environment (Theme) and the Style property is set to Null, then your control is using Implicit
Style. Basically any WPF control automatically defines a DefaultStyle for it, so that you can set only the portion of the control which you need to change.
Say for instance, you have a Button
. If you want to have its Text
to be colored Red, you just need to change the Foreground of the Button
. You need not to define the whole style. If there is no Default Style defined for Buttons, you need to define all the properties individually to make it appear. Thus the default color of the Text
is Black if not defined otherwise.
Triggers
are a set of styles that work on a particular condition. You can think Trigger as a part of Style
which will be set only when the Condition defined for the Trigger
is met.
There are few types of Triggers :
- Property Trigger : Will be set only when the
DependencyProperty
of a certain object has been set to a Value
.
- Data Trigger : Will work for any normal Properties using on
Binding
.
- Event Trigger : Will work only when some event is triggered from the control.
Now to demonstrate let us look into the code below :
<Style TargetType="{x:Type TextBox}" x:Key="MyTextBoxStyle">
<Setter Property="Text" Value="This is a TextBox with Styles"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="CharacterCasing" Value="Lower"/>
<Setter Property="FlowDirection" Value="RightToLeft"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="UltraBlack"/>
<Setter Property="Width" Value="400"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Margin" Value="0,20,0,10" />
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush>
<GradientStop Color="Cyan" Offset="0.0"/>
<GradientStop Color="Yellow" Offset="0.5"/>
<GradientStop Color="Red" Offset="1.0"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="Black"/>
</Setter.Value>
</Setter>
<Setter Property="Effect" >
<Setter.Value>
<DropShadowEffect BlurRadius="40" Color="Maroon" Direction="50"
Opacity="0.5"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="40" Color="Red"
Direction="50" Opacity="0.9"/>
</Setter.Value>
</Setter>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="True"/>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="40" Color="Violet"
Direction="50" Opacity="0.9"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" Value="Maroon" />
</MultiTrigger>
</Style.Triggers>
</Style>
Here you can see I have used Property Trigger
to change the DropShadowEffect
of TextBox when it is focussed. Every WPF control exposes few properties to work with Property Triggers, which will be set to true based on the control appearance changes. You can use these properties like IsFocused, IsMouseDown
etc to work around with Property Triggers.
On the second occasion, I have defined a MultiTrigger
. MultiTrigger
allows you to mention Condition, so that when all the conditions of the MultiTrigger
is met, the Property Setters
for the object is applied.
So you can see when you hover your mouse over the TextBox
and your textbox has its focus in it, only then you see the TextBox
to appear in Maroon background and Violet DropShadow
effect.
Another interesting thing that you might think very interesting is the support of Animation for WPF. Basically, by the word Animation, we generally think of large Texture graphics in 3D space, which would probably be created in 3DS MAX studio or MAC etc. But believe me there is nothing to worry about this in case of WPF. WPF simplifies the concept Animation to be the change of a property over time.
Say for instance, say you want your textbox
to change its color over time, you would write a simple color animation to do this or say you want to change the Opacity
of a Border element during time, you need DoubleAnimation
to do this. Animation is cool if you are clear about how it works.
I must say, don't make yourself more confused by seeing the types of Animation. Animation is actually categorized in the same way as you categorize variables. Say for instance :
- DoubleAnimation : This will animate a Double Value from one value to another. So if you want to change the Width of a
TextBox
over time you need DoubleAnimation
.
- ColorAnimation : Same as the above if the type of Changing element is Color, you need ColorAnimation.
SingleAnimation, RectAnimation, PointAnimation, Int32Animaition, ThicknessAnimation
etc each of them bears the same meaning.
So basically the basis of Animation types is based on the type of the property for which you want your animation to work on.
Animation can also be categorized into two basic ways :
- Animation Without KeyFrames : These are animation that only needs two values, From and To. It gives you a smooth animation based on the
Timeline.DesiredFramerate
property for the animation.
- Animation With KeyFrames : Allows you to specify a
KeyFrame
collection which lets you define the KeyFrame value on a specified time. So that you can adjust your own animation based on specific time intervals.
Let us take a look at a few examples to make you understand animation feature of WPF:
<Window.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetProperty="Width"
From="300" To="200" AutoReverse="True" Duration="0:0:5" ></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Height"
From="300" To="200" AutoReverse="True" Duration="0:0:5"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
In the above code, I have defined an EventTrigger
which lets you have a DoubleAnimation
(as Width is double value) on Width
of the Window
. We use Loaded
Event to start a StoryBoard
.
A StoryBoard
can be defined as a Container for TimeLines
or a collection of animation timelines for which the object specified in Target
will animate. We use StoryBoard
to specify Animation within it.
Few important properties of StoryBoard
:
- RepeatBehaviour : Specifies the number of times for which the
StoryBoard
repeat the animation.
- Target : Specifies the Target for which the
storyboard
will be applied to.
- TargetName : Defines the target and reference it by its name attribute.
- TargetProperty : Specifies the property for which the animation will be applied for.
- AccelerationRatio / DecelerationRatio : Defines the acceleration or deceleration for the animation.
- AutoReverse : Defines whether the
StoryBoard
will be reversed automatically. This is really cool concept, which allows you to get the reverse of the storyboard timeline automatically generated by the WPF.
Animation can also be applied from code.
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 1.0;
myDoubleAnimation.To = 0.0;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(5));
In the above code I have declared a DoubleAnimation
which starts From 1.0 and moves to 0.0 in 5 seconds.
Animation can be defined either using KeyFrames or without KeyFrames
. KeyFrame
allows you to define an intermediate frame so that the animation occurs for each individual frame intervals. There are three types of interpolation for an AnimationwithKeyFrames
.
- Linear
- Discrete
- Spline
Lets create an animation using KeyFrames
:
<Border Background="Violet"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" >
<Border.Triggers>
<EventTrigger RoutedEvent="Border.MouseLeftButtonDown">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="transformObj"
Storyboard.TargetProperty="X"
Duration="0:0:15">
<LinearDoubleKeyFrame Value="500"
KeyTime="0:0:3" />
<LinearDoubleKeyFrame Value="50"
KeyTime="0:0:7" />
<LinearDoubleKeyFrame Value="300"
KeyTime="0:0:13" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Border.Triggers>
<Border.RenderTransform>
<TranslateTransform x:Name="transformObj" X="0" Y="0" />
</Border.RenderTransform>
</Border>
Here the animation is applied as LinearDoubleKeyFrame
, which means the animation would be smooth while we define each KeyFrame value based on KeyTime. Here we change the Translation
of the Border
based on different KeyTime
specified such that on 3rd second, the Rectangle will move to 500, at 7th second it will be at 50 and at 13th second it will be at 300. The animation is LinearDouble
so the animation is smooth and steady.
If I change the animation to DiscreteAnimation
it will place the object only at the KeyTime
specified
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="transformObj"
Storyboard.TargetProperty="X"
Duration="0:0:15">
<DiscreteDoubleKeyFrame Value="500" KeyTime="0:0:3" />
<DiscreteDoubleKeyFrame Value="50" KeyTime="0:0:7" />
<DiscreteDoubleKeyFrame Value="300" KeyTime="0:0:13" />
</DoubleAnimationUsingKeyFrames>
Thus changing the LinearDouble
with DiscreteDouble
makes it change its position all of a sudden based on the KeyTime
specified for the animation.
SplineAnimation
is used to define more realistic animation behavior for your control. It lets you control acceleration and deceleration of the animation. With KeySpline you can define the the cubic bazier curve using Spline Key frame. Lets look at the example
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="transformObj"
Storyboard.TargetProperty="X"
Duration="0:0:15">
<SplineDoubleKeyFrame Value="500" KeyTime="0:0:3" KeySpline="0.0,0.1 0.1,0.1" />
<SplineDoubleKeyFrame Value="50" KeyTime="0:0:7" KeySpline="0.0,0.1 0.1,0.1"/>
<SplineDoubleKeyFrame Value="300" KeyTime="0:0:13" KeySpline="0.0,0.1 0.1,0.1"/>
</DoubleAnimationUsingKeyFrames>
Thus you can see KeySpline
allows you to define a smooth animation that starts with acceleration and with highest speed in the middle and ultimately decelerates back again.
You can also use EasingFunction
to specify the custom formulas for the animation. For further information try :
KeyFrameAnimation
[^]
I hope you like my article. I am also happy that I have covered almost most of the topics that you need to know in WPF. There are still a few things left behind, which I would cover in my next article. Please put your valuable feedback for the article.
Thank you for reading.