Styles and triggers in WPF

Gill Cleeren
Ordina Belgium

Applies to:
  • Visual Studio 2005
  • .NET Framework 3.0
  • Windows Presentation Foundation
  • C#
  • Windows Vista / Windows XP SP2 / Windows Server 2003 SP1
Summary:

Windows Presentation Foundation, or WPF, previously known by its code-name “Avalon”, is an all-new presentation framework included in Windows Vista. WPF is also available for Windows XP SP2 and Windows Server 2003 SP1 as part of the .NET Framework 3.0. WPF consists of numerous new technologies that as a whole provide a unified programming model for applications that will run standalone or inside a browser. In this article, we will focus on the aspect of styles and triggers.

Styles make it possible to create applications with a uniform look and with a high level of maintainability. Triggers allow WPF styles to change one or more properties in response of a user interaction. In this document, we will use both these technologies to create a richer user experience.

The code is tested on the June CTP of the .NET Framework 3.0.



Contents:

What are styles?

The concept of “styles” is very common. In fact, we all use styles almost daily. When writing a letter in Word, we use a number of properties on our text, such as font, indenting, colors… Together, these properties define a style.

Styles are also present in modern web development. When creating a web application, we can write all visual properties like background color, border thickness… directly in your markup code (HTML). There is nothing wrong with that, but it’s not easy to maintain either. If we want to give our site a different appearance for the holiday season, we have to search throughout all the code, and change all the properties.
We are doing pretty much the same work several times over: for example change the background of all buttons from blue to green.

The solution to this problem is using CSS, cascading style sheets. We create a style, in which you describe the visual appearance of the button, in this case. All the buttons in our site that use that style, will look the same. And when the end of the year is drawing near, we only have to change the style in one place to make all buttons look festive.

In WPF, styles are similar to CSS styles: WPF styles also consist of a set of properties that can be applied to a visual element, like the background color of a button.
However, styles in WPF can do more than CSS styles can: based on user interaction or on content, they can make the control look or behave differently, using triggers. WPF styles are available from code, which can be used in combination with personalization features of .NET 2.0.

Like CSS, they help giving applications a unified look, that is easy maintainable, since properties only have to be changed in one place.


When to use a style

Let’s say we’re developing a WPF web browser application. Throughout the application, we’ll probably have several buttons that have the same purpose, for example to navigate from one page to another.

The XAML code for a simple button could look this:

<Button x:Name="GoButton" Content="Go" Width="100" Height="30"></Button>

This is the resulting button:


Figure 1: A standard WPF button
Figure 1: A standard WPF button


Nothing wrong with that… But knowing that WPF is a presentation framework, we can do a lot better!
We’ll give it some extra visual properties, like a font size, a background color and a foreground color.

The code now looks like the following:

<Button x:Name="GoButton" 
  Background="BlueViolet" 
  Foreground="White" 
  FontSize="20" 
  Width="50" 
  Height="30" 
  Content="Go" >
</Button>

The button now looks like this:


Figure 2: Button decorated with some properties
Figure 2: Button decorated with some properties


This already looks a lot better (we can of course create much nicer looking buttons in WPF, with all kind of effects like rollovers…). Now, if this is the visual appearance for all our buttons throughout our web app, we will have to rewrite that same piece of markup on several places. Rewriting the same code is not a recommended practice! Not only is it a waste of effort and time, but it also makes the application harder to maintain, since we have to go through the code and change it in several places.

Therefore, what we should be able to do, is placing this markup code in one place, so that all buttons can access it. Then, when we have to update them, we would only have to update the code in one place, and all the “registered” buttons can change accordingly. This is exactly where styles can come in handy.


Named styles

We’ll now transform the markup code into a style. Styles can be added to the resources property of the window, the application… This depends on the scope from which you want to access the style, and this will be discussed later in this document. In this case, the style is added to the resources of a Page in a WPF Browser Application. This results in the style being available on that particular page: the scope of the style is the page on which it resides.

<Page.Resources>
  <Style x:Key="ButtonStyle">
    <Setter Property="Control.Background" Value="BlueViolet" />
    <Setter Property="Control.Foreground" Value="White" />
    <Setter Property="Control.FontSize" Value="20" />
    <Setter Property="Control.Width" Value="50" />
    <Setter Property="Control.Height" Value="30" />
  </Style>
