Styles and triggers in WPFGill Cleeren
Applies to:Ordina Belgium
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.
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.
![]() When to use a styleLet’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
Nothing wrong with that… But knowing that WPF is a presentation framework, we can do a lot better!
<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
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.
![]() Named stylesWe’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.
The Control-prefix is used here instead of Button. This way, it’s possible to use this style on other controls.
![]() 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.
<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.
<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.
![]() 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.
<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.
![]() 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!
<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.
<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.
<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.
![]() 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 ![]() 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.
![]() 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.
<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.
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.
<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.
![]() 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.
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.
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.
<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.
<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.
![]() 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).
<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.
![]() 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
| ||||||