WPF Style, Resource and Template

Coordinator
Jun 10, 2009 at 4:00 AM

This article will show how to use Styles, Resources, and Control Templates in WPF.

Resources

Resources store reference to an object within a collection. For instance, most objects in the WPF framework have a Resources collection. The Application object, Window, Button, and other WPF controls all have a Resources collection. A resource can be anything, such as a Brush to use for painting the background, a control template (which we'll look at later), or many other types of objects. Below is an example of a brush defined in the windows resource collection.

  1. <Window.Resources>  
  2.     <LinearGradientBrush x:Key="BlueButtonBackground">  
  3.         <GradientStop Color="AliceBlue" Offset="0" />  
  4.         <GradientStop Color="Blue" Offset=".7" />  
  5.     </LinearGradientBrush>  
  6. </Window.Resources>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t1"><Window.Resources> <LinearGradientBrush x:Key="BlueButtonBackground"> <GradientStop Color="AliceBlue" Offset="0" /> <GradientStop Color="Blue" Offset=".7" /> </LinearGradientBrush> </Window.Resources> </textarea>

In the previous example, the Window defines a gradient brush with a key of BlueButtonBackground. The background can be referenced using the BlueButtonBackground key like so:

  1. <Button Background="{StaticResource BlueButtonBackground}" ... />  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t2"><Button Background="{StaticResource BlueButtonBackground}" ... /> </textarea>

StaticResource is an extension that is used to reference items defined in a resource collection. If not found in the immediate element's resource collection (the button's), it navigates up the stack until it can find the resource with that key. If the button resides in a StackPanel control - which resides in a Grid - the resource will be sought for in the button first, StackPanel second, Grid third, Window fourth, and so on. Resources can contain anything, and the last section on Control Templates will illustrate how the control template itself will be defined and referenced as a resource.

Resources can also be stored in a central place, such as a resource dictionary. A resource dictionary is a collection of resources that can be easily incorporated into an application. They can be used to contain a single reference to all the assemblies in a single or multiple applications. The following is a simple resource dictionary.

  1. <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  2.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">  
  3.     <LinearGradientBrush x:Key="BlueBackground">  
  4.         <GradientStop Color="AliceBlue" Offset="0" />  
  5.         <GradientStop Color="Blue" Offset=".7" />  
  6.     </LinearGradientBrush>  
  7.   
  8.     <LinearGradientBrush x:Key="MouseOverBlueBackground">  
  9.         <GradientStop Color="Blue" Offset="0" />  
  10.         <GradientStop Color="Navy" Offset=".9" />  
  11.     </LinearGradientBrush>  
  12. </ResourceDictionary>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t3"><ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <LinearGradientBrush x:Key="BlueBackground"> <GradientStop Color="AliceBlue" Offset="0" /> <GradientStop Color="Blue" Offset=".7" /> </LinearGradientBrush> <LinearGradientBrush x:Key="MouseOverBlueBackground"> <GradientStop Color="Blue" Offset="0" /> <GradientStop Color="Navy" Offset=".9" /> </LinearGradientBrush> </ResourceDictionary> </textarea>

To include a resource dictionary in a Window or user control, include the following definition:

  1. <Window.Resources>  
  2.     <ResourceDictionary>  
  3.         <ResourceDictionary.MergedDictionaries>  
  4.             <ResourceDictionary Source="BasicBlueButtons.xaml" />  
  5.         </ResourceDictionary.MergedDictionaries>  
  6.     </ResourceDictionary>  
  7. </Window.Resources>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t4"><Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="BasicBlueButtons.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources> </textarea>

The referenced resource dictionary is now included in the user control, and any resources are automatically applied.

Styles

With ASP.NET 2.0 came a feature called themes. Themes had the ability to set styles to the inner style properties of a control. Any property that defined a ThemeableAttribute with a constructor value of true could assign that property in the skin file, using the same markup as the control (for the most part). Styles do not have the same markup as themes do, but the approach is the same.

A style can set the inner properties of a XAML element using setters, as will be illustrated soon. We can define event triggers, which are styles that are applied when an event occurs. An even more useful approach is to use a style trigger, which applies itself whenever a target condition is evaluated to true. For instance, it is possible to change the background color and foreground color whenever a button is pressed, and then revert back to the original setting when that event is false. This works because of the change notification that elements provide when the value exposed by a property changes.