</Page.Resources>

The code for the button has been reduced to the following:

<Button x:Name="GoButtonWithStyle"
  Content="Go"
  Style="{StaticResource ButtonStyle}">
</Button>

The resulting button looks exactly the same. However, the code for the button doesn’t. Let’s examine the code in detail.
We defined a style with a name, specified by the Key attribute in the Style element. The style can be accessed using this name in XAML and in the “code-behind”. We’ll look into that later.
The style can include as many properties as needed, and each of them is added using a Setter element. In this element, the property and the value we want the property to receive, is specified.
The actual control declaration doesn’t contain any visual properties; it does contain a Style property, which refers to the style ButtonStyle.

StaticResource means that this resource is to be read only once, i.e. at the loading of the page. Another possibility here is DynamicResource

The Control-prefix is used here instead of Button. This way, it’s possible to use this style on other controls.

This type of style is a named style: to have the style applied on a control, the control should include the Style property with the name of the style as value.


Target: TargetType

In the previous example, the type prefix before each property. However, it is possible to move this prefix into the TargetType attribute, and remove it from the properties.

The following code gives the same result. Note the TargetType attribute in the Style element.

<Page.Resources>
    <Style x:Key="ButtonStyle" TargetType="{x:Type Control}">
      <Setter Property="Background" Value="BlueViolet" />
      <Setter Property="Foreground" Value="White" />
      <Setter Property="FontSize" Value="20" />
      <Setter Property="Width" Value="50" />
      <Setter Property="Height" Value="30" />
    </Style>...
</Page.Resources>

Since the TargetType specifies on which type of controls the style can be applied on, only properties that belong to the specified type can be included. For example, the following code includes an “IsDefault” property. This property is available on the Button-type, but not on Control. Hence, we’ll get an error if we try to compile.

<!--Will not compile since Control has no IsDefault property -->
<Page.Resources><Style x:Key="ButtonStyle" TargetType="{x:Type Control}">
      <Setter Property="Background" Value="BlueViolet" />
      <Setter Property="Foreground" Value="White" />
      <Setter Property="FontSize" Value="20" />
      <Setter Property="Width" Value="50" />
      <Setter Property="Height" Value="30" />
      <Setter Property="IsDefault" Value="true" />
 </Style>
</Page.Resources>

We can resolve this by specifying Button as the TargetType.

<Page.Resources><Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
      <Setter Property="Background" Value="BlueViolet" />
      <Setter Property="Foreground" Value="White" />
      ...
 </Style>
</Page.Resources>

Apart from being easier to type, the TargetType has another, much more useful function. It can be used to give all controls of a certain type (i.e. Button) the same appearance, a default style let’s say. To achieve this, you must omit the Key attribute.
A small example makes this clear.

<Page.Resources>
  <Style TargetType="{x:Type Button}">
    <Setter Property="Background" Value="BlueViolet" />
    <Setter Property="Foreground" Value="White" />
    <Setter Property="FontSize" Value="20" />
    <Setter Property="Width" Value="50" />
    <Setter Property="Height" Value="30" />
    <Setter Property="Margin" Value="5"/>
  </Style>
  <Style TargetType="{x:Type TextBox}">
    <Setter Property="Background" Value="White" />
    <Setter Property="FontSize" Value="15" />
    <Setter Property="Width" Value="200" />
    <Setter Property="Height" Value="30" />
  </Style>
</Page.Resources>
<StackPanel>
  <Button x:Name="GoButton1" Content="Go" ></Button>
  <Button x:Name="GoButton2" Content="Go" ></Button>
  <Button x:Name="GoButton3" Content="Go" ></Button>
  <Button x:Name="GoButton4" Content="Go" ></Button>
  <TextBox x:Name="EmptyTextBox" Text="Hello!"></TextBox>
</StackPanel>

Here, 2 styles are declared, one with a TargetType attribute Button, and another one with TargetType TextBox. No Key attribute has been added though, if we did include one, we would be creating a named style.

