Chapter 6. Working with Styles, Templates, Skins, and Themes

One of the most remarkable aspects of WPF is the ability to separate and customize the visual appearance of user interface elements from the built-in behavior and functionality they provide. You can do this using styles and control templates.

The recipes in this chapter describe how to:

  • Create custom visual styles for control elements (recipes 6-1 and 6-2)

  • Customize, override, inherit from, and dynamically change these visual styles (recipes 6-3, 6-4, 6-5, 6-6, 6-7, and 6-8)

  • Set a style programmatically and ignore an implicit style (recipes 6-9 and 6-10)

  • Create styles to control the appearance of items in a collection (recipes 6-11 and 6-12)

  • Create custom control templates that specify how an element should be constructed and displayed (recipes 6-13 and 6-14)

  • Manipulate, manage, and dynamically change the elements in a control's visual tree (recipes 6-15, 6-16, and 6-17)

  • Create custom tool tip styles for elements (recipe 6-18)

  • Dynamically change the skin of an application and create styles that adapt to the current Windows operating system theme (recipes 6-19 and 6-20)

Create a Named Style

Problem

You need to display a UI control, or set of controls, with a custom look and style, instead of using the default display style.

Solution

Create a System.Windows.Style resource, and specify the Key attribute. The Style can set control properties and reference custom brush resources. Then reference this Key in the System.Windows.Style property of a control.

How It Works

A Style is a collection of property values that can be applied to one or more controls. Primarily, it does this via its Setters collection, which holds System.Windows.Setter objects. Each Setter object specifies the name of a property to act on and the value to assign to it. This allows you to create a group of values for visual appearance properties, declare this group of values in a System.Windows.ResourceDictionary, and reference it from different parts of your application.

All UI controls ultimately derive from System.Windows.FrameworkElement, and this class exposes a Style property. You can set this property to a named Style using the System.Windows.StaticResourceExtension markup extension class.

The Code

The following example declares two brush resources and a Style. The Style has a key of My Style; changes the System.Windows.FontWeight, System.Windows.Media.Brush.Background, and System.Windows.Media.Brush.BorderBrush properties of a control; and sets the width, height, and margin. There are three System.Windows.Controls.Button controls; two specify the My Style resource as the Style property, and the other receives the default for this control type.

<Window x:Class="Recipe_06_01.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_01" Height="100" Width="300">

    <Window.Resources>

        <!-- Brush Resources -->
        <LinearGradientBrush x:Key="NormalBrush"
                             EndPoint="0,1"
                             StartPoint="0,0">
            <GradientStop Color="White" Offset="0.0"/>
            <GradientStop Color="LightGray" Offset="1.0"/>
       </LinearGradientBrush>

       <LinearGradientBrush x:Key="NormalBorderBrush"
                            EndPoint="0,1"
                            StartPoint="0,0">
           <GradientStop Color="Gainsboro" Offset="0.0"/>
           <GradientStop Color="DarkGray" Offset="1.0"/>
       </LinearGradientBrush>
<!-- Named Style -->
       <Style x:Key="MyStyle">
           <Setter Property="Control.FontWeight" Value="Bold"/>
           <Setter Property="Control.Background"
                   Value="{DynamicResource NormalBrush}"/>
           <Setter Property="Control.BorderBrush"
                   Value="{DynamicResource NormalBorderBrush}"/>
           <Setter Property="Control.Width" Value="88"/>
           <Setter Property="Control.Height" Value="24"/>
           <Setter Property="Control.Margin" Value="4"/>
       </Style>

   </Window.Resources>

   <Grid>
       <StackPanel Orientation="Horizontal">

           <Button Style="{StaticResource MyStyle}"
                   Content="Named Style"/>

           <Button Style="{StaticResource MyStyle}"
                   Content="Named Style"/>

           <Button Width="88" Height="24" Margin="4"
                   Content="Default Style"/>

       </StackPanel>
   </Grid>

</Window>

Figure 6-1 shows the resulting window.

Applying a named style to Button controls

Figure 6-1. Applying a named style to Button controls

Create a Typed Style

Problem

You need to apply the same custom style to all instances of a control type.

Solution

Create a System.Windows.Style resource, and specify the TargetType attribute. The Style can set control properties and reference custom brush resources. It will be automatically applied to every instance of this control type, within the scope of the Style resource.

How It Works

If a Style is given a TargetType, then it will automatically be applied to any instance of this control type within the scope of the resource. (See Chapter 2 for more information on resource scopes.) The System.Windows.FrameworkElement and System.Windows.FrameworkContentElement base classes both have a Resources property, so Style resources can be added to most WPF classes. If a typed Style is added to a control's Resources collection, then any child of this type within the logical tree will have the Style applied to it.

Tip

A System.Type can also be specified as the Key property of a style, which achieves the same result as specifying the TargetType. For example, <Style TargetType="{x:Type ListView}"> is the same as <Style Key="{x:Type ListView}">.

The Code

The following example demonstrates a window that declares two brush resources and a Style. Instead of giving the Style a key, the TargetType of System.Windows.Controls.Button is specified. The Style sets some display properties for a Button, including the Margin, Size, and Background. The window displays two Button controls, neither of which have been explicitly given a style property, and they both automatically receive the custom typed style.

Note

If a TargetType is specified, notice that the Control. prefix is not needed in the Setter properties. This is because the style knows what type of control it can be applied to and therefore what properties are available to it.

The XAML for the window is as follows:

<Window x:Class="Recipe_06_02.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_02" Height="120" Width="240">

    <Window.Resources>

        <!-- Brush Resources -->
        <LinearGradientBrush x:Key="NormalBrush"
                             EndPoint="0,1"
                             StartPoint="0,0">
<GradientStop Color="White" Offset="0.0"/>
            <GradientStop Color="LightGray" Offset="1.0"/>
        </LinearGradientBrush>

        <LinearGradientBrush x:Key="NormalBorderBrush"
                             EndPoint="0,1"
                             StartPoint="0,0">
            <GradientStop Color="Gainsboro" Offset="0.0"/>
            <GradientStop Color="DarkGray" Offset="1.0"/>
        </LinearGradientBrush>

        <!-- Typed Style -->
        <Style TargetType="{x:Type Button}">
            <Setter Property="Margin" Value="4"/>
            <Setter Property="Width" Value="80"/>
            <Setter Property="Height" Value="24"/>
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="Background"
                    Value="{DynamicResource NormalBrush}"/>
            <Setter Property="BorderBrush"
                    Value="{DynamicResource NormalBorderBrush}"/>
        </Style>

    </Window.Resources>

    <Grid Margin="20">
        <StackPanel Orientation="Horizontal">

            <Button>One</Button>
            <Button>Two</Button>

        </StackPanel>
    </Grid>
</Window>

