Skip to content

WPF Styles and Triggers

In this article I will introduce you into the concept of WPF Styles and Triggers.

Introduction

WPF Styles consist of Setters and Triggers that are supposed to change the Style and Behavior of a Control. From a technical point of view the purpose of Styles is to set Dependency Properties on a Control.

Some Use Cases of Styles:

  • Change Colors of a Control
  • Change the Control Template of a Control
  • Add animation
  • Add behaviors
  • Define Themes

Definition of a WPF Style

In its simplest form a WPF Style only contains one or more Setters.

<Window.Resources> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="Blue"/> </Style> </Window.Resources>
Code language: HTML, XML (xml)

In the listing above you can see a Style which targets ‘TextBlock’ and contains a Setter for the property ‘Foreground’. If that style is applied to a TextBlock the property Foreground will be set to ‘Blue’.

<Window x:Class="StylesTutorial.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StylesTutorial" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="Blue"/> </Style> </Window.Resources> <Grid> <TextBlock Text="Hello, World!"/> </Grid> </Window>
Code language: HTML, XML (xml)

You can override the Foreground Property when you declare a value for Foreground locally at the place where you declare the TextBlock element. See my Dependency Property article for more information about the value evaluation order.

<Window x:Class="StylesTutorial.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StylesTutorial" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="Blue"/> </Style> </Window.Resources> <Grid> <TextBlock Text="Hello, World!" Foreground="Black"/> </Grid> </Window>
Code language: HTML, XML (xml)

In the listing above the TextBlock Style will be applied to the TextBlock element inside the Grid. However, the local value ‘Black’ will override the Style Setter as it has a higher priority in the evaluation order.

Styles can be declared at different places and can have different scopes.

Local WPF Style

You can declare a style directly at the place where it is used.

<TextBlock Text="Hello World!"> <TextBlock.Style> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="Blue"/> </Style> </TextBlock.Style> </TextBlock>
Code language: HTML, XML (xml)

If you define your Style at this place it can only be applied to this TextBlock element. It cannot be applied to other TextBlock elements. I use that approach very rarely.

Implicit WPF Style

Probably you noticed the property ‘TargetType’ inside the Style declaration. The usage of the TargetType has two effects:

  • It sets the target type scope to ‘TextBlock’ for your complete Style. That means that you can simply write ‘Foreground’ instead of ‘TextBlock.Foreground’. If you omit the TargetType you always need to prefix the property name with the class type.
  • It automatically applies your style to all TextBlock instances in the scope of the Style.

Hint: Our Style is only in scope if it is contained in the Resource Dictionary of the XAML file. This is the case if the style is declared directly in the Resource section in the XAML file. If it is declared in another Resource Dictionary it needs to be merged into the XAML file where we want to use the Style.

Explicit WPF Style

If you don’t want your style to be applied to all elements of a specific type you need to define a Key Name for the Style. If a Style has a Key Name you need to reference your Style in the target element by yourself. It will no longer be applied automatically!

<Window x:Class="StylesTutorial.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StylesTutorial" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style x:Key="TextBlockStyle" TargetType="TextBlock"> <Setter Property="Foreground" Value="Blue"/> </Style> </Window.Resources> <Grid> <TextBlock Text="Hello, World!" Style="{StaticResource TextBlockStyle}"/> </Grid> </Window>
Code language: HTML, XML (xml)

In the listing above we used the Key ‘TextBlockStyle’ to give a unique name for our style. To apply the Style we need to reference it in our TextBlock element.

Control Template WPF Style

Styles are often used inside Control Templates and also to apply the Control Templates to a Control.

In the listing below you can see a Control Template Style automatically created by Visual Studio. Please see the Control Templates article for more information about how to create it.

<Style x:Key="FocusVisual"> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate> <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/> </ControlTemplate> </Setter.Value> </Setter> </Style> <SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/> <SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/> <SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/> <SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/> <SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/> <SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/> <SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/> <SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/> <SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/> <Style x:Key="ButtonStyle" TargetType="{x:Type Button}"> <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/> <Setter Property="Background" Value="{StaticResource Button.Static.Background}"/> <Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Padding" Value="1"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true"> <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsDefaulted" Value="true"> <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/> </Trigger> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/> <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/> </Trigger> <Trigger Property="IsPressed" Value="true"> <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/> <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/> </Trigger> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/> <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/> <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
Code language: HTML, XML (xml)

The style can simply be applied by referencing its Key Name.

<Button Style="{DynamicResource ButtonStyle}" />
Code language: HTML, XML (xml)

WPF Style Inheritance