Now, whenever a button is created without specifying a Style, the style with TargetType “Button” will be automatically applied. Of course, the same goes for the TextBox.
These styles can be considered as being a default style: all controls of a certain type will use it, unless they have another style applied to them, using the Style attribute.


Inheritance with styles

Like classes, styles can inherit from other styles. This way, you can create a base style, and have one or more derived styles. In these styles, you can add extra properties. When the derived style is applied on a control, both the properties from the base and from the derived style will be applied.

Have a look at the next sample.

<Page.Resources>
    <Style x:Key="ButtonBaseStyle">
      <Setter Property="Button.Background" Value="BlueViolet" />
      <Setter Property="Button.FontSize" Value="20" />
      <Setter Property="Button.Width" Value="200" />
      <Setter Property="Button.Height" Value="30" />
    </Style>
    <Style x:Key="ButtonDerivedStyle" BasedOn="{StaticResource ButtonBaseStyle}">
      <Setter Property="Button.Foreground" Value="FloralWhite" />
    </Style>
</Page.Resources>
<StackPanel>
    <Button x:Name="GoButtonWithBaseStyle" 
      Style="{StaticResource ButtonBaseStyle}"  Content="Go" >
    </Button>
    <Button x:Name="GoButtonWithDerivedStyle" 
      Style="{StaticResource ButtonDerivedStyle}"  Content="Go" >
    </Button>
</StackPanel>

We have 2 styles here: ButtonBaseStyle and ButtonDerivedStyle. The BasedOn attribute in the latter one results in all the properties of the base style, ButtonBaseStyle, being also available.

The result looks like this:


Figure 3: A base style and a derived style
Figure 3: A base style and a derived style


As can be seen, all the properties declared in the first style, are also applied to the control using the derived style.


Overriding style properties

When inheritance exists between styles, overriding is bound to exist too… And yes, it is included in XAML!

When applying a style, it’s possible that for a particular control, one or more properties should not to follow the style. No problem, we can simply declare them as we would do without styles. The property declared on the control itself will take precedence over the declaration in the style.

In the next sample, we have a style and apply it to a control. Since that particular button should stand out from the rest, it should get another background color.

<Page.Resources>
  <Style x:Key="ButtonPurpleStyle">
      <Setter Property="Button.Background" Value="BlueViolet" />
      <Setter Property="Button.FontSize" Value="20" />
      <Setter Property="Button.Width" Value="200" />
      <Setter Property="Button.Height" Value="30" />
  </Style>
</Page.Resources>
...
<Button x:Name="GoButtonWithBaseStyle" 
Style="{StaticResource ButtonBaseStyle}" 
Background="LightGreen"  
Content="Go with green!" ></Button>

When using inheritance, we can also have a derived style override a property set in the base style. Simply declare it in the derived style, and only the property from the derived class will be used.


Where to declare your styles

Based on where you want your style to be available, it can be defined on different locations in your code.

The largest scope is Application scope. When declaring the style as a part of the Application resources in your App.xaml file, every control in our entire application has access to the style. This can be handy when declaring default styles.

Your App.xaml file should look like the following:

<Application x:Class="WinFxBrowserApplication1.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Page1.xaml"
    >
    <Application.Resources>
      <Style x:Key="ApplicationWideStyle">
        <Setter Property="Button.Background" Value="BlueViolet" />
        ...
      </Style>
    </Application.Resources>
</Application>

If we want the style to be available only on a single page or window, we can add it to the resources of that page or window.

<Page>
  <Page.Resources>
    <Style x:Key="PageWideStyle">
      <Setter Property="Button.Background" Value="BlueViolet" />
      ...
    </Style>
  </Page.Resources>
  ...
</Page>

Or, if our style should only be applied inside another element, say a StackPanel, we can add it to the resources of that element.

<StackPanel>
    <StackPanel.Resources>
      <Style x:Key="ButtonPurpleStyle">
        <Setter Property="Button.Background" Value="BlueViolet" />
        ...
      </Style>
    </StackPanel.Resources>
    ...
</StackPanel>

Change the style programmatically

In .NET 2.0, a lot of new personalization features were added, for example, it became very easy to load a different theme based on the logged-in user. These features are part of the personalization features in the framework.