Figure 6-2 shows the resulting window, with the custom style automatically applied to both instances of the Button control.

Button controls with a typed style

Figure 6-2. Button controls with a typed style

Override Style Properties

Problem

You need to override the value of a property that has been set by a named or typed System.Windows.Style.

Solution

Set the property or properties in the inline XAML declaration for a specific instance.

How It Works

Styles set the initial appearance of a control, but you can override any of the values they set in the inline XAML for any element. The control will automatically use these instead of taking the values from a named or typed style.

The Code

The following example declares a Style for all System.Windows.Button controls within the window's Resources collection. There are two Button controls that will automatically receive the typed Style, but the second Button has overridden the System.Windows.FontWeight property.

<Window x:Class="Recipe_06_03.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title=" WPF Recipes 6_03" Height="120" Width="240">

    <Window.Resources>

        <!-- Typed Style -->
        <Style TargetType="{x:Type Button}">
            <Setter Property="Margin" Value="4"/>
            <Setter Property="Width" Value="80"/>
            <Setter Property="Height" Value="24"/>
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>

    </Window.Resources>

    <Grid Margin="20">
        <StackPanel Orientation="Horizontal">

            <Button>One</Button>
            <Button FontWeight="Thin">Two</Button>

        </StackPanel>
    </Grid>
</Window>

Figure 6-3 shows the resulting window.

Overriding style properties

Figure 6-3. Overriding style properties

Inherit from a Common Base Style

Problem

You need to create a System.Windows.Style that defines some common display properties for all your controls and then allow different custom styles to inherit from, and extend, this base style.

Solution

When defining a Style, use the BasedOn attribute to inherit the properties of a base Style.

How It Works

You can define a named Style with a TargetType of a common base type such as System.Windows.Control and give the Style all the display properties you want to share across different types of controls. Then define typed styles for the specific controls you want to use, and set the BasedOn attribute to ensure these styles inherit the properties of the base Style. The derived styles can still override and extend the properties set on the base Style.

The Code

The following example demonstrates a window that declares a Style called BaseControlStyle with a TargetType of Control. The Style specifies the values of the FontFamily, FontSize, FontStyle, and Margin properties. There are three typed styles that specify this base Style in the BasedOn attribute. A group of controls are then displayed in a System.Windows.Controls.StackPanel. Some of the controls have a typed Style defined for them, namely, the System.Windows.CheckBox, System.Windows.Controls.TextBox, and System.Windows.Button controls. These inherit the common properties defined in the base Style. The other controls do not have a typed Style declared for them, namely, the System.Windows.Controls.TextBlock and the System.Windows.Controls.ComboBox, and so do not inherit any of the appearance properties specified in the Style. The Style targeting the Button control demonstrates how to extend a property in an inherited Style. And the inline XAML declaration for one of the Button instances demonstrates how to override a Style's property.

<Window x:Class="Recipe_06_04.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_04" Height="220" Width="300">
    <Window.Resources>

        <!-- Base Style -->
         <Style x:Key="BaseControlStyle"
                TargetType="{x:Type Control}">
             <Setter Property="FontFamily" Value="Tahoma" />
             <Setter Property="FontSize" Value="14pt"/>
             <Setter Property="FontStyle" Value="Italic" />
             <Setter Property="Margin" Value="4" />
         </Style>

         <!-- Button Style -->
         <Style TargetType="{x:Type Button}"
                BasedOn="{StaticResource BaseControlStyle}">
             <!-- Add any overriding property values here -->
             <Setter Property="FontWeight" Value="Bold" />
         </Style>

         <!-- CheckBox Style -->
         <Style TargetType="{x:Type CheckBox}"
                BasedOn="{StaticResource BaseControlStyle}">
         </Style>

         <!-- TextBox Style -->
         <Style TargetType="{x:Type TextBox}"
                BasedOn="{StaticResource BaseControlStyle}">
        </Style>

    </Window.Resources>

    <Grid>
        <StackPanel>
            <CheckBox>CheckBox with inherited style</CheckBox>
            <TextBox>TextBox with inherited style</TextBox>
            <Button>Button with inherited style</Button>
            <Button FontWeight="Light">Button with overridden style</Button>
            <TextBlock>TextBlock with default style</TextBlock>
            <ComboBox>ComboBox with default style</ComboBox>
        </StackPanel>
    </Grid>
</Window>

Figure 6-4 shows the resulting window.

Inheriting style properties

Figure 6-4. Inheriting style properties

Change a Control's Appearance on Mouse Over

Problem

You need to change the appearance of a control when the mouse moves over it.

Solution

Create a System.Windows.Style resource for the System.Windows.Controls.Control, and use a "property trigger" to change the properties of the Style when the IsMouseOver property is True.

How It Works

Every control ultimately inherits from System.Windows.UIElement. This exposes a dependency property called IsMouseOverProperty. A System.Windows.Trigger can be defined in the Style of the control, which receives notification when this property changes and can subsequently change the control's Style. When the mouse leaves the control, the property is set back to False, which notifies the trigger, and the control is automatically set back to the default state.

The Code

The following example demonstrates a window with a Style resource and two System.Windows.Controls.Button controls. The Style uses a Trigger to change the System.Windows.FontWeight and BitmapEffect properties of the Button controls when the mouse is over them.

The XAML for the window is as follows:

<Window x:Class="Recipe_06_05.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_05" Height="120" Width="240">
<Window.Resources>

        <Style TargetType="{x:Type Button}">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="FontWeight" Value="Bold" />
                    <Setter Property="BitmapEffect">
                        <Setter.Value>
                            <OuterGlowBitmapEffect
                                     GlowColor="Orange"
                                     GlowSize="5"
                                   />
                        </Setter.Value>
                    </Setter>
                </Trigger>
            </Style.Triggers>
        </Style>

    </Window.Resources>

    <StackPanel Margin="8">
        <Button Height="25" Width="100" Margin="4">
            Mouse Over Me!
        </Button>
        <Button Height="25" Width="100" Margin="4">
            Mouse Over Me!
        </Button>
    </StackPanel>

</Window>

Figure 6-5 shows the resulting window.

Changing a control's appearance on mouse over

Figure 6-5. Changing a control's appearance on mouse over

Apply Multiple Triggers to the Same Element

Problem

You need to make the same change to the appearance of a control but under different scenarios. This is the same as saying "If property x is true or property y is true, then change the appearance to this...."

Solution

Create a System.Windows.Style resource for the control, and add multiple triggers to the style's System.Windows.Triggers collection. In the Trigger, set the values of the properties to achieve the desired appearance.

How It Works

It's possible to create multiple Trigger objects that apply to the same element at once. If they set the values of different properties, then the multiple Trigger objects do not affect each other. If they affect the same properties and they assign the same values, then it is the same as saying that the controls should have a certain visual appearance under multiple circumstances. However, if there are multiple Trigger objects affecting the same property and they assign different values, then the last one to set the value will win.