An example style is shown below:

  1. <Style x:Key="BlueBackgroundStyle" TargetType="{x:Type Button}">  
  2. <Setter Property="Control.Background">  
  3.         <Setter.Value>  
  4.             <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">  
  5.                 <GradientStop Color="AliceBlue" Offset="0" />  
  6.                 <GradientStop Color="Blue" Offset="1" />  
  7.             </LinearGradientBrush>  
  8.         </Setter.Value>  
  9.     </Setter>  
  10.     <Setter Property="Control.FontSize" Value="12" />  
  11. </Style>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t5"><Style x:Key="BlueBackgroundStyle" TargetType="{x:Type Button}"> <Setter Property="Control.Background"> <Setter.Value> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Color="AliceBlue" Offset="0" /> <GradientStop Color="Blue" Offset="1" /> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="Control.FontSize" Value="12" /> </Style> </textarea>

This style is defined in the Window's Resources collection. The style is given a key, so it can be uniquely referenced. A series of setters can be defined in order to specify which properties will be changed. In the example above, only two setters are defined, but you could have continued on with many more entries. In the following example, the style changes the background property to a gradient brush. To use this style, the following code defines a static resource extension and embeds it in a control's background:

  1. <Button Style="{StaticResource BlueBackgroundStyle}" Content="Click Styled Button" />  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t6"><Button Style="{StaticResource BlueBackgroundStyle}" Content="Click Styled Button" /> </textarea>

In this situation, a style is applied to a button. But it doesn't have to be; it could be applied to many types of elements. Suppose that the only target for this style was a button; using a different property, we can apply it to only buttons:

  1. <Style TargetType="{x:Type Button}">  
  2.    ...   
  3. </Style>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t7"><Style TargetType="{x:Type Button}"> ... </Style> </textarea>

In this situation, the style is applied to all buttons, depending on where it is defined (if in the application, Window, resource dictionary, or local resource collection). TargetType looks for any instance of a button and applies the appropriate style. With this approach, the Control prefix on the setter property is no longer needed.

Triggers can be used for styles as well. For instance, a button has several properties it can use to determine what state it is in. It has an IsPressed property that is used to determine whether a mouse button is currently pressed, an IsEnabled property that determines if the button is enabled, an IsFocused property for determining whether the control has focus (such as when tabbing through the controls on a form), and many more. Each of these properties can be used in a style trigger to perform some action. Take the definition below:

  1. <Style x:Key="BlueBackgroundStyle" TargetType="{x:Type Button}">  
  2.     <Style.Triggers>  
  3.         <Trigger Property="IsPressed" Value="True">  
  4.             <Setter Property="Background">  
  5.                 <Setter.Value>  
  6.                     <LinearGradientBrush>  
  7.                         <GradientStop Color="Blue" Offset="0" />  
  8.                         <GradientStop Color="Navy" Offset=".7" />  
  9.                     </LinearGradientBrush>  
  10.                 </Setter.Value>  
  11.             </Setter>  
  12.         </Trigger>  
  13.     </Style.Triggers>  
  14. </Style>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t8"><Style x:Key="BlueBackgroundStyle" TargetType="{x:Type Button}"> <Style.Triggers> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush> <GradientStop Color="Blue" Offset="0" /> <GradientStop Color="Navy" Offset=".7" /> </LinearGradientBrush> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style> </textarea>

Upon pressing the button, the background changes from a lighter gradient to a darker one. This happens because the control has its own change notification. When the button is pressed, the "swap is made". Triggers require that the comparison is an equality comparison. A trigger can't be used to determine if a property is within a specific range. Rather, it often checks if a value matches its condition exactly.

Styles also have the ability to inherit from each other, by specifying the key of another style in the BasedOn attribute. The following is a style that inherits from another style. All of the base styles are applied, and additional ones are defined in the new style shown below.

  1. <Style BasedOn="OtherStyle">  
  2.     ...   
  3. </Style>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t9"><Style BasedOn="OtherStyle"> ... </Style> </textarea>

As you can see, style inheritance is easy to do.

Control Templates

Each control has a base template that it uses to render its user interface. This interface is a template and is instantiated whenever the control is displayed. The control itself is made up of a lot of different parts; but often includes styles and style triggers, and utilizes resources in several ways.