WPF Styles can inherit from each other. This is very helpful if you have some Styles which shall be applied to all Controls of a distinct base class.

To base your style on another style you need to reference the other one using the property ‘BasedOn’ in your style declaration.

<Window x:Class="StylesTutorial.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StylesTutorial" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style TargetType="FrameworkElement" x:Key="FrameworkElementStyle"> <Setter Property="Margin" Value="10"/> </Style> <Style TargetType="TextBlock" BasedOn="{StaticResource FrameworkElementStyle}"> <Setter Property="Foreground" Value="Blue"/> </Style> <Style TargetType="Button" BasedOn="{StaticResource FrameworkElementStyle}"> <Setter Property="Foreground" Value="Blue"/> </Style> </Window.Resources> <StackPanel> <TextBlock Text="Hello, World!"/> <Button Content="Hello, World!"/> </StackPanel> </Window>
Code language: HTML, XML (xml)

In the listing above I declared a style which targets ‘FrameworkElement’ and sets the Margin to 10. This style is inherited in Button and TextBlock. Because the Button and TextBlock style have no Key they are applied automatically to TextBlock and Button inside the StackPanel.

WPF Style Triggers

Until now we just used simple Setters to set properties. However, WPF Styles are much more powerful. You can set properties based on triggers.

In WPF there are different types of triggers which are introduced in the following sections.

Property Trigger

The most commonly used Trigger type is the Property Trigger. A Property Trigger can set properties depending on the value of another property.

<Window x:Class="StylesTutorial.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StylesTutorial" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style TargetType="TextBlock"> <Style.Triggers> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="Foreground" Value="Green"/> <Setter Property="Background" Value="Yellow"/> </Trigger> </Style.Triggers> <Setter Property="Foreground" Value="Blue"/> <Setter Property="Background" Value="Beige"/> </Style> </Window.Resources> <StackPanel> <TextBlock Text="Hello, World!"/> </StackPanel> </Window>
Code language: HTML, XML (xml)

In the listing above we have a style with a Setter which sets the Foreground property to ‘Blue’ and the Background property to ‘Beige’. Additionally we have a Property Trigger which will be applied when the TextBlock.IsMouseOver property is true. The Property Trigger contains two Setters for the Foreground and the Background properties.

When the user hovers with the mouse over the TextBlock the Foreground will change its color to ‘Green’ and the Background to ‘Yellow’.

But what happens if the property IsMouseOver changes to false? Don’t we need another Property Trigger for the IsMouseOver=false case?

To answer this question we need to think about the evaluation order of Dependency Properties. Triggers have a higher priority than Setters. That’s the reason why the Foreground changes from Blue to Green when the Property Trigger gets active. If the Property Trigger gets inactive WPF will just re-evaluate the priority order and find another value source. In our case its the Setter in the same style. Thus it will turn back to Blue. The Background color will also change back to Beige.

Event Trigger

There are two differences compared to Property Triggers:

  • Event Triggers react on events instead of property changes
  • Event Triggers cannot contain Setters to set properties but instead perform other actions like a storyboard

In the example below we add animation to the Opacity property of our TextBlock. When the user enters the TextBlock with the mouse we change to Opacity to 0.5 during a time period of 1 second. When the user leaves the TextBlock we change the Opacity back to 1.

<Style TargetType="TextBlock"> <Style.Triggers> <EventTrigger RoutedEvent="MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty = "Opacity" Duration = "0:0:1"> <LinearDoubleKeyFrame Value = "0.5" KeyTime = "0:0:2"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty = "Opacity" Duration = "0:0:1"> <LinearDoubleKeyFrame Value = "1" KeyTime = "0:0:2"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style>
Code language: HTML, XML (xml)

Data Trigger

Last but not least the Data Triggers. Data Triggers are very similar to Property Triggers. The only difference is that you can use data binding to bind to any other element or data context. Despite that you can use Property Setters in the same way as for Property Triggers.

<Window x:Class="StylesTutorial.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:StylesTutorial" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.Resources> </Window.Resources> <StackPanel> <CheckBox x:Name="checkbox"/> <TextBlock Text="Hello, World!"> <TextBlock.Style> <Style TargetType="TextBlock"> <Style.Triggers> <DataTrigger Binding="{Binding ElementName=checkbox, Path=IsChecked}" Value="true"> <Setter Property="Foreground" Value="Blue"/> </DataTrigger> </Style.Triggers> </Style> </TextBlock.Style> </TextBlock> </StackPanel> </Window>
Code language: HTML, XML (xml)
Copyright (c) by Thomas Kemp, 2021