The Code

The following example specifies a Style resource for a System.Windows.Controls.TextBox control with two triggers. The triggers set the value of the Background property when either the mouse is over the control or when the control has the focus.

<Window x:Class="Recipe_06_06.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_06" Height="100" Width="300">
    <Window.Resources>
        <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <rigger Property="IsMouseOver"
                        Value="True">
                    <Setter Property="Background"
                            Value="Orange" />
                </Trigger>
                <Trigger Property="IsFocused"
                         Value="True" >
                    <Setter Property="Background"
                            Value="Orange" />
</Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <Grid>
        <TextBox Height="20" Width="200">
            Mouse over or give me focus!
        </TextBox>
    </Grid>
</Window>

Figure 6-6 shows the resulting window.

Applying multiple triggers to the same element

Figure 6-6. Applying multiple triggers to the same element

Evaluate Multiple Properties for the Same Trigger

Problem

You need to make changes to the appearance of a control when multiple conditions are true. This is the same as saying "If property x is true and property y is true, then change the appearance to this...."

Solution

Create a System.Windows.Style resource for the control, and add a System.Windows.MultiTrigger to the Style's Triggers collection. Then create multiple instances of the System.Windows.Condition class, and add them to the MultiTrigger's Conditions collection. In the MultiTrigger, set the values of the properties to achieve the desired appearance.

How It Works

A MultiTrigger exposes a Conditions collection that lets you define a series of property and value combinations. The MultiTrigger's Setters are applied to the control only when all the Conditions evaluate to True.

The Code

The following example specifies a Style resource for a System.Windows.Controls.TextBox control with one MultiTrigger. The MultiTrigger sets the Background property of the TextBox and specifies two conditions: when the mouse is over the control and when it has the focus.

<Window x:Class="Recipe_06_07.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_07" Height="100" Width="300">
    <Window.Resources>
        <Style
            TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition
                            Property="IsMouseOver"
                            Value="True"/>
                        <Condition
                            Property="IsFocused"
                            Value="True"/>
                    </MultiTrigger.Conditions>
                    <Setter
                        Property="Background"
                        Value="Orange" />
                </MultiTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <Grid>
        <TextBox Height="20" Width="200">
            Mouse over and give me focus!
        </TextBox>
    </Grid>

</Window>

Figure 6-7 shows the resulting window.

Evaluating multiple properties for the same trigger

Figure 6-7. Evaluating multiple properties for the same trigger

Programmatically Extract an Element's Style

Problem

You need to programmatically extract a UI element's System.Windows.Style to XAML, for example, because you want to modify a default System.Windows.Controls.ControlTemplate for a standard WPF control.

Solution

Get the key for the element's Style, and use it to find the Style in the application's resources. Programmatically extract the Style by saving it to a string or System.IO.Stream.

How It Works

The FrameworkElement class has a dependency property called DefaultStyleKeyProperty. This property holds the key to the element's Style in the application's resources. You can use the System.Reflection namespace to get the value of this key for a given element and then use the Application.Current.FindResource method to find the relevant Style. Save the Style to XAML with the System.Windows.Markup.XamlWriter.Save method.

The Code

The following example defines a custom Style for a System.Windows.Controls.ProgressBar control. There is a named ProgressBar element that uses this Style and a System.Windows.Button that can be clicked to extract the Style in the code-behind.

<Window x:Class="Recipe_06_08.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_08" Height="120" Width="220">
    <Window.Resources>

        <Style
            x:Key="CustomProgressBarStyle"
            TargetType="{x:Type ProgressBar}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate
                        TargetType="{x:Type ProgressBar}">
                        <Grid MinHeight="20" MinWidth="240">
                            <Border
                          Name="PART_Track"
                          Background="{DynamicResource
                            {x:Static SystemColors.InactiveCaptionBrushKey}}"
BorderBrush="{DynamicResource
                            {x:Static SystemColors.InactiveBorderBrushKey}}"
                          BorderThickness="1"
                              />
                            <Border
                          Name="PART_Indicator"
                          Background="{DynamicResource
                            {x:Static SystemColors.ActiveCaptionBrushKey}}"
                          BorderBrush="{DynamicResource
                            {x:Static SystemColors.ActiveBorderBrushKey}}"
                          BorderThickness="1"
                          HorizontalAlignment="Left"
                             />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

    </Window.Resources>

    <StackPanel>

        <ProgressBar x:Name="MyProgressBar"
                     Value="30"
                     Width="200"
                     HorizontalAlignment="Center"
                     Margin="8"
                     Style="{DynamicResource CustomProgressBarStyle}"
                     />

        <Button Click="Button_Click"
                Width="100"
                Height="28"
                Margin="8"

                Content="Extract Style"/>

    </StackPanel>
</Window>

In the Click event handler for the Button, the following code extracts the Style from the ProgressBar and saves it to XAML:

using System;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;

namespace Recipe_06_08
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Type type = typeof(FrameworkElement);

            // Get the DefaultStyleKeyProperty dependency
            // property of FrameworkElement
            FieldInfo fieldInfo = type.GetField(
                                    "DefaultStyleKeyProperty",
                                    BindingFlags.Static
                                  | BindingFlags.NonPublic);
            DependencyProperty defaultStyleKeyProperty =
                            (DependencyProperty)fieldInfo.GetValue
                                (MyProgressBar);

            // Get the value of the property for the
            //progress bar element
            object defaultStyleKey =
                MyProgressBar.GetValue(defaultStyleKeyProperty);

            // Get the style from the application's resources
            Style style =
                (Style)Application.Current.FindResource
                    (defaultStyleKey);

            // Save the style to a string
            string styleXaml = XamlWriter.Save(style);
        }
    }
}

Set a Style Programmatically

Problem

You need to set which System.Windows.Style to apply to a UI element programmatically, based on custom application logic.

Solution

Use the System.Windows.FrameworkElement.FindResource method to locate and apply the required Style resource to the UI element.

How It Works

When a Style is given a name, it can be retrieved from the resources using the FindResource method. This method will search all the available resource dictionaries within the scope of the FrameworkElement on which it is called. If it is unable to find a resource with the specified key, it will throw a System.Windows.ResourceReferenceKeyNotFoundException.

The Code

The following example demonstrates a window that displays a System.Windows.Controls.Image and a System.Windows.Controls.TextBox. In the window's Resources collection, there are two sets of styles for both of these controls. There is custom logic in the window's Loaded event to programmatically set which Style should be used for the controls, based on the hour of the day.

The XAML for the window is as follows:

