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)
You need to display a UI control, or set of controls, with a custom look and style, instead of using the default display style.
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.
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 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.
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.
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.
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 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.
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.
You need to override the value of a property that has been set by a named or typed System.Windows.Style.
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 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.
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.
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 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.
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
.
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 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.
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...."
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.
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 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.
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...."
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.
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 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.
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.
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.
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 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); } } }
You need to set which System.Windows.Style
to apply to a UI element programmatically, based on custom application logic.
Use the System.Windows.FrameworkElement.FindResource
method to locate and apply the required Style
resource to the UI element.
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 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.
Set the Style
property of the element to the System.Windows.Markup.NullExtension
markup extension.
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 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.
You need to change the System.Windows.Style
of items in a System.Windows.Controls.ListBox
to change the appearance of alternate rows.
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 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.
You need to change the appearance of an item in a System.Windows.Controls.ListBox
when it is selected.
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
.
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 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.
Create a System.Windows.Controls.ControlTemplate
, and apply it to the Template
property of a System.Windows.Controls.Control.
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.
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 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.
You need to share a System.Windows.Controls.ControlTemplate
across multiple instances of a System.Windows.Controls.Control
.
Create a typed System.Windows.Style
, and set the Template
property to an inline ControlTemplate
.
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 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.
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.
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.
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 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.
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.
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 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.
You need to locate an element in the visual tree of a System.Windows.Controls.ControlTemplate
and access the values of its properties.
The FindName
method finds the element associated with the specified name, defined within the template.
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)); } } }
You need to create a custom System.Windows.Style
for the System.Windows.Controls.ToolTip
for an element.
Define a typed Style
for the ToolTip
class, and specify custom content and appearance properties.
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
.
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 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.
Create a System.Windows.ResourceDictionary
for each custom skin, and use the System. Windows.Application.LoadComponent
method to dynamically load one at runtime.
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 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.
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.
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.
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 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.
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.