A control template is actually an element that one can define in a resources collection, or in a resource dictionary. The following declaration is perfectly valid:

  1. <Window.Resources>  
  2.     <ControlTemplate x:Key="SomeTemplate" TargetType="{x:Type Button}">  
  3.         ...   
  4.     </ControlTemplate>  
  5. </Window.Resources>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t10"><Window.Resources> <ControlTemplate x:Key="SomeTemplate" TargetType="{x:Type Button}"> ... </ControlTemplate> </Window.Resources> </textarea>

To use this template, a button must be declared as follows:

  1. <Button Padding="3" Template="{StaticResource SomeTemplate}">Templated    
  2.   
  3. Button</Button>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t11"><Button Padding="3" Template="{StaticResource SomeTemplate}">Templated Button</Button> </textarea>

Using this approach, the following template can add a rounded corner border to a button control. In addition, the button has an inner white square border, and includes a commonly-used text statement at the top.

  1. <ControlTemplate x:Key="BlueButtonTemplate" TargetType="{x:Type Button}">  
  2.     <Border BorderBrush="Navy" BorderThickness="1" CornerRadius="5" Background="CornflowerBlue">  
  3.         <Border BorderBrush="White" BorderThickness="3" Padding="10" Margin="10">  
  4.             <Grid>  
  5.                 <Grid.RowDefinitions>  
  6.                     <RowDefinition/>  
  7.                     <RowDefinition/>  
  8.                 </Grid.RowDefinitions>  
  9.                    
  10.                 <Label Content="Please welcome:" Grid.Row="0" />  
  11.                 <ContentPresenter Grid.Row="1" />  
  12.             </Grid>  
  13.         </Border>  
  14.     </Border>  
  15. </ControlTemplate>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t12"><ControlTemplate x:Key="BlueButtonTemplate" TargetType="{x:Type Button}"> <Border BorderBrush="Navy" BorderThickness="1" CornerRadius="5" Background="CornflowerBlue"> <Border BorderBrush="White" BorderThickness="3" Padding="10" Margin="10"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Label Content="Please welcome:" Grid.Row="0" /> <ContentPresenter Grid.Row="1" /> </Grid> </Border> </Border> </ControlTemplate> </textarea>

Our button has a rounded corner border consisting of blue colors. Note the ContentPresenter declaration; this declaration allows the inner content of the button to be bound inside the control. So the text "Templated Button" is rendered inside the templated button.

Styles can be used to apply a control template. For example, it is possible to apply a style template using the following style (note that the style is still set the same way, by applying it through the Style property).

  1. <Style x:Key="BlueButtonTemplateStyle" TargetType="{x:Type Button}">  
  2.     <Setter Property="Control.Template" Value="{StaticResource BlueButtonTemplate}" />  
  3. </Style>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t13"><Style x:Key="BlueButtonTemplateStyle" TargetType="{x:Type Button}"> <Setter Property="Control.Template" Value="{StaticResource BlueButtonTemplate}" /> </Style> </textarea>

In addition, a template can be applied automatically, simply by using the TargetType attribute on a style. The following applies the control template to all buttons:

  1. <Style TargetType="{x:Type Button}">  
  2.     <Setter Property="Control.Template" Value="{StaticResource BlueButtonTemplate}" />  
  3. </Style>  
<textarea class="xml" style="display: none;" cols="20" rows="2" name="t14"><Style TargetType="{x:Type Button}"> <Setter Property="Control.Template" Value="{StaticResource BlueButtonTemplate}" /> </Style> </textarea>

A button can ignore this style by manually applying a style with the {x:Null} property value.

Summary

Resources and styles are great ways to modify the basic appearance of a .NET application. Control templates still rely on these capabilities to provide a stellar user interface. Resources provide an excellent way to define certain objects in a global location, and resource dictionaries provide a way to make resources available at a global scale. Styles are really dynamic in their ability to set property values, because they can listen to property changes and apply certain values through a trigger; only when the trigger condition is met.

I discussed control templates from the perspective of styles and resources. However, control templates have the ability to customize the user interface. Although there are some precautions to take, control templates have the powerful ability to completely redesign the user interface.

In the examples provided, the styles, resources, and control templates are mostly embedded in the main Window. However, it would be a better approach to split them into a more centralized place, like a resource dictionary.