<Window x:Class="Recipe_06_09.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Loaded="Window_Loaded"
    Title="WPF Recipes 6_09"
    Height="230" Width="140">
    <Window.Resources>
        <Style x:Key="lblDaytimeStyle">
            <Setter Property="Label.Background" Value="LightYellow" />
            <Setter Property="Label.BorderBrush" Value="Orange" />
            <Setter Property="Label.BorderThickness" Value="1" />
            <Setter Property="Label.FontSize" Value="20" />
            <Setter Property="Label.Width" Value="96" />
            <Setter Property="Label.Height" Value="36" />
            <Setter Property="Label.Margin" Value="4" />
            <Setter Property="Label.Foreground" Value="Orange" />
            <Setter Property="Label.HorizontalContentAlignment" Value="Center" />
</Style>
        <Style x:Key="imgDaytimeStyle">
            <Setter Property="Image.Source" Value="authorDay.png" />
            <Setter Property="Image.Height" Value="140" />
            <Setter Property="Image.Width" Value="96" />
        </Style>
        <Style x:Key="lblNighttimeStyle">
            <Setter Property="Label.Background" Value="AliceBlue" />
            <Setter Property="Label.BorderBrush" Value="DarkBlue" />
            <Setter Property="Label.BorderThickness" Value="1" />
            <Setter Property="Label.FontSize" Value="20" />
            <Setter Property="Label.Width" Value="96" />
            <Setter Property="Label.Height" Value="36" />
            <Setter Property="Label.Margin" Value="4" />
            <Setter Property="Label.Foreground" Value="DarkBlue" />
            <Setter Property="Label.HorizontalContentAlignment" Value="Center" />
        </Style>
        <Style x:Key="imgNighttimeStyle">
            <Setter Property="Image.Source" Value="authorNight.png" />
            <Setter Property="Image.Height" Value="140" />
            <Setter Property="Image.Width" Value="96" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="148"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Image x:Name="img"/>
        <Label Grid.Row="1" x:Name="lbl" Content="Hello" />
    </Grid>
</Window>

The code-behind for the window is as follows:

using System;
using System.Windows;

namespace Recipe_06_09
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            if (DateTime.Now.TimeOfDay.Hours >= 18
                || DateTime.Now.TimeOfDay.Hours < 6 )
            {
                lbl.Style = (Style)FindResource("lblNighttimeStyle");
                img.Style = (Style)FindResource("imgNighttimeStyle");
            }
            else
            {
                lbl.Style = (Style)FindResource("lblDaytimeStyle");
                img.Style = (Style)FindResource("imgDaytimeStyle");
            }
        }
    }
}

Figure 6-8 shows the resulting windows.

Setting styles programmatically

Figure 6-8. Setting styles programmatically

Ignore an Implicit Style

Problem

You need to specify that a UI element should ignore a typed System.Windows.Style.

Solution

Set the Style property of the element to the System.Windows.Markup.NullExtension markup extension.

How It Works

Each System.Windows.Control, such as System.Windows.Controls.Button, has two styles: the local Style, as specified by the Style property and the theme or default Style, which is defined by the control or in the system. When you set the Style property of a Control to null, it overrides a typed Style and forces it to use the default theme Style. To set a Style property to null, use the x:Null markup extension.

The Code

The following example demonstrates a window with two Button controls. The window's Resources collection contains a simple Style that targets Button controls and changes their Background and FontWeight properties. The Button at the top will inherit this Style automatically, but the one after it will ignore it, because its Style is set to {x:Null}.

<Window x:Class="Recipe_06_10.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Recipe_06_10="clr-namespace:Recipe_06_10;assembly="
    Title="WPF Recipes 6_10" Height="88" Width="180">

    <Window.Resources>

        <Style TargetType="{x:Type Button}">
            <Setter Property="Background" Value="LightGray"/>
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>

    </Window.Resources>

    <StackPanel Margin="4">
        <Button>Implicit Style</Button>
        <Button Style="{x:Null}">Ignores Style</Button>
    </StackPanel>

</Window>

Figure 6-9 shows the resulting window.

Ignoring implicit styles

Figure 6-9. Ignoring implicit styles

Change the Appearance of Alternate Items in a List

Problem

You need to change the System.Windows.Style of items in a System.Windows.Controls.ListBox to change the appearance of alternate rows.

Solution

Create a System.Windows.Controls.StyleSelector class, and override the SelectStyle method.

How It Works

When you set the ItemContainerStyleSelector property of a ListBox to a StyleSelector, it will evaluate each item and apply the correct Style. This allows you to specify custom logic to vary the appearance of items based on any particular value or criteria.

The Code

The following example demonstrates a window that displays a list of country names in a ListBox. In the XAML for the ListBox, its ItemContainerStyleSelector property is set to a local StyleSelector class called AlternatingRowStyleSelector. This class has a property called AlternateStyle, which is set to a Style resource that changes the Background property of a ListBoxItem.

The AlternatingRowStyleSelector class overrides the SelectStyle property and returns either the default or the alternate Style, based on a boolean flag.

The XAML for the window is as follows:

<Window x:Class="Recipe_06_11.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Recipe_06_11;assembly="
    Title="WPF Recipes 6_11" Height="248" Width="128">

    <Window.Resources>
        <local:Countries x:Key="countries"/>

        <Style x:Key="AlternateStyle">
            <Setter Property="ListBoxItem.Background" Value="LightGray"/>
        </Style>

    </Window.Resources>

    <Grid Margin="4">
        <ListBox
            DisplayMemberPath="Name"
ItemsSource="{Binding
                 Source={StaticResource countries}}" >

            <ListBox.ItemContainerStyleSelector>
                <local:AlternatingRowStyleSelector
                    AlternateStyle="{StaticResource AlternateStyle}" />
            </ListBox.ItemContainerStyleSelector>
        </ListBox>
    </Grid>
</Window>

The code for the StyleSelector is as follows:

using System.Windows;
using System.Windows.Controls;

namespace Recipe_06_11
{
    public class AlternatingRowStyleSelector : StyleSelector
    {
        public Style DefaultStyle
        {
            get;
            set;
        }

        public Style AlternateStyle
        {
            get;
            set;
        }

        // Flag to track the alternate rows
        private bool isAlternate = false;

        public override Style SelectStyle(object item, DependencyObject container)
        {
            // Select the style, based on the value of isAlternate
            Style style = isAlternate ? AlternateStyle : DefaultStyle;

            // Invert the flag
            isAlternate = !isAlternate;

            return style;
        }
    }
}

Figure 6-10 shows the resulting window.

Changing the appearance of alternate rows

Figure 6-10. Changing the appearance of alternate rows

Change the Appearance of a List Item When It's Selected

Problem

You need to change the appearance of an item in a System.Windows.Controls.ListBox when it is selected.

Solution

Create a System.Windows.Style resource that targets the System.Windows.Controls.ListBoxItem type, and use a property trigger to change the appearance of an item when the IsSelected property is True.