In WPF, we can also change the style from code. Once a style is declared in markup, it is available from code also.
Suppose we have 2 styles in our application. Based on the date in the year, we want to use the summer style or the winter style. Using what we know about styles already, this cannot be done from XAML, so we’ll have to write some “code-behind” to do this.

Here, 2 styles are defined in the XAML.

<Page.Resources>
    <Style x:Key="WinterStyle">
      <Setter Property="Button.Background" Value="AliceBlue" />
      <Setter Property="Button.FontSize" Value="20" />
      <Setter Property="Button.Width" Value="200" />
      <Setter Property="Button.Height" Value="30" />
      <Setter Property="Button.Foreground" Value="SteelBlue" />
    </Style>
    <Style x:Key="SummerStyle">
      <Setter Property="Button.Background" Value="Yellow" />
      <Setter Property="Button.FontSize" Value="20" />
      <Setter Property="Button.Width" Value="200" />
      <Setter Property="Button.Height" Value="30" />
      <Setter Property="Button.Foreground" Value="Orange" />
    </Style>
  </Page.Resources>

The control on which we’ll apply the style programmatically look as follows. Note that no Style has to be applied in the markup.

<Button x:Name="WelcomeButton"  Content="Welcome user!" ></Button>

In the code, an event that fires when the page loads, is added:

void Page_Load(object sender, RoutedEventArgs e) {
    if (DateTime.Now.Month > 8 || DateTime.Now.Month < 3) {
        WelcomeButton.Style = (Style)FindResource("WinterStyle");
    } else {
        WelcomeButton.Style = (Style)FindResource("SummerStyle");
    }
}

By means of the FindResource method, the style that has been defined in the XAML code, can be found in code. A specific cast is necessary.

If you’re running this example in the winter, you’ll see the following.


Figure 4: The button with the Winter Style
Figure 4: The button with the Winter Style


In the summer, the example will have more summer-style colors.


Figure 5: The button with the Summer Style
Figure 5: The button with the Summer Style



Trigger the action: using styles with triggers

When applying a style to a control, all properties defined in setter elements are unconditionally applied to the control. If that is not the desired behavior, we can use triggers.

Triggers allow us to define several Setter elements in a style. But, contrary to the previous situation, not all these Setter elements will be applied to the controls using the particular style. The trigger includes a condition, and when that condition is true, the Setters defined in the trigger will be applied. When the condition is false, the Setters are ignored.

We can check 3 kinds of properties in a trigger, hence, we have 3 kinds of triggers. We’ll now look at each of them.


Property triggers

Modern applications make intensive use of interactive UI elements: highlighting buttons when the mouse hovers over them, rollover images. Most of the time, these effects are achieved through the use of some scripting code, like Javascript in the case of web applications.

In WPF, it’s not necessary to write scripting code. Instead, property triggers can help us create the same compelling effects. Property triggers are, like all triggers defined inside a style, but when applied to a certain object, they fire when a certain property of that object itself changes. This way, one or more properties of the object can be changed.

Let’s take a look at some code examples.

<Style TargetType="{x:Type Button}">
  <Setter Property="Button.Background" Value="AliceBlue" />
  ...
  <Style.Triggers>
    <Trigger Property="IsMouseOver" Value="True">
      <Setter Property="Button.Background" Value="Yellow" />
    </Trigger>
  </Style.Triggers>
</Style> 

In this style, we have added the Style.Triggers element, which contains one Setter element, that is not applied by default to the control. The property being checked is the IsMouseOver, and when true, the trigger fires and properties defined in its Setters are applied. In this case, this results in a button that changes color whenever the mouse hovers over it.

There are 2 important things to note. First, we did not have to write any code, it’s all handled by WPF. Secondly, there is no Setter to counteract the first one. When the condition is no longer valid, WPF reverts the control back to its previous state.

We are not limited in the number of triggers included in a Style, and each trigger can have as many Setter elements as we want, like in the next sample.

<Style TargetType="{x:Type Button}">
  <Setter Property="Button.Background" Value="AliceBlue" />
  <Setter Property="Button.Opacity" Value="0.5" />       ...
  <Style.Triggers>
    <Trigger Property="IsMouseOver" Value="True">
      <Setter Property="Button.Opacity" Value="1"></Setter>
      <Setter Property="Button.Background" Value="Green"></Setter>
    </Trigger>
    <Trigger Property="IsEnabled" Value="False">
      <Setter Property="Button.Background" Value="Yellow"></Setter>
    </Trigger>
  </Style.Triggers>
