5,276,156 members and growing! (20,516 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Windows Presentation Foundation » Templates     Beginner License: The Code Project Open License (CPOL)

WPF: A Beginner's Guide - Part 6 of n

By Sacha Barber

An introduction into WPF Styles And Templates
C# (C# 3.0, C#), .NET (.NET, .NET 3.0, .NET 3.5), WPF, Design, Dev

Posted: 11 Mar 2008
Updated: 5 Apr 2008
Views: 26,056
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
47 votes for this Article.
Popularity: 7.95 Rating: 4.75 out of 5
0 votes, 0.0%
1
0 votes, 0.0%
2
5 votes, 11.1%
3
2 votes, 4.4%
4
38 votes, 84.4%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Preface And Thanks

I am a .NET programmer, but a busy one, I do VB .NET and C#, ASP .NET / Winforms / WPF / WCF Flash Silverlight the lot. Basically I keep my toe in. But when I started writing this article series I naturally chose my favourite language (which happens to be C#). I since got an email from an individual who requested that I publish this series with source code in VB .NET and C#. I simply stated I didn't have time. So this individual (Robert Ranck) volunteered to help out, and do the translation to VB .NET, based on my orginal C# projects

So for that and the subsequent VB .NET projects that you will find here I ask you to thank Robert Ranck. Cheers Robert, your contributions will surely make this series more open to all .NET developers.

And another thanks also goes out Karl Shifflett (AKA the blog/article machine, also known as the Molenator) for answering my dumb VB .NET questions. And I'd also like to mention that Karl has just started a more advanced series of WPF articles (which at present will be in VB.NET, but will hopefully appear in C# as well). Karls new series will be excellent and I urge you all to encourage Karl on this series. Its not easy obligating ones self to write an entire series in one language let alone 2. Karls 1st article is located right here, and he has also published part2 now, go have a look for yourself. Personally I love them.

Introduction

This article is the 6th in my series of beginners articles for WPF. In this article we will discuss Styles/Templates. The proposed schedule for this series has been as follows:

In this article I'm aiming to cover, is a brief introduction into the following:

I will NOT be covering the usage on animations within Styles/Templates. Josh Smith has an excellent usage of animations within a Style, within this article. There is also a good MSDN article about this right here should you want to do that

What Is This Article All About

If you are reading this and have ever tried to create an owner drawn tab/customised a button (you know override the OnPaint() and OnPaintBackGround()) then you will probably know that creating custom controls that look different to the standard controls is do-able but just not that much fun.

I've done a fair ammount of WinForms custom/user controls, and I have no love for all that method overriding and mouse handling, and have often thought that there must be a better way.

WPF addressed all of this, by creating 2 UI design pillars, one called STYLES and one called TEMPLATING. This article covers both of these within a WPF environment.

What are Styles

Overview

Simply put Styles allow a WPF developer to maintain a common list of property values within a convenient place to store all these property values. It is somewhat similar to how CSS works within web based development. Typically Styles will be maintained within a Resource section or a seperate Resource dictionary. It is also by using Styles that WPF is able to cater for theme aware controls. There is an excellent post Chazs blog about how to do this.

In this article I dont want to get to bogged down in how to create themes, I just want to cover the basics, so I'm going to show you a few things that are available within a Style, but after that, I shall be concentrating on the main areas of Styles that you will use most often.

For a Style the following properties are available

Name Description
BasedOn Gets or sets a defined style that is the basis of the current style
Dispatcher Gets the Dispatcher this DispatcherObject is associated with. (Inherited from DispatcherObject.)
IsSealed Gets a value that indicates whether the style is read-only and cannot be changed.
Resources Gets or sets the collection of resources that can be used within the scope of this style.
TargetType Gets or sets the type for which this style is intended.
Setters Gets a collection of Setter and EventSetter objects.
Triggers Gets a collection of TriggerBase objects that apply property values based on specified conditions.

Out of these by far the most import properties are

  • BasedOn
  • TargetType
  • Setters
  • Triggers

So I think it's worth have a quick look into each of these bits on syntax.

BasedOn

This is like inheritence. Where one Style inherits common properties from another Style. Each style only supports one BasedOn value. Here is a small example

<Style x:Key="Style1">
...
</Style>

<Style x:Key="Style2" BasedOn="{StaticResource Style1}">
...
</Style>

TargetType

The Target type property is used to limit which controls may use a particular style. For example if we had a Style with a TargetType property set to Button this Style could not be used against a TextBox type control.

Setting a valid TargetType property is as simple as the following:

<Style TargetType="{x:Type Button}">
....
</Style>

Setters

Setters are simply things really. They simply set a event or a property to some value. In the case of setting an event, they wire up an event. In the case of setting a property they set a property to a value.

EventSetters for events, would be something like this, where a Styled Buttons Click event is getting wired up.

<Style TargetType="{x:Type Button}">
    <EventSetter Event="Click" Handler="b1SetColor"/>
</Style>

However, much more typically Setters are simply used to set a property to a value. Maybe something like this:

<Style TargetType="{x:Type Button}">
    <Setter Property="BackGround" Value="Yellow"/>
</Style>

Property Element Syntax

There are also occassions where you dont want the value to be a single value, but rather a complex chunk of XAML, comprising many elements. In order to do this, XAML allows developers to use the Property Element syntax. The most likely place you will see this in Styles, is within a Template Setter. Something like the following:

<!-- Tab Item Style -->
<Style x:Key="TabItemStyle1" TargetType="{x:Type TabItem}">
<Setter Property="FocusVisualStyle" Value="{StaticResource TabItemFocusVisual}"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Padding" Value="6,1,6,1"/>
<Setter Property="BorderBrush" Value="{StaticResource TabControlNormalBorderBrush}"/>
<Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type TabItem}">
            <Grid SnapsToDevicePixels="true" Margin="0,5,0,0">
                .....
                .....
                .....
            </Grid>
        </ControlTemplate>
    </Setter.Value>