How It Works

The ListBoxItem class exposes a DependencyProperty called IsSelectedProperty. A System. Windows.Trigger can be defined in the Style of the ListBoxItem, which receives notification when this property changes and can subsequently change the appearance properties.

The Code

The following example creates a System.Windows.Controls.ListBox and specifies a style resource for a ListBoxItem. In the style, a property trigger changes the FontWeight and FontSize properties when the IsSelected property is True.

<Window x:Class="Recipe_06_12.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Recipe_06_12="clr-namespace:Recipe_06_12;assembly="
Title="WPF Recipes 6_12" Height="248" Width="128">
        <Window.Resources>

        <Recipe_06_12:Countries x:Key="countries"/>

        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="Content"
                    Value="{Binding Path=Name}"/>
            <Setter Property="Margin"
                    Value="2"/>
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="FontWeight"
                            Value="Bold" />

                    <Setter Property="FontSize"
                            Value="14" />
                </Trigger>
            </Style.Triggers>
        </Style>

    </Window.Resources>

    <Grid Margin="4">
        <ListBox
            ItemsSource="{Binding
                 Source={StaticResource countries}}"
            Width="100"
            />

    </Grid>

</Window><

Figure 6-11 shows the appearance of the selected item in a list box.

Modifying the appearance of the selected item

Figure 6-11. Modifying the appearance of the selected item

Create a Control Template

Problem

You need to replace an element's visual tree while keeping all its functionality intact.

Solution

Create a System.Windows.Controls.ControlTemplate, and apply it to the Template property of a System.Windows.Controls.Control.

How It Works

The ControlTemplate contains the desired visual tree of elements and can be changed independently from the Control's behavior and functionality. For example, you can choose to use a System.Windows.Controls.ToggleButton in your application if you want ToggleButton-like behavior. That is, if you want a Control that can be clicked and that can be in one of two states: checked or unchecked. However, you are free to change the appearance and visual behavior of the ToggleButton, including what it looks like and how it reacts when you mouse over it or press it. You do this by replacing its ControlTemplate with a new declaration of visual elements and by specifying System.Windows.Triggers to change the appearance of the elements in response to property changes.

Tip

One thing to keep in mind is that once you create a ControlTemplate for your control, you are replacing the entire ControlTemplate. This is in contrast to System.Windows.Styles, where any property that doesn't get explicitly set by its Style will automatically inherit the value from the control's default style.

The Code

The following example demonstrates a window that displays a ToggleButton control. The ToggleButton is given a new ControlTemplate that displays a System.Windows.Controls.Image inside a System.Windows.Controls.Border. The ControlTemplate contains three Trigger objects. The first two change the Border's Background and BorderBrush when you mouse over it, and the second one changes the image when the ToggleButton is in a checked state.

<Window x:Class="Recipe_06_13.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_13" Height="240" Width="160">
<Grid>
        <ToggleButton Width="122" Height="170">
            <ToggleButton.Template>
               <ControlTemplate
                       TargetType="{x:Type ToggleButton}">
                   <Border x:Name="border"
                               CornerRadius="4"
                               BorderThickness="3"
                               BorderBrush="DarkGray"
                               Background="LightGray">
                        <Image x:Name="img"
                               Source="authorDay.png"
                               Margin="10"
                               />
                   </Border>
                   <ControlTemplate.Triggers>
                       <Trigger Property="IsMouseOver" Value="True">
                           <Setter TargetName="border"
                                   Property="BorderBrush"
                                   Value="Black"
                                   />
                       </Trigger>
                       <Trigger Property="IsMouseOver" Value="True">
                           <Setter TargetName="border"
                                   Property="Background"
                                   Value="DarkGray"
                                   />
                       </Trigger>
                       <Trigger Property="IsChecked" Value="True">
                           <Setter TargetName="img"
                                   Property="Source"
                                   Value="authorNight.png"
                                   />
                       </Trigger>
                   </ControlTemplate.Triggers>
               </ControlTemplate>
          </ToggleButton.Template>

      </ToggleButton>
  </Grid>

</Window>

Figure 6-12 shows the appearance of the window in the three states: mouse over, unchecked, and checked.

Creating a control template

Figure 6-12. Creating a control template

Put a Control Template into a Style

Problem

You need to share a System.Windows.Controls.ControlTemplate across multiple instances of a System.Windows.Controls.Control.

Solution

Create a typed System.Windows.Style, and set the Template property to an inline ControlTemplate.

How It Works

A Style can be defined as a resource and given the TargetType attribute to apply it to all instances of the specified control type, within the scope of that resource. A System.Windows.Setter can be used to set the value of the Template property to an inline ControlTemplate.

The Code

The following example demonstrates a window that displays five System.Windows.Controls.ToggleButton controls. Three of these are in a checked state; two are unchecked. There is a typed Style in the window's Resources collection, which sets the Template property to an inline ControlTemplate. This ControlTemplate specifies that a ToggleButton should be displayed as a System.Windows.Shapes.Path in the shape of a star. There are two System.Windows.Trigger objects that change the Fill property of the Path when the mouse is over it or when the ToggleButton is checked.

The XAML for the window is as follows:

<Window x:Class="Recipe_06_14.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="WPF Recipes 6_14" Height="120" Width="260">
<Window.Resources>

      <!-- Typed Style -->
      <Style TargetType="{x:Type ToggleButton}">
          <Setter Property="Width" Value="36"/>
          <Setter Property="Height" Value="30"/>
          <Setter Property="Template" >
              <Setter.Value>
                 <!-- Control Template -->
                 <ControlTemplate
                     TargetType="{x:Type ToggleButton}">
                     <Canvas Canvas.Left="5" Canvas.Top="20">
                         <Path x:Name="pth" Stroke="#000080" Fill="#C0C0C0"
                           StrokeThickness="3" StrokeStartLineCap="Round"
                           StrokeEndLineCap="Round" StrokeLineJoin="Round"
                           Data="M 0,0 l 10,0 l 5,-10 l 5,
                               10 l 10,0 l −7,10 l 2,10 l −10,
                               −5 l −10,5 l 2,-10 Z"
                           />
                     </Canvas>
                     <ControlTemplate.Triggers>
                         <Trigger Property="IsMouseOver" Value="True">
                             <Setter TargetName="pth"
                                 Property="Fill"
                                 Value="#000080"
                                 />
                         </Trigger>
                         <Trigger Property="IsChecked" Value="True">
                             <Setter TargetName="pth"
                                 Property="Fill"
                                 Value="#FFFF00"
                                 />
                         </Trigger>
                     </ControlTemplate.Triggers>
                 </ControlTemplate>
             </Setter.Value>
        </Setter>
    </Style>

  </Window.Resources>

  <StackPanel VerticalAlignment="Center"
              HorizontalAlignment="Center"
              Margin="10" Orientation="Horizontal">
      <ToggleButton IsChecked="True"/>
      <ToggleButton IsChecked="True"/>
      <ToggleButton IsChecked="True"/>