</Style>

Here, we now have 2 triggers, one of which contains 2 Setter elements. The conditions of both triggers are checked and when true, the properties are applied to the control.

So far we’ve had more than one trigger inside a style, but each of them only had one condition to check.What happens when the trigger should only fire when 2 or more conditions are true? For example: only change the background of a button when the mouse is over it AND the button is enabled.

For this purpose, we have so-called multi-condition property triggers.

Here’s some sample code using a multi-condition trigger.

<Page.Resources>
    <Style TargetType="{x:Type Button}">
      <Setter Property="Button.Background" Value="AliceBlue" />
      ...
      <Style.Triggers>
        <MultiTrigger>
          <MultiTrigger.Conditions>
            <Condition Property="IsMouseOver" Value="True"/>
            <Condition Property="IsEnabled" Value="True" />
          </MultiTrigger.Conditions>
          <Setter Property="Button.Background" Value="Yellow" />
        </MultiTrigger>
      </Style.Triggers>
    </Style>
  </Page.Resources>
  <StackPanel>
    <Button x:Name="WelcomeButton"  Content="Welcome user!" ></Button>
  </StackPanel>

We used a MultiTrigger element here, in which we specify both the conditions that have to be checked. If needed, more can be added in an analogue manner. One or more normal Setters are applied when all of these conditions are true.

As can be seen, property triggers are a very easy-to-use way to change our application’s interface based on one or more conditions and they help increase the user experience with effects which in the past, cost a lot more effort to create.


Data triggers

As we’ve seen, property triggers are used to check on WPF dependency properties, like the IsMouseOver property. However, there will be times when we have to check the value of a property of a non-visual .NET object, like a self-created User object for example. For this purpose, data triggers come in handy.

In the following sample, we’ll be creating and filling a listbox. Each item represents a user, an instance of the User class. Based on the value of the Role property, the user will receive another color. To accomplish this, I’ll use a style with a data trigger that fires whenever the specified condition is true.

Let’s create a User class, with 2 string properties: name and role.

namespace Demo {
    public class User {
        private string name;
        private string role;

        public User(string name, string role) {
            this.name = name;
            this.role = role;
        }

        public string Name {
            get {return name;}
            set { name = value; }
        }

        public string Role {
            get { return role; }
            set { role = value; }
        }
    }
}

Note the namespace Demo, we’ll need it later on.

Within the same namespace, let’s create a class Users.

public class Users : ObservableCollection<User> {
    public Users() {
        this.Add(new User("Gill Cleeren", "Admin"));
        this.Add(new User("Steve Smith", "Contributor"));
        this.Add(new User("John Miller", "User"));
    }
}

This class inherits from ObservableCollection, which represents a dynamic data collection. ObservableCollection provides methods to notify when items are added to or removed from the collection. Since we provide User as type parameter, only User instances can be added to the collection.

Now, let’s get back to the XAML-code. As said earlier, we want to display the users in a listbox.

<Page x:Class="Demo.Page1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Page1"
    xmlns:clr="clr-namespace:Demo"
    >
        <Page.Resources>
            <clr:Users x:Key="myUsers" />
            <DataTemplate DataType="{x:Type clr:User}">
                <TextBlock Text="{Binding Path=Name}"/>
            </DataTemplate>
            ...
        </Page.Resources>
        <StackPanel>
            <ListBox Width="200" 
                ItemsSource="{Binding Source={StaticResource myUsers}}" />
        </StackPanel>
</Page>

Since we’ll be working with the Users collection in the XAML code, it has to be made available in that context. Therefore, we have to create a mapping between a CLR namespace and a namespace in WPF, using the following syntax.

xmlns:clr="clr-namespace:Demo"

The namespace Demo is mapped to a namespace in WPF, which is called clr. Note that the name clr is not obligatory.

Within the resources of the Page in the XAML code, we can now use the clr-prefix to have WPF create an instance of the Users class, using the default constructor. The value myUsers, specified in the x:Key attribute, can be used to access the instance.