</Setter>
</Style>

The important part here is the part where the Setter is split over several lines, by using the property value syntax.

<Setter Property="Template">
      <Setter.Value>
       .....
       .....
       .....
    </Setter.Value>
</Setter>

Triggers

The WPF styling and templating model enables you to specify Triggers within your Style. Essentially, Triggers are objects that enable you to apply changes when certain conditions (such as when a certain property value becomes true, or when an event occurs) are satisfied.

The following example shows a named Style available to Button controls. The Style defines a Trigger element that changes the Foreground property of a Button when the IsPressed property is true.

<Style x:Key="Triggers" TargetType="Button">
    <Style.Triggers>
        <Trigger Property="IsPressed" Value="true">
        <Setter Property = "Foreground" Value="Green"/>
        </Trigger>
    </Style.Triggers>
</Style>
There are some more types of Triggers that may be used in Styles

DataTriggers

Represents a Trigger that applies property values or performs actions when the bound data meets a specified condition.

The DataTrigger is specified such that if the State of the Place data item is "WA" then the foreground of the corresponding ListBoxItem is set to Red.

<Style TargetType="ListBoxItem">
    <Style.Triggers>
      <DataTrigger Binding="{Binding Path=State}" Value="WA">
        <Setter Property="Foreground" Value="Red" />
      </DataTrigger>    
    </Style.Triggers>
</Style>

There is also a special type of Trigger which uses more than 1 value for its consitional test. This is known as a Multitrigger. And all this does is use several conditions within a single MultiDataTrigger. Here is an example

  <Style TargetType="ListBoxItem">
    <Style.Triggers>
      <MultiDataTrigger>
        <MultiDataTrigger.Conditions>
          <Condition Binding="{Binding Path=Name}" Value="Portland" />
          <Condition Binding="{Binding Path=State}" Value="OR" />
        </MultiDataTrigger.Conditions>
        <Setter Property="Background" Value="Cyan" />
      </MultiDataTrigger>
    </Style.Triggers>
  </Style>

In this example the bound object must have a Name="Portland" and a State="OR", then the foreground of the corresponding ListBoxItem is set to Red.

EventTriggers

Are special Triggers which represents a Trigger that applies a set of actions in response to an event. These Eventriggers are strane in that they ONLY allow animations to be triggered. They do not allow normal properties to be set based, that is what the normal Triggers are for. And example Eventrigger may be something like the following

<EventTrigger RoutedEvent="Mouse.MouseEnter">
  <EventTrigger.Actions>
    <BeginStoryboard>
      <Storyboard>
        <DoubleAnimation
          Duration="0:0:0.2"
          Storyboard.TargetProperty="MaxHeight"
          To="90"  />
      </Storyboard>
    </BeginStoryboard>
  </EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
  <EventTrigger.Actions>
    <BeginStoryboard>
      <Storyboard>
        <DoubleAnimation
          Duration="0:0:1"
          Storyboard.TargetProperty="MaxHeight"  />
      </Storyboard>
    </BeginStoryboard>
  </EventTrigger.Actions>