<ToggleButton IsChecked="False"/>
        <ToggleButton IsChecked="False"/>
    </StackPanel>

</Window>

Figure 6-13 shows the resulting window with the five ToggleButton controls.

Putting a control template into a style

Figure 6-13. Putting a control template into a style

Create a Control Template That Can Be Customized by Properties

Problem

You need to replace an element's visual tree whilst keeping all its functionality intact and also expose it to customization by the user of the control via properties.

Solution

Create a System.Windows.Controls.ControlTemplate resource, and specify it as the Template property of a control. Use the System.Windows.TemplateBindingExtension markup extension to specify that values of properties within the ControlTemplate should be bound to a property on the templated element.

How It Works

The TemplateBinding markup extension is a lightweight one-way data binding that maps the values of properties on the System.Windows.Controls.Control being templated to properties within the template itself. This allows you to customize the appearance of the controls by setting their properties in the inline XAML, which will then be adopted by the elements in the template.

The Code

The following example demonstrates a window that defines a custom ControlTemplate for the System.Windows.Controls.Label control. Within the ControlTemplate there is a System. Windows.Controls.Border control that uses the TemplateBinding markup extension to declare that its Background property should derive its value from the Background property of the Label element, which in this case is set to LightBlue.

The XAML for the window is as follows:

<Window x:Class="Recipe_06_15.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_15" Height="100" Width="180">

    <Window.Resources>
        <ControlTemplate
                    x:Key="labelTemplate"
                    TargetType="{x:Type Label}">
            <Border x:Name="border"
                    CornerRadius="4"
                    BorderThickness="3"
                    BorderBrush="DarkGray"
                    Background="{TemplateBinding
                    Property=Background}">
                <ContentPresenter
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"/>
            </Border>
        </ControlTemplate>
    </Window.Resources>
    <Grid>
        <Label Width="100" Height="24"
               Margin="4" Content="Custom Label"
               Template="{StaticResource labelTemplate}"
               Background="LightBlue"/>
    </Grid>
</Window>

Figure 6-14 shows the resulting window.

Creating a control template that can be customized by properties

Figure 6-14. Creating a control template that can be customized by properties

Specify Named Parts of a Control Template

Problem

You need to restyle a WPF control without modifying or replacing expected behavior.

Solution

Identify the names of expected elements in your System.Windows.Controls.ControlTemplate by looking for the System.Windows.TemplatePartAttribute on the class declaration for the control. Apply these names to corresponding elements in your ControlTemplate to maintain expected behavior.

How It Works

System.Windows.Controls.Control elements use the TemplatePartAttribute to specify named parts of the visual element that the code expects. Look at the documentation for a given control to determine which named elements the ControlTemplate requires. The names are always of the form PART_Xxx.

Create a typed System.Windows.Style, and set the Template property to an inline System.Windows.Controls.ControlTemplate. Define elements in the ControlTemplate, and give them the expected PART_Xxx names. The expected behaviors will automatically be applied to them.

The Code

The following example demonstrates a window that defines a Style resource with a TargetType of System.Windows.Controls.ProgressBar and an inline ControlTemplate. The ControlTemplate contains two System.Windows.Shapes.Rectangle elements within a grid. The Rectangle elements are given the names PART_Track and PART_Indicator, which are the two template parts defined using the TemplatePartAttribute in the ProgressBar class. This ensures that the width of PART_Indicator automatically remains the correct size relative to the width of PART_Track, based on the Value, Minimum, and Maximum properties.

The XAML for the window is as follows:

<Window x:Class="Recipe_06_16.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_16" Height="120" Width="300">

    <Window.Resources>
        <Style
            TargetType="{x:Type ProgressBar}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate
                        TargetType="{x:Type ProgressBar}">
                        <Grid MinHeight="20" MinWidth="240">
                            <Rectangle
                            Name="PART_Track"
                            Fill="Gainsboro"
                            Stroke="Gray"
                            StrokeThickness="1" />
                          <Rectangle
Name="PART_Indicator"
                            Fill="DarkGray"
                            Stroke="Gray"
                            StrokeThickness="1"
                            HorizontalAlignment="Left" />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <StackPanel>
        <ProgressBar x:Name="progress" Value="30"
                     HorizontalAlignment="Center"
                     Margin="10"/>
        <Slider Value="{Binding ElementName=progress, Path=Value, Mode=TwoWay}"
                Minimum="0" Maximum="100" Margin="10"/>
    </StackPanel>
</Window>

Figure 6-15 shows the resulting window.

Specifying named parts of a control template

Figure 6-15. Specifying named parts of a control template

Find ControlTemplate-Generated Elements

Problem

You need to locate an element in the visual tree of a System.Windows.Controls.ControlTemplate and access the values of its properties.

Solution

Use the FindName method of a System.Windows.FrameworkTemplate.

How It Works

The FindName method finds the element associated with the specified name, defined within the template.

The Code

The following example demonstrates a window that defines a ControlTemplate resource and creates a System.Windows.Button element that references this template. There is a System.Windows.Controls.Border control within the ControlTemplate called border. In the code-behind for the Click event of the Button, the Template.FindName method is used to return the Border within the Button template and read the value of its ActualWidth property.

The XAML for the window is as follows:

<Window x:Class="Recipe_06_17.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="WPF Recipes 6_17" Height="100" Width="160">

       <Window.Resources>
           <ControlTemplate
                       x:Key="buttonTemplate"
                       TargetType="{x:Type Button}">
               <Border x:Name="border"
                       CornerRadius="4"
                       BorderThickness="3"
                       BorderBrush="DarkGray"
                       Background="LightGray">
                   <ContentPresenter
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       />
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger
                        Property="IsMouseOver"
                        Value="True">
                        <Setter
                            TargetName="border"
                            Property="Background"
                            Value="Orange"
                            />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Window.Resources>
        <Grid>
            <Button x:Name="button"
                    Height="24"
                    HorizontalAlignment="Stretch"
                    Margin="4"
                    Content="Custom Template"
                    Template="{StaticResource buttonTemplate}"
Click="Button_Click">
        </Button>
    </Grid>
</Window>

The code-behind for the window is as follows:

using System.Windows;
using System.Windows.Controls;

namespace Recipe_06_17
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Finding the border that is generated by the
            // ControlTemplate of the Button
            //
            Border borderInTemplate = (Border)
                button.Template.FindName("border", button);

            // Do something to the ControlTemplate-generated border
            //
            MessageBox.Show(
              "The actual width of the border in the ControlTemplate: "
              + borderInTemplate.GetValue(Border.ActualWidthProperty));
        }
    }
}

