Style is used to share properties, resources, and event handlers across different instances of a type. You can think of a Style as a shortcut for applying a set of property values to multiple elements.
That is MSDN’s description of Style—fairly conventional, as translations go. Put simply, Style is a batch operation on property values. Much like CSS on the web, it lets you apply a whole set of property values to UI elements at once.
Example
The simplest Style example:
<Window>
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Button}" x:Key="ButtonStyle">
<Setter Property="Height" Value="22"/>
<Setter Property="Width" Value="60"/>
</Style>
</Grid.Resources>
<Button Content="Button" Style="{StaticResource ButtonStyle}"/>
<Button Content="Button" Style="{StaticResource ButtonStyle}" Margin="156,144,286,145" />
</Grid>
</Window>
For Resources, see MSDN. Here we create a ButtonStyle whose target type is Button. The two Buttons find this Style through static resource (StaticResource) lookup. The Style defines the Button’s Height and Width. Once the Style is applied, neither Button needs manual sizing—they automatically take the preset values 22 and 60 from ButtonStyle.
As a batch mechanism for properties, resources, and events, Style provides a shortcut for configuring controls quickly. There are two main benefits:
- Extract common settings into a Style so those controls share a consistent look; changing property values in the Style conveniently affects every control that uses it.
- Define multiple Styles for the same control type and switch looks by swapping the Style.
Style Elements
In the Style example above, Setter was used inside Style to define preset property values. Style not only batches property values but can also share resources and event handlers:
<Window>
<Window.Resources>
<Style TargetType="{x:Type Button}" x:Key="ButtonStyle">
<Style.Resources>
<SolidColorBrush x:Key="brush" Color="Yellow"/>
</Style.Resources>
<Setter Property="Height" Value="22"/>
<Setter Property="Width" Value="60"/>
<EventSetter Event="Loaded" Handler="Button_Loaded"/>
</Style>
</Window.Resources>
<Grid>
<Button x:Name="button1" Style="{StaticResource ButtonStyle}" Background="{DynamicResource brush}"/>
<Button x:Name="button2" Style="{StaticResource ButtonStyle}" Background="{DynamicResource brush}" Margin="156,144,286,145" />
</Grid>
</Window>
The Button_Loaded handler lives in the code-behind:
void Button_Loaded(object sender, RoutedEventArgs e)
{
MessageBox.Show((sender as Button).Name + " Loaded");
}
The Style defines the SolidColorBrush resource, sets Height and Width, and uses EventSetter to attach the Loaded handler.
Trigger
Style uses Setter and EventSetter to set a control’s properties and event handlers respectively—the word “setter” simply means “to set.” After designing those two kinds of settings, Style introduces a more advanced idea: conditional setting.
For a plain Setter such as <Setter Property="Height" Value="22"/>, the meaning is obvious: set height to 22. Conditional setting means assigning a value only when some condition holds.
WPF introduces the Trigger to express that condition, written like this:
<Style TargetType="{x:Type Button}" x:Key="ButtonStyle">
<Setter Property="Width" Value="60"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Width" Value="80"/>
</Trigger>
</Style.Triggers>
</Style>
Here the Trigger means: when the Button’s IsMouseOver property is set to True, set the Button’s Width to 80.
Inside a Style you do not need to specify the Setter’s TargetName; the default target is the control using the Style. As a trigger, when it fires it sets width to 80; when IsMouseOver becomes False—i.e. the condition is no longer met—width returns to the default Setter value of 60.
WPF defines five kinds of Trigger as conditions: Trigger, DataTrigger, MultiTrigger, MultiDataTrigger, and EventTrigger. Their conditions are:
- Trigger: a control property is the condition—for example, the earlier case when IsMouseOver is True.
- DataTrigger: a property on the control’s DataContext is the condition.
- MultiTrigger: multiple control properties form the condition.
- MultiDataTrigger: multiple DataContext properties form the condition.
- EventTrigger: a RoutedEvent is the condition; it fires when the specified routed event is raised.
For detailed usage of these five Triggers, see MSDN; they are not described further here.
Implicit Style
In the examples above, Style was always set with StaticResource. You can of course also use DynamicResource. Both approaches require an explicit reference in XAML or code-behind. For convenience, WPF offers an Implicit Style that can be applied to controls automatically:
<Window>
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Height" Value="22"/>
<Setter Property="Width" Value="60"/>
</Style>
</Grid.Resources>
<Button x:Name="button1" Style="{x:Null}"/>
<Button x:Name="button2" Margin="156,144,286,145" />
<Button x:Name="button3" Margin="196,144,0,145" />
</Grid>
</Window>
When the Style is defined in the Grid’s Resources without a Key, it is applied automatically to every child Button of the Grid. If a Button explicitly sets a Style—like button1, which sets it to Null—the implicit Style does not apply.
Digging Deeper into Style
Style is a good concept. For a presentation framework, separating structure, style, and behavior of UI objects is a solid design. Style is also easy to pick up; even Implicit Style feels like a natural idea. In practice, however, problems appear. WPF often follows this pattern: a nice concept, a simple description, a bright future—and bizarre bugs. To explain those issues clearly, we need to start from the root: what exactly is a Style?
By the usual intuition, Style should be something like a Dictionary<string, object> of setters—pre-stored property names and preset values that are then applied to UI objects. WPF’s ambitions for Style go much further. It packs in a great many features around several key technologies. Let’s walk through them in detail.
Style and Dependency Property
The Dependency Property (DP for short) is at the heart of WPF, and Style is built on Dependency Properties. For the internals of DPs, see Deep Dive into WPF — Dependency Property (Part 1). A Style’s Setter acts on DPs; if you define a plain CLR property on a control, Style cannot set it. The essence of Dependency Property design is to peel field storage away from the object (DependencyObject). A single property value is stored across multiple internal fields, and the current value is decided by the priority of the value sources.
The priority of Dependency Property value sources (lowest to highest from top to bottom) is:
public enum BaseValueSource
{
Unknown,
Default,
Inherited,
DefaultStyle,
DefaultStyleTrigger,
Style,
TemplateTrigger,
StyleTrigger,
ImplicitStyleReference,
ParentTemplate,
ParentTemplateTrigger,
Local
}
A concrete example:
<Window>
<Window.Resources>
<Style TargetType="{x:Type Button}" x:Key="ButtonStyle">
<Setter Property="Width" Value="60"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Width" Value="80"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button x:Name="button1" Style="{StaticResource ButtonStyle}" Background="{DynamicResource brush}" Width="20"/>
</Grid>
</Window>
Line 4 uses the Style’s Setter to set Width=60; this priority is Style. Line 6 sets Width=80 when IsMouseOver is True; this priority is StyleTrigger. Line 13 defines Width=20 directly on the Button that uses the Style; this priority is Local. Local has the highest priority, so even when the mouse is over the Button, the Trigger on line 6 does not take effect because its priority is not high enough. Remove Width="20" from line 13, and hovering the Button makes Width become 80; when the mouse leaves, it returns to the 60 set on line 4.
Style and FrameworkElement
Style is a property defined on FrameworkElement, so every control that inherits from FrameworkElement can use Style. FrameworkElement defines several styles: Style, ThemeStyle, and FocusVisualStyle.
- FocusVisualStyle: the dashed rectangle shown around a control when it receives keyboard focus. This style is not applied directly to the corresponding FrameworkElement. When the control gets keyboard focus, the AdornerLayer creates a new Control, FocusVisualStyle is applied to that Control, and it is overlaid on the original FrameworkElement to form the dashed frame.
- Style: the Style we have been setting all along.
- ThemeStyle: this introduces the concept of a theme—let’s talk about it.
Windows defines many themes, and you can switch themes in Control Panel:
The top two rows belong to the Aero theme. Switching from Aero to Windows Classic changes the appearance of the taskbar, windows, and the controls inside them. To support theme switching more cleanly, WPF introduced ThemeStyle. When you generate a custom control from a VS2010 template, it automatically adds a Themes folder and a Generic.xaml file:
The Aero.NormalColor.xaml file here was added by hand; set that aside for a moment and talk about default control styles.
WPF ships many controls—Button, ListBox, TabControl, and so on. When we use them we do not specify a Style; WPF provides a default Style that is tied to the Windows theme. For example, if you switch Windows from Aero to Classic, the look of controls inside WPF windows changes as well. Those default Styles are stored as ResourceDictionaries in DLLs such as PresentationFramework.Aero.dll and PresentationFramework.Classic.dll. The naming convention is: assembly name + theme name + .dll.
How does WPF find the matching ThemeStyle for the current Windows theme? WPF introduces the ThemeInfo attribute to specify theme information. ThemeInfo is usually defined in Properties/AssemblyInfo.cs:
[assembly: ThemeInfo(
ResourceDictionaryLocation.SourceAssembly,
ResourceDictionaryLocation.SourceAssembly)]
ThemeInfo has two parameters. The first refers to ThemeResource and the second to GenericResource; their type is ResourceDictionaryLocation:
public enum ResourceDictionaryLocation
{
None = 0,
SourceAssembly = 1,
ExternalAssembly = 2,
}
For ResourceDictionaryLocation: None means there is no corresponding resource; SourceAssembly means the resource lives in this assembly; ExternalAssembly means the resource lives in an external assembly, found by the rule we already saw: assembly name + theme name + .dll.
For any control—whether built-in or custom—when WPF starts, it looks up the ThemeStyle matching the current Windows theme. The lookup rules are:
- First, find the assembly for the control’s Type, then read its ThemeInfo to see where ThemeResource and GenericResource live. If ThemeResource is not None, the system reads the corresponding ResourceDictionary and looks for
TargetType={x:Type controlType}. If found, that Style becomes the control’s ThemeStyle. - If the first step fails, GenericResource comes into play—“generic” meaning general. WPF looks at ThemeInfo’s second parameter, GenericResource, using the same rules as step one. On success, that Style becomes the control’s ThemeStyle.
Any control that does not explicitly set Style and cannot find a default ThemeStyle has no visual appearance. For convenience, when you add a custom control via VS, VS generates Generic.xaml by default. If you want the custom control to track system theme changes as well, add a corresponding ResourceDictionary under Themes—such as the Aero.NormalColor.xaml above—and set ThemeInfo’s first parameter to SourceAssembly, indicating that this assembly supports system theme changes and that the resource files live in it. The ResourceDictionary must sit under the Themes folder, because WPF locates it with something like:
string relativePackUriForResources = "/" +
themeAssemblyName.FullName +
";component/themes/" +
themeName + "." +
colorScheme + ".xaml";
Style and ResourceDictionary
We have mentioned ResourceDictionary many times; the WPF resource system will be covered in more detail later. The resource system stores resources in a ResourceDictionary. As the name implies, it is a dictionary—key/value pairs. When we created a Style in a Window’s Resources earlier, we gave the Style a key (x:Key) and later referenced that key with StaticResource.
If you add a Button object to a ResourceDictionary without a key (x:Key), the code will not compile. Yet the Implicit Style we introduced earlier only specified TargetType={x:Type type} and no key—why does it compile?
When you add a Style to a ResourceDictionary without specifying x:Key, WPF generates a key for you. That key is not a string; it is of type object—specifically a Type instance. So it is equivalent to:
<Style TargetType="{x:Type Button}" x:Key="{x:Type Button}">
The trailing x:Key can be omitted.
Both Application and FrameworkElement define a Resources property, each holding a ResourceDictionary. Resource lookup follows a nearest-wins principle:
<Window>
<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Yellow"/>
</Style>
<Style TargetType="{x:Type ToggleButton}" x:Key="toggleBtnStyle">
<Setter Property="Background" Value="Red"/>
</Style>
</Window.Resources>
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Blue"/>
</Style>
<Style TargetType="{x:Type ToggleButton}" x:Key="toggleBtnStyle">
<Setter Property="Background" Value="Green"/>
</Style>
</StackPanel.Resources>
<ToggleButton Width="80" Height="20" Style="{DynamicResource toggleBtnStyle}"/>
<Button Width="80" Height="20" Content="button2" Click="Button_Click"/>
</StackPanel>
</Window>
Both Window and StackPanel Resources define toggleBtnStyle and an implicit Style for Button. By nearest-wins, the ToggleButton and Button inside StackPanel use StackPanel’s resources rather than Window’s.
Style Merge
This brings us to the focus of this article—a topic that is easy to overlook and yet frequently a source of bugs: Style merging.
We have mentioned many styles: ThemeStyle, Style, Implicit Style. Style is a batch of property values. For any single property there can be only one preset value, not many. At runtime these styles must be merged and then applied to the FrameworkElement.
Merging Styles happens in two steps:
- Find all relevant Styles.
- Determine their priority and merge by that priority.
For a Button:
- If the current Windows theme is Aero, after startup the corresponding ThemeStyle is found in PresentationFramework.Aero.dll.
- If the Button uses StaticResource or DynamicResource to specify a Style, that Style is found in the resource system by key.
- If no Style is set explicitly on the Button, the resource system looks for an Implicit Style (
x:Type Button). - Steps 2 and 3 are mutually exclusive—they identify at most one Style. That Style is then merged (Merge) with the ThemeStyle to produce the Button’s final look.
Start with the merge itself. Explicit and Implicit styles have higher priority than ThemeStyle. If both a Style and a ThemeStyle set the same property via Setter, the Style’s Setter wins and ThemeStyle is ignored. EventSetter is special: it uses a RoutedEvent. If both styles’ EventSetters handle the same RoutedEvent, both are registered on that RoutedEvent.
As we saw, explicit and Implicit styles are mutually exclusive—only one applies. In real projects, however, it is common to define a global base style for Button and then apply special handling on top of that base at the point of use. To support that, Style has a BasedOn property to express inheritance:
<Window>
<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Width" Value="80"/>
<Setter Property="Height" Value="20"/>
<EventSetter Event="Click" Handler="btnBase_Click"/>
</Style>
<Style TargetType="{x:Type ButtonBase}" x:Key="toggleBtnStyle">
<Setter Property="Width" Value="80"/>
<Setter Property="Height" Value="20"/>
<Setter Property="Background" Value="Red"/>
</Style>
</Window.Resources>
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="Blue"/>
<EventSetter Event="Click" Handler="btn_Click"/>
</Style>
<Style TargetType="{x:Type ToggleButton}" x:Key="toggleBtnStyle" BasedOn="{StaticResource toggleBtnStyle}">
<Setter Property="Background" Value="Green"/>
</Style>
</StackPanel.Resources>
<ToggleButton Style="{DynamicResource toggleBtnStyle}"/>
<Button Content="button2"/>
</StackPanel>
</Window>
To explain more clearly, I used an uncommon example. Line 16 creates an Implicit Style for Button whose BasedOn is itself an Implicit Style for Button. The resource system walks up and finds the Implicit Style for Button in Window.Resources, then merges the two. A single ResourceDictionary cannot have duplicate keys; StackPanel and Window each have their own ResourceDictionary, so their keys do not clash, and nearest-wins finds the highest-priority resource during lookup. The ToggleButton example on line 20 is the same as Button. The difference is that the toggleBtnStyle found on line 8 has TargetType ButtonBase—ButtonBase is the base class of ToggleButton—so BasedOn still works.
WPF’s Style mechanism is a sealing (Seal) mechanism. Its authoring model is flexible and supports merging, but once merging is complete the Style is sealed, and its Setters and other members can no longer be modified. That sealing design has its reasons, but it falls short on Style dynamism.
Consider a custom control. Suppose you custom-derive a Button named MyButton that inherits from Button. In custom controls you often see code like:
static MyButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton), new FrameworkPropertyMetadata(typeof(MyButton)));
}
Here DefaultStyle appears—another name WPF uses for ThemeStyle. ThemeStyle is what determines the default Style; later, BaseValueSource also uses DefaultStyle to mean ThemeStyle. Overriding the internal metadata of DefaultStyleKeyProperty in MyButton’s static constructor tells the WPF system that the key used to look up MyButton’s ThemeStyle has changed from {x:Type Button} to {x:Type MyButton}.
If you change DefaultStyleKeyProperty as above, you must define MyButton’s default (Theme) Style in Themes/Generic.xaml; otherwise MyButton has no appearance, because the ThemeStyle lookup key has changed and assemblies such as PresentationFramework.Aero.dll do not define {x:Type MyButton}.
That covers ThemeStyle usage. Back to Implicit Style: even if you define an Implicit Style for Button (TargetType={x:Type Button}) in Application.Resources, every MyButton control will not use that Implicit Style unless you also define, below the Button Implicit Style in Application.Resources:
<Style TargetType="{x:Type local:MyButton}" BasedOn="{StaticResource {x:Type Button}}"/>
This brings us back to Style merging. Style’s merge is very basic (rather dumb)—it is not automatic. Specifically:
- An Implicit Style for a base control type does not apply to derived control types.
- If Implicit Styles for Button are defined separately in Window and StackPanel as above, they are not intelligently merged and then applied to the Button; nearest-wins picks only one.
- Style’s BasedOn only supports StaticResource references, because Style inherits from DispatcherObject rather than DependencyObject, and DynamicResource only supports DPs.
All of these must be solved through BasedOn. Because BasedOn uses a static reference, updating Implicit Styles at runtime becomes painful.
Theming / Skinning
Skinning a UI can look very slick. There are two kinds: (1) replace the entire control Style; (2) replace the color brushes inside the Style. The second is simple: define color brush resources in a ResourceDictionary, bind with DynamicResource when using the brush, and when re-skinning just swap the brush resource dictionary.
Many companies maintain their own skin libraries. Those libraries are usually Implicit Styles—Implicit Styles for all controls—and at use time the skin resource is merged into Application.Resources. To change skins, remove the old skin resource from Application.Resources and replace it with the new skin’s ResourceDictionary.
That approach is easy to understand, but it breaks down with BasedOn. BasedOn uses StaticResource—a one-shot static reference. After a new skin library is added to Application resources, if Application resources already define an Implicit Style such as <Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}"/>, the controls will not update their skin. If you have that requirement, you must merge Styles manually, something like:
public static void Merge(this Style style, Style otherStyle)
{
foreach (SetterBase currentSetter in otherStyle.Setters)
{
style.Setters.Add(currentSetter);
}
foreach (TriggerBase currentTrigger in otherStyle.Triggers)
{
style.Triggers.Add(currentTrigger);
}
foreach (object key in otherStyle.Resources.Keys)
{
style.Resources[key] = otherStyle.Resources[key];
}
}
You still need additional condition checks here, and a decision about whether to recursively merge otherStyle’s BasedOn chain. Returning to the earlier point: the application needs DynamicResource to watch for Implicit Style changes in Application resources. An attached property can solve that:
public static readonly DependencyProperty AutoMergeStyleProperty =
DependencyProperty.RegisterAttached("AutoMergeStyle", typeof(Type), typeof(Behavior),
new FrameworkPropertyMetadata((Type)null,
new PropertyChangedCallback(OnAutoMergeStyleChanged)));
private static void OnAutoMergeStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue == e.NewValue)
{
return;
}
FrameworkElement control = d as FrameworkElement;
if (control == null)
{
throw new NotSupportedException("AutoMergeStyle can only be used in FrameworkElement");
}
Type type = e.NewValue as Type;
if (type != null)
{
control.SetResourceReference(Behavior.BaseOnStyleProperty, type);
}
else
{
control.ClearValue(Behavior.BaseOnStyleProperty);
}
}
SetResourceReference is the code form of DynamicResource in XAML—equivalent to Behavior.BaseOnStyle="{DynamicResource type}". Applying SetResourceReference to a control watches the key type on a custom attached property BaseOnStyleProperty. When re-skinning replaces Application’s resource dictionary, the BaseOnStyle property is updated. In BaseOnStyleProperty’s Changed handler you can read the control’s Style and the new ThemeStyle, call Merge on the two, and set the result back on the control’s Style.
Conclusion
WPF’s Style design is fairly conventional. Separating an object’s style from its structure was the original goal, and the design also includes good ideas such as Trigger. In practice, though, problems still appear; Style is neither as smart nor as perfect as one might hope. I hope readers will look at Style from the inside out and get more out of it.
Aside
This “Deep Dive into WPF” series has grown to several posts. Writing articles takes more patience and care than talking through something out loud. There are many levels of explanation: making simple things complicated; making complicated things complicated; making complicated things simple; and making complicated things simple while adding poetry and philosophy. I cannot claim to reach that highest level. I hope instead to explain the technical main line directly and without avoidance, and I hope to hear more feedback so I can keep expanding and improving the series.
Thank you for your support—and for clicking the upvote button. ^_^


Top comments (0)