The DataTemplate is used to tell WPF how it should display a non-visual object like a User. In this case, a TextBlock containing the name will be used.

In the StackPanel, I declare a ListBox. This ListBox will bind its ItemsSource to the Users instance, myUsers, which is a collection of User instances.

Now, let’s add the trigger functionality.

<Style TargetType="{x:Type ListBoxItem}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=Role}" Value="Admin">
            <Setter Property="Foreground" Value="Red" />
        </DataTrigger>
    </Style.Triggers>
</Style>

Here a data trigger is used. The condition whether the trigger should fire is based on the value of the Role property of the specified User object: if it has the value “Admin”, the trigger’s condition will become true, and the property declared within the Setter will be applied.

The TargetType of the Style is ListBoxItem. Whenever the Role property of the User instance to which a ListBoxItem is bound, has the value “Admin”, the foreground will be set to red.

And this is the result, with the first user colored red:


Figure 6: Listbox with a data trigger applied
Figure 6: Listbox with a data trigger applied



Event triggers

To finish our trigger-tour, we have to make a stop at the event triggers. Event triggers are used to watch for events to happen, like a click-event or a mouse-over event. They are used in combination with animation: whenever the event is raised, the event trigger fires and starts an animation action. (For more information on Animation, see the SDK).

In the sample code, we’ll use an event trigger to react to the mouse-over event for a button.

<Window.Resources>
    <Style TargetType="{x:Type Button}">
      <Setter Property="Width" Value="200" />
      <Setter Property="Height" Value="100" />
      <Setter Property="Margin" Value="20" />
      <Setter Property="HorizontalAlignment" Value="Left" />
      <Style.Triggers>
        <EventTrigger RoutedEvent="Button.MouseEnter">
          <EventTrigger.Actions>
            <BeginStoryboard>
              <Storyboard>
                <DoubleAnimation To="300" Duration="0:0:3" 
                  Storyboard.TargetProperty="(Button.Width)" />
		   <DoubleAnimation To="200" Duration="0:0:3" 
                  Storyboard.TargetProperty="(Button.Height)" />
              </Storyboard>
            </BeginStoryboard>
          </EventTrigger.Actions>
        </EventTrigger>
        <EventTrigger RoutedEvent="Button.MouseLeave">
          <EventTrigger.Actions>
            <BeginStoryboard>
              <Storyboard>
                <DoubleAnimation Duration="0:0:3" 
                  Storyboard.TargetProperty="(Button.Width)" />
		  <DoubleAnimation  Duration="0:0:3" 
                  Storyboard.TargetProperty="(Button.Height)" />
              </Storyboard>
            </BeginStoryboard>
          </EventTrigger.Actions>
        </EventTrigger>
      </Style.Triggers>
    </Style>
</Window.Resources>
<StackPanel>
  <Button x:Name="GrowButton" Content="Hello MSDN" />
</StackPanel>

The style now includes 2 event triggers. The first one will react to the mouse starting to hover over the Button, the second one will react to the mouse leaving the button surface.

When either one of the events is raised, the actions specified for the trigger that reacts to the event, will be executed.
The result is a button that expands both in width and height when hovering over it.


Figure 7: When hovering over the button, it expands due to the attached event trigger
Figure 7: When hovering over the button, it expands due to the attached event trigger


Note that we also have to include code that will return the button to its original state here, which wasn’t necessary for the other types of triggers. This is due to the fact that event triggers have no concept of termination of state, so the property won’t be changed back to the state it was in before the trigger fired.

As you can see, using triggers is fairly easy and they make your applications much more interactive, without having to write much code.

About the author:


About the author

Gill Cleeren (MCP ASP.NET) is a .NET developer at Ordina Belgium. He focuses on ASP.NET development and lately, Windows Presentation Foundation has attracted his attention. At Ordina, Gill is part of the Task Force ‘Ontwikkelstraten’ within the Microsoft .NET Framework & Tooling Competence Center.  In his spare time, he can be found behind a laptop, playing around with some CTP or blogging on blog.n-technologies.be and on his site, www.snowball.be .
Gill Cleeren