Create a Custom ToolTip Style

Problem

You need to create a custom System.Windows.Style for the System.Windows.Controls.ToolTip for an element.

Solution

Define a typed Style for the ToolTip class, and specify custom content and appearance properties.

How It Works

The System.Windows.FrameworkElement class exposes a ToolTip property that allows you to set the text that should appear in the tool tip. Because the ToolTip class derives from System.Windows.Controls.ContentControl, you can create a typed Style for it and define a custom System.Windows.Controls.ControlTemplate.

Warning

The content of a ToolTip can contain interactive controls such as Button controls, but they never get focus, and you can't click or otherwise interact with them.

The Code

The following example displays a System.Windows.Controls.TextBox with a ToolTip and a custom ToolTip style:

<Window x:Class="Recipe_06_18.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_18" Height="160" Width="300">
    <Window.Resources>

        <Style TargetType="{x:Type ToolTip}">
            <Setter Property="HasDropShadow"
                    Value="True"/>
            <Setter Property="OverridesDefaultStyle"
                    Value="True"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate
                        TargetType="{x:Type ToolTip}">
                        <Border Name="Border"
                            BorderBrush="DarkGray"
                            BorderThickness="1"
                            Width="{TemplateBinding Width}"
                            Height="{TemplateBinding Height}"
                            CornerRadius="4">
                            <Border.Background>
                                <LinearGradientBrush
                                    StartPoint="0,0"
                                    EndPoint="0,1">
                                    <GradientStop
                                        Color="Snow"
                                        Offset="0.0"/>
<GradientStop
                                        Color="Gainsboro"
                                        Offset="1.0"/>
                                  </LinearGradientBrush>
                              </Border.Background>
                              <StackPanel Orientation="Horizontal">
                                  <Image Margin="4,4,0,4" Source="help.gif"/>
                                  <ContentPresenter
                                    Margin="4"
                                    HorizontalAlignment="Left"
                                    VerticalAlignment="Top" />
                                  </StackPanel>
                          </Border>
                      </ControlTemplate>
                 </Setter.Value>
             </Setter>
         </Style>

    </Window.Resources>

    <Grid>
        <Border Margin="8"
                BorderThickness="1"
                BorderBrush="Black"
                Width="160"
                Height="60">
            <TextBlock Foreground="DarkGray"
                       VerticalAlignment="Center"
                       HorizontalAlignment="Center"
                       ToolTip="This is a custom tooltip"
                       Text="Mouse Over for tooltip"/>
        </Border>
    </Grid>

</Window>

Figure 6-16 shows this custom tool tip.

A tool tip with a custom style

Figure 6-16. A tool tip with a custom style

Dynamically Change the Skin of an Application

Problem

You need to dynamically customize the look and feel of an entire application.

Solution

Create a System.Windows.ResourceDictionary for each custom skin, and use the System. Windows.Application.LoadComponent method to dynamically load one at runtime.

How It Works

Each System.Windows.ResourceDictionary should contain the named resources such as VisualBrush, Style, and ControlTemplate for each custom skin you want the application to be able to use. The Application.LoadComponent method can dynamically load one of the resource dictionaries at runtime and apply it to the Application.Current.Resources property. If the visual elements in your XAML use the DynamicResource markup extension, instead of StaticResource, then they can change their styles and visual appearance dynamically, pulling them from the selected ResourceDictionary.

The Code

The following example demonstrates an application that creates four resource dictionaries in separate XAML files. Figure 6-17 shows the solution tree. In each resource dictionary, there are two named styles, which are referenced as dynamic resources in the XAML for a window containing a number of different elements.

When the selected index of the SkinsComboBox System.Windows.Controls.ComboBox is changed, there is application logic in the code-behind to dynamically load the appropriate resource dictionary and use it to set the application's current resources.

Figure 6-17 shows the solution tree with the four resource dictionaries.

Creating multiple resource dictionaries

Figure 6-17. Creating multiple resource dictionaries

The XAML for the window is as follows:

<Window x:Class="Recipe_06_19.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="clr-namespace:System;assembly=mscorlib"
    xmlns:Recipe_06_19="clr-namespace:Recipe_06_19;assembly="
    Title="WPF Recipes 6_19" Height="228" Width="300">

    <Window.Resources>

        <!-- Base Style -->
        <Style x:Key="baseControlStyle"
                TargetType="{x:Type Control}">
              <Setter Property="FontFamily" Value="Tahoma" />
              <Setter Property="FontSize" Value="11pt"/>
              <Setter Property="Margin" Value="4"/>
              <Setter Property="Foreground"
                      Value="{DynamicResource TextForegroundBrush}" />
              <Setter Property="Background"
                      Value="{DynamicResource BackgroundBrush}" />
          </Style>

          <!-- Button Style -->
          <Style TargetType="{x:Type Button}"
                 BasedOn="{StaticResource baseControlStyle}">
          </Style>

          <!-- CheckBox Style -->
          <Style TargetType="{x:Type CheckBox}"
                 BasedOn="{StaticResource baseControlStyle}">
</Style>

         <!-- TextBox Style -->
         <Style TargetType="{x:Type TextBox}"
                BasedOn="{StaticResource baseControlStyle}">
         </Style>

         <!-- ComboBox Style -->
         <Style TargetType="{x:Type ComboBox}"
                BasedOn="{StaticResource baseControlStyle}">
         </Style>

         <!-- Skins -->
         <ObjectDataProvider x:Key="Skins"
                             MethodName="GetValues"
                             ObjectType="{x:Type s:Enum}">
             <ObjectDataProvider.MethodParameters>
                 <x:Type TypeName="Recipe_06_19:Skin" />
             </ObjectDataProvider.MethodParameters>
         </ObjectDataProvider>

    </Window.Resources>

    <Grid>
        <StackPanel>
            <StackPanel Orientation="Horizontal" >
                <Label Content="Choose a skin:"
                       VerticalAlignment="Center"
                       Foreground="{DynamicResource
                       TextForegroundBrush}"/>
                <ComboBox x:Name="SkinsComboBox"
                       Height="24" Width="160"
                       IsSynchronizedWithCurrentItem="True"
                       SelectionChanged="SkinsComboBox_SelectionChanged"
                       ItemsSource="{Binding Mode=OneWay,
                       Source={StaticResource Skins}}"/>
            </StackPanel>

            <CheckBox>Hello World</CheckBox>
            <TextBox>Hello World</TextBox>
            <Button>Hello World</Button>
            <Button>Hello World</Button>
            <ComboBox>Hello World</ComboBox>

        </StackPanel>
    </Grid>
</Window>

The code-behind for the window is as follows:

using System;
using System.Windows;
using System.Windows.Controls;

namespace Recipe_06_19
{
    public partial class Window1 : Window
    {
        public Window1()
       {
           InitializeComponent();
        }