</EventTrigger>

Example Styles Within The Demo App

The attached demo app uses a fair few Styles. Typically these are mixed with the usage of Templates, so its a bit tricky to isolate a single example.

Here is an example of a Style that has been setup and targets TabItems, this is a full Style so you can see that there is also Templates in here as well as the things we just discussed (you know Setters / TargetTypes and property element syntax...no triggers in this one)

<!-- Tab Item Style -->
<Style x:Key="TabItemStyle1" TargetType="{x:Type TabItem}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource TabItemFocusVisual}"/>
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="Padding" Value="6,1,6,1"/>
    <Setter Property="BorderBrush" Value="{StaticResource TabControlNormalBorderBrush}"/>
    <Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
    <Setter Property="HeaderTemplate">
        <Setter.Value>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding}"/>
                        <Button x:Name="btnClose" Margin="10,3,3,3" 
                                Template="{StaticResource closeButtonTemplate}" 
                                Background="{StaticResource buttonNormalBrush}" 
                                IsEnabled="True"/>
                </StackPanel>
            </DataTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabItem}">
                <Grid SnapsToDevicePixels="true" Margin="0,5,0,0">
                    <Border x:Name="Bd" Background="{TemplateBinding Background}" 
                            BorderBrush="{TemplateBinding BorderBrush}" 
                            BorderThickness="1,1,1,0" CornerRadius="10,10,0,0" 
                            Padding="{TemplateBinding Padding}">
                        <ContentPresenter SnapsToDevicePixels=
                            "{TemplateBinding SnapsToDevicePixels}" 
                            HorizontalAlignment="{Binding 
                            Path=HorizontalContentAlignment, 
                            RelativeSource={RelativeSource 
                            AncestorType={x:Type ItemsControl}}}" 
                            x:Name="Content" VerticalAlignment="
                            {Binding Path=VerticalContentAlignment, 
                            RelativeSource={RelativeSource 
                            AncestorType={x:Type ItemsControl}}}" 
                            ContentSource="Header" RecognizesAccessKey="True"/>
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="true">
                        <Setter Property="Background" TargetName="Bd" 
                            Value="{StaticResource TabItemHotBackground}"/>
                    </Trigger>
                    <Trigger Property="IsSelected" Value="true">
                        <Setter Property="Panel.ZIndex" Value="1"/>
                        <Setter Property="Background" TargetName="Bd" 
                            Value="{StaticResource TabItemSelectedBackground}"/>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="false"/>
                            <Condition Property="IsMouseOver" Value="true"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="BorderBrush" TargetName="Bd" 
                                Value="{StaticResource TabItemHotBorderBrush}"/>
                    </MultiTrigger>
                    <Trigger Property="TabStripPlacement" Value="Bottom">
                        <Setter Property="BorderThickness" TargetName="Bd" Value="1,0,1,1"/>
                    </Trigger>
                    <Trigger Property="TabStripPlacement" Value="Left">
                        <Setter Property="BorderThickness" TargetName="Bd" Value="1,1,0,1"/>
                    </Trigger>
                    <Trigger Property="TabStripPlacement" Value="Right">
                        <Setter Property="BorderThickness" TargetName="Bd" Value="0,1,1,1"/>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Top"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-2,-2,-2,-1"/>
                        <Setter Property="Margin" TargetName="Content" Value="0,0,0,1"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Bottom"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-2,-1,-2,-2"/>
                        <Setter Property="Margin" TargetName="Content" Value="0,1,0,0"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Left"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-2,-2,-1,-2"/>
                        <Setter Property="Margin" TargetName="Content" Value="0,0,1,0"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Right"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-1,-2,-2,-2"/>
                        <Setter Property="Margin" TargetName="Content" Value="1,0,0,0"/>
                    </MultiTrigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Background" TargetName="Bd" 
                                Value="{StaticResource TabItemDisabledBackground}"/>
                        <Setter Property="BorderBrush" TargetName="Bd" 
                                Value="{StaticResource TabItemDisabledBorderBrush}"/>
                        <Setter Property="Foreground" 
                                Value="{DynamicResource 
                            {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This Style and its associated Templates (which are not all shown here) are enough to change the TabItems from the standard visual representation into ones with round corners and close buttons like this. The top tabs are the standard un-styled tabs, and the other ones are the ones that have been Styled by yours truly.

The demo app also contains a Styles and associated Templates to create a totally new ScrollBar and ScrollViewer control representations, which are shown in the figure below ScrollBar on the left ScrollViewer on the right.

In order to do this, there needed to be quite a few Styles and associated Templates. Have a look at how much XAML it took. I have removed the guts of the the Templates as I have not discussed how they work yet. I just wanted to show you what it takes to totally re-Style a standard control. Obviously some are more compliacted than others. A Button control for example is trivial.

<!-- Brushses-->
<LinearGradientBrush x:Key="VerticalScrollBarBackground" 
                     EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#E1E1E1" Offset="0"/>
    <GradientStop Color="#EDEDED" Offset="0.20"/>
    <GradientStop Color="#EDEDED" Offset="0.80"/>
    <GradientStop Color="#E3E3E3" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="HorizontalScrollBarBackground" 
                     EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#E1E1E1" Offset="0"/>
    <GradientStop Color="#EDEDED" Offset="0.20"/>
    <GradientStop Color="#EDEDED" Offset="0.80"/>
    <GradientStop Color="#E3E3E3" Offset="1"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="ListBoxBackgroundBrush"
StartPoint="0,0" EndPoint="1,0.001">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="White" Offset="0.0" />
            <GradientStop Color="White" Offset="0.6" />
            <GradientStop Color="#DDDDDD" Offset="1.2"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="StandardBrush"
StartPoint="0,0" EndPoint="0,1">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#FFF" Offset="0.0"/>
            <GradientStop Color="#CCC" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="PressedBrush"
StartPoint="0,0" EndPoint="0,1">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#BBB" Offset="0.0"/>
            <GradientStop Color="#EEE" Offset="0.1"/>
            <GradientStop Color="#EEE" Offset="0.9"/>
            <GradientStop Color="#FFF" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<SolidColorBrush x:Key="ScrollBarDisabledBackground" Color="#F4F4F4"/>
<SolidColorBrush x:Key="StandardBorderBrush" Color="#888" />
<SolidColorBrush x:Key="StandardBackgroundBrush" Color="#FFF" />
<SolidColorBrush x:Key="HoverBorderBrush" Color="#DDD" />
<SolidColorBrush x:Key="SelectedBackgroundBrush" Color="Gray" />
<SolidColorBrush x:Key="SelectedForegroundBrush" Color="White" />
<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />
<SolidColorBrush x:Key="NormalBrush" Color="#888" />
<SolidColorBrush x:Key="NormalBorderBrush" Color="#888" />
<SolidColorBrush x:Key="HorizontalNormalBrush" Color="#888" />
<SolidColorBrush x:Key="HorizontalNormalBorderBrush" Color="#888" />
<SolidColorBrush x:Key="GlyphBrush" Color="#444" />

<!-- ScrollBarButton Vertical -->
<Style x:Key="VerticalScrollBarPageButton" TargetType="{x:Type RepeatButton}">
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="Focusable" Value="false"/>
    <Setter Property="IsTabStop" Value="false"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type RepeatButton}">
                ...
                ...
              </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<!-- ScrollBarButton Horizontal -->
<Style x:Key="HorizontalScrollBarPageButton" TargetType="{x:Type RepeatButton}">
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="Focusable" Value="false"/>
    <Setter Property="IsTabStop" Value="false"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type RepeatButton}">
                ...
                ...
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<!-- Scroll Buttons Down-->
<Style x:Key="RepeatButtonStyleDown" TargetType="{x:Type RepeatButton}">
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="Focusable" Value="false"/>
    <Setter Property="IsTabStop" Value="false"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type RepeatButton}">
                ...
                ...
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<!-- Scroll Buttons Up-->
<Style x:Key="RepeatButtonStyleUp" TargetType="{x:Type RepeatButton}">
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="Focusable" Value="false"/>
    <Setter Property="IsTabStop" Value="false"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type RepeatButton}">
                ...
                ...
              </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<!-- Scroll Thumb Style-->
<Style x:Key="ThumbStyle1" TargetType="{x:Type Thumb}">
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="IsTabStop" Value="false"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Thumb}">
                ...
                ...
              </