        private void SkinsComboBox_SelectionChanged(
                         object sender,
                         SelectionChangedEventArgs e)
        {
            ResourceDictionary resourceDictionary;
            Skin skin = (Skin)((ComboBox)sender).SelectedItem;

            switch (skin)
            {
                case Skin.Red:
                   resourceDictionary = Application.LoadComponent(
                       new Uri(@"Skins/RedResources.xaml",
                           UriKind.Relative)) as ResourceDictionary;
                   break;

               case Skin.Green:
                   resourceDictionary = Application.LoadComponent(
                       new Uri(@"Skins/GreenResources.xaml",
                           UriKind.Relative)) as ResourceDictionary;
                   break;

               case Skin.Blue:
                   resourceDictionary = Application.LoadComponent(
                       new Uri(@"Skins/BlueResources.xaml",
                           UriKind.Relative)) as ResourceDictionary;
                   break;

               default:
                   resourceDictionary = Application.LoadComponent(
                       new Uri(@"Skins/DefaultResources.xaml",
                           UriKind.Relative)) as ResourceDictionary;
                   break;
            }
Application.Current.Resources = resourceDictionary;
        }
    }

    public enum Skin
    {
        Default,
        Red,
        Green,
        Blue
    }
}

Figure 6-18 shows the resulting window with a blue skin applied to the controls.

Dynamically changing the skin of an application

Figure 6-18. Dynamically changing the skin of an application

Create Styles That Adapt to the Current OS Theme

Problem

You need to create styles and templates that adapt to the current operating system (OS) theme.

Solution

Use the System.Windows.SystemColors, System.Windows.SystemFonts, and System.Windows. SystemParameters classes in System.Windows.Styles to specify the values for brushes, colors, and fonts. Define theme-specific resource dictionaries in a themes subfolder in your application.

How It Works

The values of SystemColors, SystemFonts, and SystemParameters are automatically updated when the Windows OS theme changes.

Create a themes subfolder in the root of your project, and put theme-specific resource dictionaries in the subfolder. The resource dictionary files need to be named themes<ThemeName>.<ThemeColor>.xaml, where ThemeName and ThemeColor correspond to the following valid Microsoft themes (case is insensitive):

  • The Windows Vista theme: aero.normalcolor.xaml

  • The default blue Windows XP theme: luna.normalcolor.xaml

  • The olive green Windows XP theme: luna.homestead.xaml

  • The silver Windows XP theme: luna.metallic.xaml

  • The Windows XP Media Center Edition 2005 and Windows XP Tablet PC Edition 2005 theme: Royale.NormalColor.xaml

  • The Windows Classic theme: Classic.xaml

  • The Zune Windows XP theme: Zune.NormalColor.xaml

You can also specify a generic dictionary that gets used if there isn't a dictionary corresponding to the current theme and color. This should be named Generic.xaml.

You must then specify the ThemeInfoAttribute in the application's AssemblyInfo.cs file. This specifies where the automatic theming mechanism should look for the theme dictionaries and the generic dictionary. Each option can be set to one of the following values:

  • None (default): Don't look for a resource dictionary.

  • SourceAssembly: The dictionary is the current assembly.

  • ExternalAssembly: The dictionary is in a different assembly, which must be named <AssemblyName>.<ThemeName>.dll, where <AssemblyName> is the current assembly's name.

If the theme dictionaries specify styles for controls that are defined in external assemblies, for example, the WPF controls such as System.Windows.Controls.ProgressBar and System.Windows.Button, then you must use the ThemeDictionaryExtension to specify the application as the source for the theme dictionaries.

The Code

The following example creates a ProgressBar and two Button elements. It uses the SystemColors class for the Foreground property of one Button and references a custom brush for the other and a custom style for the ProgressBar.

<Window x:Class="Recipe_06_20.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 6_20" Height="134" Width="200">
<StackPanel>
        <ProgressBar Value="30"
                     HorizontalAlignment="Center"
                     Margin="4"
                     Style="{DynamicResource CustomProgressBarStyle}"/>
        <Button Margin="4"
                Content="Custom Brush"
                Foreground="{DynamicResource ButtonText}"/>
        <Button Margin="4"
                Content="System Brush"
                Foreground="{DynamicResource
                   {x:Static SystemColors.ActiveCaptionBrushKey}}"/>
    </StackPanel>
</Window>

There is a themes subfolder in the solution tree, which contains the theme dictionaries, named according to the Windows themes convention. Each theme dictionary contains its version of the custom resources used by the ProgressBar and Button controls.

Figure 6-19 shows the solution tree with the themes subfolder and the theme dictionaries.

Creating theme dictionaries in a project

Figure 6-19. Creating theme dictionaries in a project

For example, the Luna.Homestead.xaml resource dictionary contains the following Brush and Style definitions:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <SolidColorBrush x:Key="ButtonText" Color="Green"/>
<Style
        x:Key="CustomProgressBarStyle"
        TargetType="{x:Type ProgressBar}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate
                        TargetType="{x:Type ProgressBar}">
                    <Grid MinHeight="20" MinWidth="240">
                        <Border
                         Name="PART_Track"
                         Background="{DynamicResource
                           {x:Static SystemColors.InactiveCaptionBrushKey}}"
                         BorderBrush="{DynamicResource
                           {x:Static SystemColors.InactiveBorderBrushKey}}"
                         BorderThickness="1"
                              />
                        <Border
                          Name="PART_Indicator"
                          Background="{DynamicResource
                            {x:Static SystemColors.ActiveCaptionBrushKey}}"
                          BorderBrush="{DynamicResource
                            {x:Static SystemColors.ActiveBorderBrushKey}}"
                          BorderThickness="1"
                          HorizontalAlignment="Left"
                             />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

The ThemeInfoAttribute is declared in the application's AssemblyInfo.cs file, specifying the current assembly as the source of both the theme dictionaries and the generic dictionary:

[assembly: ThemeInfo(
    ResourceDictionaryLocation.SourceAssembly,
    ResourceDictionaryLocation.SourceAssembly
)]

Furthermore, in App.xaml, the ThemeDictionaryExtension is used to specify the application as the source of theme styles for externally defined elements:

<Application x:Class="Recipe_06_20.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml">
    <Application.Resources>

        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary
                     Source="{ThemeDictionary Recipe_06_20}"/>
            </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>

   </Application.Resources>

</Application>

Figure 6-20 shows the same window, viewed in two different Windows themes.

Tip

To change the OS theme in Windows XP, right-click the Windows desktop and select Properties. Then select the Appearance tab in the dialog box, and set the required theme in the Color Scheme drop-down box.

The same progress bar and buttons under the Windows XP blue theme and the olive green theme

Figure 6-20. The same progress bar and buttons under the Windows XP blue theme and the olive green theme

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset