WPF Custom Visualization Part 1 of some: Styling






4.71/5 (29 votes)
(Yet Another) Investigation of WPF styling
Content
- Introduction
- Ways of Customization
- Styling in WPF: what are the variables
- Basic styling in WPF: setting properties
- Crossing application and library boundaries
- A somewhat special case: the ItemsControl
- Setting eventhandlers: the
EventSetter
- Conclusion
- Version history
Introduction
This series of articles is a deep dive into the possibilities for customization of controls in WPF. Allthough there are allready various articles explaining the possibilities of customizing WPF controls, I wanted this one to provide an extensive explanation on the subject and also to provide examples on what works but also on what doesn't.
Of course, it is up to you to decide if I succeeded.
The article only handles using XAML as a way for customization (with some exceptions if certain things require coding)
Ways of Customization
WPF provides 2 main ways of customizing controls:
- Styling: with Styling you can set various properties on the controls.
- Templating: with Templating you can completely replace the standard look of your control with something else.
For this, I split the series in N parts, for which I currently have the following planned:
- WPF Custom Visualization Part 1 of N: Styling (this article)
- WPF Custom Visualization Part 2 of N: Styling with Triggers
- WPF Custom Visualization Intermezzo: Resources
- WPF Custom Visualization Intermezzo 2: Binding
- WPF Custom Visualization Part 3 of N: Templating
- WPF Custom Visualization Part 4 of N: Addorners
Styling in WPF: what are the variables
Styling in WPF is a way of defining a grouping of values for properties.
As such, there are three variables:
- When is the style composed.
- How to reference the style we want to apply.
- When is the style applied.
Below you will find various cases in an attempt to find the answers to the above three questions and thus distilling a way to predict what will happen when using styles in a WPF application.
Basic styling in WPF: setting properties
Concepts
Basically, if you apply a style to an existing control, you set some of its poperties. These can be any properties and do not necesarily have to be visual properties. Of course, if we define a style, we must also be able to apply it to an object and thus reference it from an object.
The "apply to an object" part has three main possibilities:
- Define the style directly on the button itself.
- Apply the style automagically to objects of a certain class. For this we supply the
TargetType
attribute. Mind however that the style will only be applied to objects of this exact type on not to any descendants of this class. - Apply the style to a specific object. We do this by referencing it from the object to which we want it applied. For this we supply the
x:Key
attribute - .
The visibility of the style is bounded by the container in which it is defined. If you define it in the resource section of a window, it is visible to all controls placed in that window. If you move up a level and define it in the resource section of the application, it is visible in the complete application. If you move down a level and define it in the resource section of a control, it is only visisble inside that control.
Style resources are evaluated at compile time. This means that of all properties set in the style, it must be known to which class they belong. This can be specified by using the TargetType attribute in the Style definition or by prepending the the classname to the propertyname.
How to do it?
As an example the following XAML sets the Background
property of any Button
s in the container in which the Style
is defined to Yellow.
<!-- make all buttons yellow in this window -->
<Style TargetType="Button">
<Setter Property="Background" Value="Yellow" />
</Style>
If we only want to change the Background
of specific Button
s, we specify a key in the style and reference this key in the button
<!-- this style will only be applied to buttons referencing this style through the key -->
<Style x:Key="myStyle" TargetType="Button">
<Setter Property="Background" Value="Orange" />
</Style>
<Button Content="Button (with action)" Style="{StaticResource myStyle}" />
Under the hood, if we only specify the TargetType
attribute, XAML uses it's type as a x:Key
. So, we can achieve the same thing like this:
<!-- Make all labels yellow-ish in this window: following has the same effect as specifying the TargetType -->
<Style x:Key="{x:Type Label}">
<Setter Property="Label.Background" Value="#FFAEAE46" />
</Style>
The above definitions will result in following visuals:
If we want a style to be applicable to multiple control types, we have two options:
- Specify a TargetType with the class name of a common base class on which the property is defined
- Specify the name of a common base class in the property-setter
Following is an example of applying the TargetType
attribute
<!-- by providing a key, we can now apply it to derived controls, like a button, etc... -->
<Style x:Key="myStyleForGenericControl" TargetType="Control">
<Setter Property="Background" Value="Fuchsia" />
</Style>
<Button Content="Button with applied style for base type through its key" Style="{StaticResource myStyleForGenericControl}"/>
<TextBox Text="Textbox with same style as above button through its key" Style="{StaticResource myStyleForGenericControl}" />
The above definitions will result in following visuals:
Following is an example of specifying the type in the Setter
definition
<!-- When not supplying the targettype, you must explicitely provide it in the setter -->
<Style x:Key="myStyleNoTargetType">
<Setter Property="Control.Background" Value="Brown" />
</Style>
Mind however that with option 1 the style will not get automagically applied to all controls derived of that type! Or otherwise stated: if you specify the TargetType
attribute, the style will only automagically be applied to that exact type. As a result, following style will not be aplied to anything because you probably won't place a Control
somewhere in your UI, but instead use Control
derived classes:
<!-- allthough we define the targettype as control, this will NOT get automatically
applied to types inheriting from control -->
<Style TargetType="Control">
<Setter Property="Control.Background" Value="CadetBlue" />
</Style>
You can not specify any non existing properties when providing a TargetType
attribute, and neither can you leave of the type that is defining the property. Following will not compile:
<!-- this will not even compile -->
<Style x:Key="aNotCompileableSample1" TargetType="Control">
<!-- Following will not work or compile: we cannot set a non-existing property -->
<!--<Setter Property="NonExistingProperty" Value="SomeValue" />-->
</Style>
<!-- When not supplying the targettype, you must explicitely provide it in the setter -->
<Style x:Key="aNotCompileableSample2">
<!-- Following will not work or compile: WPF doesn't know to what
property to apply the value -->
<!--<Setter Property="Background" Value="Brown" />-->
</Style>
It is also possible to specify different types for the various Setter
s in a single Style
<!-- can we mix source classes ? -->
<Style x:Key="myStyleMixedControlType">
<!-- The button background definition will also alter the background of other controls to which this style is applied -->
<Setter Property="Button.Background" Value="Brown" />
<!-- The label borderthickness definition will be ignored for any controls without this property -->
<Setter Property="Label.BorderThickness" Value="3" />
</Style>
<Button Content="Button with mixed style applied" Style="{StaticResource myStyleMixedControlType}" />
<!-- strange enough, this label gets the value of the background
allthough we specified a type of button for the property -->
<Label Style="{StaticResource myStyleMixedControlType}" >Label with mixed style applied</Label>
<!-- let's try that again with a button targetted style: this doesn't work !!! -->
<!--<Label Style="{StaticResource myStyle}" >Label with button targetted style applied</Label>-->
The above definitions will result in following visuals:
This may result in some unexpected behaviour (at least it was to me): with the above definition, if you apply it on a label, it's background will also be set! And if applied to types not implementing any of the properties, this Setter
will simply be ignored !
However, think about the type which implements the Background
property: is it the Button
or is it one of it's baseclasses. And does Label
derive from this same baseclass?
Styles are not additive, with which I mean: if you define a style for a control by specifying it's TargetType
and then you specify another x:Key
-ed Style
, then this last one will not adopt the values from the first. To achieve this you need to use the BasedOn
attribute in the Style
definition:
<Style TargetType="Button">
<Setter Property="Background" Value="Yellow" />
</Style>
<Style x:Key="myStyle" TargetType="Button">
<Setter Property="Background" Value="Orange" />
</Style>
<!-- this is a completely new style and doesn't know anything about myStyle
thus, applying it will not set the buttons background, but only its borderbrush
-->
<Style x:Key="myOtherStyle" TargetType="Button">
<Setter Property="BorderBrush" Value="Red" />
</Style>
<!-- this style also sets the background because its based on another style defining the background
the base style is defined through its type: in this case the Yellow background will be used -->
<Style x:Key="myBasedOnStyleThroughType" TargetType="Button"
BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="BorderBrush" Value="Red" />
</Style>
<!-- this style also sets the background because its based on another style defining the background
the base style is defined through it's x:Key: in this case the Orange background
will be used -->
<Style x:Key="myBasedOnStyleThroughKey" TargetType="Button"
BasedOn="{StaticResource myStyle}">
<Setter Property="BorderBrush" Value="Red" />
</Style>
<Button Content="Button with border from style and backgound from basedon style"
Style="{StaticResource myBasedOnStyleThroughType}" />
<Button Content="Button with border from style and backgound from basedon style"
Style="{StaticResource myBasedOnStyleThroughKey}" />
The above definitions will result in following visuals:
Of course, also the scope or visibility of the defined style is important.
The scope is hierarchically determined by the container in which the style is defined. This results in following hierarchy:
- Application
- Window
- (Parent) Control
- Control
This hierarchy is defined by "containment", meaning that a dialog opened by a click on a button inside another window does NOT see the styles defined in this last window
Thus, at an Application
level we get:
<Application x:Class="WpfVisualCustomization.AppWideStyle.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!-- Make all buttons yellow in this application -->
<Style TargetType="Button">
<Setter Property="Background" Value="Yellow" />
</Style>
</Application.Resources>
</Application>
<Window x:Class="WpfVisualCustomization.AppWideStyle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Margin="10">
<!-- I'm yellow because the Application told me so -->
<Button Content="Button" Click="Button_Click"/>
</StackPanel>
</Grid>
</Window>
The application contains all windows it displays. Thus any button on any window of the application will be yellow.
The above definitions will result in following visuals:
A Window with a button opening another Window does NOT contain the opened Window: any resources defining in the opening Window will not be applied to the opened Window, resulting in follwoing visuals:
The Window
level will be clear because we've been using it all along.
So let's move one level down: a container control with styles in it's Resource
section
Any style defined in the level above are still visible here, but if you define a style with the same TargetType
as in the level above, the first will hide the definition of the last. And because specifying only the TargetType
is a different way of applying the x:Key
attribute, the same goes for styles with the same key
<Window>
<Window.Resources>
<!-- make all buttons yellow in this window -->
<Style TargetType="Button">
<Setter Property="Background" Value="Yellow" />
</Style>
<!-- Make all labels yellow-ish in this window: following has the same effect
as specifying the TargetType -->
<Style x:Key="{x:Type Label}">
<Setter Property="Label.Background" Value="#FFAEAE46" />
</Style>
<!-- this style will only be applied to buttons referencing this style
through the key -->
<Style x:Key="myStyle" TargetType="Button">
<Setter Property="Background" Value="Orange" />
</Style>
</Window.Resources>
<Grid>
<StackPanel Grid.Row="1" Margin="10">
<StackPanel.Resources>
<!-- no style defined for a button here, at least not with a
targettype of button -->
<!-- Make all labels green in this stackpanel -->
<Style TargetType="Label">
<Setter Property="Background" Value="Green" />
</Style>
<!-- basedon first looks in the current scope and if nothing
is found, then it moves up a level. In casu, the base style
will be the one defined in the Windows resource section -->
<Style x:Key="greenOnYellow" TargetType="Button"
BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Foreground" Value="Green" />
</Style>
<!-- this style's key also exists at the parent scope -->
<Style x:Key="myStyle" TargetType="Button">
<Setter Property="Background" Value="#FFAC8133" />
</Style>
</StackPanel.Resources>
<!-- following button receives its style from the window resource section -->
<Button Content="Button with style from Window"></Button>
<Label>Label with a style from the stackpanel applied through its type</Label>
<Button Content="Button with style from StackPanel with duplicate key"
Style="{StaticResource myStyle}"></Button>
<Button Content="Button with style from StackPanel BasedOn style from Window"
Style="{StaticResource greenOnYellow}"></Button>
<Expander Header="Expander" HorizontalAlignment="Left">
<Button Content="Button in expander with style from Window"></Button>
</Expander>
</StackPanel>
</Grid>
</Window>
The above definitions will result in following visuals:
Of course, we can NOT use a style across child scope boundaries in different branches of a scope tree. Thus following will not even compile:
<StackPanel Grid.Row="1" Margin="10">
<StackPanel.Resources>
<Style x:Key="greenForground" TargetType="Button">
<Setter Property="Foreground" Value="Green" />
</Style>
</StackPanel.Resources>
</StackPanel>
<StackPanel Grid.Row="2" Margin="10">
<!-- Following will not work or compile: the style is not known in this scope -->
<Button Content="Button with style from StackPanel" Style="{StaticResource greenForground}"></Button>
</StackPanel>
Lets go one step deeper now:
<StackPanel Grid.Row="2" Margin="10">
<Button>
<Button.Content>
<Label>The text of the button in a window styled Label</Label>
</Button.Content>
</Button>
<Button>
<Button.Resources>
<Style TargetType="Label">
<Setter Property="Background" Value="Green" />
</Style>
</Button.Resources>
<Button.Content>
<Label>The text of the button in a locally styled Label</Label>
</Button.Content>
</Button>
<Button>
<Button.Style>
<Style>
<Setter Property="Button.Background" Value="Fuchsia" />
<!-- these are aplied directly to the button which does not have these properties -->
<!-- they are NOT applied to the label inside the content property of the button -->
<Setter Property="Label.BorderBrush" Value="Beige" />
<Setter Property="Label.BorderThickness" Value="3" />
</Style>
</Button.Style>
<Button.Content>
<Label>The text of the button in a locally styled Label (but it doesn't work)</Label>
</Button.Content>
</Button>
<Button>
<Button.Style>
<Style>
<Style.Resources>
<Style TargetType="Label">
<Setter Property="Label.BorderBrush" Value="Beige" />
<Setter Property="Label.BorderThickness" Value="3" />
</Style>
</Style.Resources>
<Setter Property="Button.Background" Value="Fuchsia" />
</Style>
</Button.Style>
<Button.Content>
<Label>The text of the button in a locally styled Label (and it works)</Label>
</Button.Content>
</Button>
</StackPanel>
The above definitions will result in following visuals:
The first example will probably be not much of a surprise: the labels receive their styling from the style defined in the Window
s Resource
section. The second example then just defines the style still one level deeper: in the Resource
section of the Button
itself.
The third example is a, failed, attempt at defining the Label
style inside the style of the Button
. It could have been foreseen of course: with the style setters you can only modify properties of the control you apply the style on. In this case the style is applied on the Button
, so you can only set properties of the Button
itself. The correct way of doing this is shown in example four: you define a new Style
inside the Resource
section of the parent style. Somehow this child Style
is then merged into the resources of the Button
and applied as if it where defined there as in sample 2.
Crossing application and library boundaries
Concepts
Let's find out what happens on the boundaries of executables and dll's
There are two use cases here:
- Applying a style defined in a library project onto controls in our project.
- Applying a style defined in our project onto controls defined in a library project.
For the first case is simply a case of applying the right type of x:Key
on our control. Skip forward to the "How to do it?" section
For the second case we have two subcases:
The first is the basic case where we have a usercontrol in the library with no styling inside the control itself and the simple result of any styles in our application applicable at the scope of the usercontrol being applied.
Of course, the more interesting second case is when styling to controls inside the usercontrol is applied at the usercontrol scope and we also have our own styles defined in our application: how to they interfere with each other?
How to do it?
To be able to apply a Style
defined in a library project to a control in our project we must use the correct typ of key: ComponentResourceKey
. A regular key as used previously will make our Style
unreferenceable outside our library project. A ComponentResourceKey
has two pieces of information:
- The component/DLL in which the
Style
is defined: this is done by providing aType
of a class inside the component. - The key value, similar as to what we have done before
In practice, if we have a library project, we can create a referenceable Style
like this:
<!-- to make the style available outside the library we MUST use a ComponentResourceKey -->
<Style x:Key="{ComponentResourceKey {x:Type local:DummyClass}, myComponentLibStyle}"
TargetType="Button">
<Setter Property="Background" Value="#FF32B6A4" />
<Setter Property="BorderBrush" Value="#FFB8B838" />
</Style>
<!-- following style is not available outside this component/dll -->
<Style x:Key="myLibStyle" TargetType="Button">
<Setter Property="Background" Value="#FF20DAC1" />
<Setter Property="BorderBrush" Value="#FFB8B838" />
</Style>
Of course one problem remains: where do we define this style in XAML: it will be clear we cannot define it inside another Control
or Window
because it would not be visisble: remember the scope of a style doesn't cross children in seperate branches of a tree. We also do not have an Application
object in a library, and even if we where to define one, any styles defined in its resource section are not applicable outside it's scope.
The correct answer is to create a Generic.xaml in a Theme folder and define our style inside a ResourceDictionary
section:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfVisualCustomization.Styling.StyleLib">
<!-- to make our style available outside the library we define it here and we MUST use a ComponentResourceKey -->
</ResourceDictionary>
Finally, in the client application we also use it with the ComponentResourceKey
:
<!-- You must also use a ComponentResourceKey in the client -->
<!--<Button Content="Button (with libstyle from componentkey)" Style="{StaticResource {ComponentResourceKey {x:Type slib:DummyClass}, myComponentLibStyle}}" />-->
<Button Content="Button (with libstyle from componentkey)" Style="{StaticResource {ComponentResourceKey {x:Type slib:DummyClass}, myComponentLibStyle}}" />
<!-- No matter what you try, if you didn't define it with a ComponentResourceKey in the library, you will not be able to access it -->
<!--<Button Content="Button (with libstyle)" Style="{StaticResource myLibStyle}" />-->
<!--<Button Content="Button (with libstyle from componentkey)" Style="{StaticResource {ComponentResourceKey {x:Type slib:DummyClass}, myLibStyle}}" />-->
The above definitions will result in following visuals:
There is a fun thing going on here which might not be clear at first sight.
As Pete O'Hanlon pointed out in the comments (thanks Pete), the "identity" of the key is also dependent on the type used in the ComponentResourceKey
. Thus, altough one might think you only need to provide a Type
for XAML to be able to find the library, the type used also determines the "idenity" (or "equality" if you like) of the Key
. As such, following are actually two different keys:
<!-- Inside the library project -->
<!-- to make the style available outside the library we MUST use a ComponentResourceKey -->
<Style x:Key="{ComponentResourceKey {x:Type local:DummyClass}, myComponentLibStyle}" TargetType="Button">
<Setter Property="Background" Value="#FF32B6A4" />
<Setter Property="BorderBrush" Value="#FFB8B838" />
</Style>
<!-- although we use the same name, this actually is another x:Key because we're
using another type in the ComponentResourceKey -->
<Style x:Key="{ComponentResourceKey {x:Type local:OtherDummyClass}, myComponentLibStyle}" TargetType="Button">
<Setter Property="Background" Value="#FFB8B838" />
<Setter Property="BorderBrush" Value="#FF32B6A4" />
</Style>
<!-- Inside the client project -->
<Button Content="Button (with libstyle from componentkey)" Style="{StaticResource {ComponentResourceKey {x:Type slib:DummyClass}, myComponentLibStyle}}" />
<Button Content="Button (with libstyle from other componentkey)" Style="{StaticResource {ComponentResourceKey {x:Type slib:OtherDummyClass}, myComponentLibStyle}}" />
The above definitions will result in following visuals:
For the second (applying a style defined in our project onto controls defined in a library project) case we differentiate between a CustomControl and a UserControl:
UserControl
If we have a UserControl with no styling applied and use it in a styled window, the controls in the UserControl will get the styles defined in the window:
<!-- the UserControl inside a library project-->
<UserControl>
<Grid>
<StackPanel Margin="10">
<Button Content="An unstyled UserControl with a Button" />
</StackPanel>
</Grid>
</UserControl>
<!-- A window inside our executable project -->
<Window x:Class="VisualCustomization.MainWindow">
<Window.Resources>
<!-- make all buttons yellow in this window -->
<Style TargetType="Button">
<Setter Property="Background" Value="Yellow" />
</Style>
</Window.Resources>
<!-- ... more levels inbetween ...-->
<StackPanel Grid.Row="4" Margin="5">
<Button Content="A Button outside the usercontrol: it gets the 'current' style"></Button>
<!-- the button inside the usercontrol get's the window button style !!! -->
<UserControlLib:UserControlNoStyle/>
</StackPanel>
<!-- ... more levels inbetween ...-->
</Window>
The above definitions will result in following visuals:
In case we do apply a style in the library project we have several possibilities:
We do have the known use cases which will not be much of a surprise as long as we do not cross any exe/dll boundaries:
- A
Style
with ax:Key
defined in the DLL and applied to the control in this DLL. - A
Style
with ax:Key
, basedon a style defined in the DLL and applied to the control in this DLL.
It will be no surprise these behave as they do in a regular application: after all, there really isn't any difference except for the type of project into which we work.
Where it does get interesting is when we try to cross exe/dll boundaries.
For example: what if we define our style to be basedon another style but referenced by Type
AND we change the definiton of the types style in the client project:
<!-- the UserControl inside a library project-->
<UserControl>
<UserControl.Resources>
<Style x:Key="buttonStyle" TargetType="Button">
<Setter Property="Background" Value="Red" />
</Style>
<Style x:Key="basedOnStyle1" TargetType="Button"
BasedOn="{StaticResource buttonStyle}">
<Setter Property="Foreground" Value="Blue" />
</Style>
<!--Allthough you specify button is the base-type, it will not use the button style
of the application you use this control in-->
<Style x:Key="basedOnStyle2" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Foreground" Value="Green" />
</Style>
</UserControl.Resources>
<Grid>
<StackPanel Margin="10">
<Button Content="A Custom Control with an Unstyled Button"/>
<Button Content="A Custom Control with Styled Button from the Custom Control" Style="{StaticResource buttonStyle}"/>
<Button Content="A Custom Control with Styled Button from the Custom Control BasedOn by x:Key" Style="{StaticResource basedOnStyle1}"/>
<Button Content="A Custom Control with Styled Button from the Custom Control BasedOn by Type" Style="{StaticResource basedOnStyle2}"/>
</StackPanel>
</Grid>
</UserControl>
<!-- A window inside our executable project -->
<Window x:Class="VisualCustomization.MainWindow">
<Window.Resources>
<!-- make all buttons yellow in this window -->
<Style TargetType="Button">
<Setter Property="Background" Value="Yellow" />
</Style>
</Window.Resources>
<!-- ... more levels inbetween ...-->
<StackPanel Grid.Row="4" Margin="5">
<Button Content="A Button outside the usercontrol: it gets the 'current'
style"></Button>
<!-- our windows defined button style does not get propagated to basedon styles inside the customcontrol -->
<UserControlLib:UserControlWithStyle/>
</StackPanel>
<!-- ... more levels inbetween ...-->
</Window>
Well, possibly to your disappointment, nothing happens: although we changed the type's style in our client, it will not be used by basedon styles in our library project.
The above definitions will result in following visuals:
Also, what happens if we use the same resourcekey in our client as used in the UserControl
?
<!-- the UserControl inside a library project-->
<UserControl>
<UserControl.Resources>
<Style x:Key="styleWithKey" TargetType="Button">
<Setter Property="Foreground" Value="Green" />
</Style>
</UserControl.Resources>
<Grid>
<StackPanel Margin="10">
<Button Content="A Custom Control with Styled Button by using key"
Style="{StaticResource styleWithKey}"/>
</StackPanel>
</Grid>
</UserControl>
<!-- A window inside our executable project -->
<Window x:Class="VisualCustomization.MainWindow">
<Window.Resources>
<!-- make these buttons brown in this window -->
<Style x:Key="styleWithKey" TargetType="Button">
<Setter Property="Foreground" Value="Brown" />
</Style>
</Window.Resources>
<!-- ... more levels inbetween ...-->
<StackPanel Grid.Row="4" Margin="5">
<Button Content="A Button outside the usercontrol: key is same as inside usercontrol"
Style="{StaticResource styleWithKey}"></Button>
<!-- our windows defined button style does not get propagated to same x:Key-ed styles inside the customcontrol -->
<UserControlLib:UserControlWithStyle/>
</StackPanel>
<!-- ... more levels inbetween ...-->
</Window>
Again, nothing happens. The scope of the resourcesection defined in the UserControl
itself hides the definition in our client.
CustomControl
In case we have a CustomControl with no special styling applied, the style of the type of control from which it was derived will be applied. If in the scope of the CustomControl a style is current for the base type of the control it will NOT be applied for the same reason a style of a base class control does not get applied to any parent type control. Of course, if we use a key, then the style will be applied:
public class CustomUnstyledButton : Button
{
}
<!-- this style will only be applied to buttons referencing this style through the key -->
<Style x:Key="myStyle" TargetType="Button">
<Setter Property="Background" Value="Orange" />
</Style>
<!-- following looks like an ordinary button, notice how it does NOT get the changed default button style -->
<CustomControlLib:CustomUnstyledButton Content="Custom unstyled button"></CustomControlLib:CustomUnstyledButton>
<!-- following looks like an orange button -->
<CustomControlLib:CustomUnstyledButton Content="Custom unstyled button with style"
Style="{StaticResource myStyle}"></CustomControlLib:CustomUnstyledButton>
The above definitions will result in following visuals:
A similar thing happens when we have a CustomControl with a style:
public class CustomStyledButton : Button
{
static CustomStyledButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomStyledButton), new FrameworkPropertyMetadata(typeof(CustomStyledButton)));
}
}
<!-- inside the library project we set the style for our custom control -->
<Style TargetType="{x:Type local:CustomStyledButton}"
BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="Blue" />
<Setter Property="BorderBrush" Value="Yellow" />
</Style>
<!-- inside the client -->
<!-- this style will only be applied to buttons referencing this style through the key -->
<Style x:Key="myStyle" TargetType="Button">
<Setter Property="Background" Value="Orange" />
</Style>
<CustomControlLib:CustomStyledButton Content="Custom styled button" />
<!-- following looks like an orange button with a yellow border-->
<CustomControlLib:CustomStyledButton Content="Custom styled button with button style"
Style="{StaticResource myStyle}"></CustomControlLib:CustomStyledButton>
Mind however how for styling with a key, the defaultstyle defined in the library project is still applied!
The above definitions will result in following visuals:
Also note how the CustomStyledButton
gets the Style
defined in the Generic.xaml
file by using the DefaultStyleKeyProperty.OverrideMetadata
method in its static constructor.
The application of this method tells WPF to search for this file in the Themes folder. Mind that WPF does not search for this Generic.xaml
file by default. You might be tempted to think that any style defined in this file will be globally available in your library project. This is not true! There is only one way to make the Style
s defined in this file available to other controls in the same library and that i by including it through a MergedDictionaries
entry:
Lets say following exists in a file Generic.xaml in the Themes folder:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfVisualCustomization.CustomControlLib">
<Style x:Key="MyGenericCustomButton" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Yellow" />
</Style>
</ResourceDictionary>
Then following will work:
<UserControl x:Class="WpfVisualCustomization.CustomControlLib.UserControlThereIsNoFreeLunchV2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/WpfVisualCustomization.CustomControlLib;component/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Button Content="Click me for a free lunch" Style="{StaticResource MyGenericCustomButton}" HorizontalAlignment="Left" Margin="20,20,0,0" VerticalAlignment="Top"/>
</Grid>
</UserControl>
Following will NOT work:
<UserControl x:Class="WpfVisualCustomization.CustomControlLib.UserControlThereIsNoFreeLunchV1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<!-- Following will throw an exception at runtime
The Style with key MyGenericCustomButton cannot be found
-->
<!-- <Button Content="Click me for a free lunch" Style="{StaticResource MyGenericCustomButton}" HorizontalAlignment="Left" Margin="20,20,0,0" VerticalAlignment="Top"/> -->
</Grid>
</UserControl>
A somewhat special case: the ItemsControl
Concepts
Some controls do have multiple styling properties. An example of these are the ItemsControl
derived controls: they have on top of their regular Style
also a ItemContainerStyle
property. But don't be fooled here: this style assigned to this property is aplied to the ItemsContainer and ONLY to the ItemsContainer and NOT to any of it's created children.
How to do it?
Thus, following will NOT set the background color of any of the Items created !
<Style x:Key="myItemsStyle">
<Setter Property="Control.Background" Value="Red" />
</Style>
<!-- this will not get applied to the textboxes -->
<Style x:Key="myTextBlockStyle">
<Setter Property="TextBlock.Background" Value="Red" />
</Style>
<!-- There is no background coloring here: the itemstyle is applied to the
container which in this case is a ContentPresenter. This class does not
have a Background property -->
<ItemsControl Grid.Row="0" ItemContainerStyle="{StaticResource myItemsStyle}" >
<ItemsControl.ItemsSource>
<Int32Collection >1,2,3,4,5</Int32Collection>
</ItemsControl.ItemsSource>
</ItemsControl>
<!-- To display numbers, the ItemsControl uses TextBlocks but
still no styling applied to the controls displaying the values:
the style is applied to the ContentPresenters which contain these TextBlocks-->
<ItemsControl Grid.Row="2" ItemContainerStyle="{StaticResource myTextBlockStyle}" >
<ItemsControl.ItemsSource>
<Int32Collection>10,20,30,40,50</Int32Collection>
</ItemsControl.ItemsSource>
</ItemsControl>
The above definitions will result in following visuals:
If you uncomment theForeground
setter, you might be surprised that the color of the text in the firstItemsControl
does get set while you might expect it not to be set neither! What is going on here? TheContentPresenter
control does have aForeground
attached property fromTextElement
, thus we are able to set the foregound color for theContentPresenter
. But how does this end up on our numbers which are shown in aTextBlock
? This is done by the Property Value Inheritance mechanism. It basically means that certain values on a controls properties are also used by the child controls. In this case, theForeground
property of theContentPresenter
is also used on theTextBlock
used to show the numbers.
However, in the following case there is background coloring ! This is however due to the way the ItemsControl
creates its items. If the ItemsSource
is a collection of Control
s, then no itemcontainers are created, but the controls themselves are used directly. As a consequence, the ItemContainerStyle
is directly applied to the controls, in this case the Button
s: hence red buttons.
<!-- There is background coloring here: the itemstyle is applied to the
container which in this case is the Buttons themselves. If the itemssource
contains Controls, then no ContentPresenter controls are created, but the
controls themselves are used directly-->
<ItemsControl Grid.Row="1" ItemContainerStyle="{StaticResource myItemsStyle}" >
<Button Content="A button" />
<Button Content="A button" />
<Button Content="A button" />
</ItemsControl>
The above definitions will result in following visuals:
Following however does succeed:
<Style x:Key="myTextBlockStyle2">
<Style.Resources>
<Style TargetType="TextBlock">
<Setter Property="Background" Value="Red" />
</Style>
</Style.Resources>
</Style>
<!-- aha: if we use nested styles we do succeed!!! -->
<ItemsControl Grid.Row="3" ItemContainerStyle="{StaticResource myTextBlockStyle2}" >
<ItemsControl.ItemsSource>
<Int32Collection>10,20,30,40,50</Int32Collection>
</ItemsControl.ItemsSource>
</ItemsControl>
It probably will not be a surprise: this is the same reason we could change a Label
s background used as the Content
of a Button
using a similar syntax: the Style
defined in the Resources
section is "merged" into the resources of the ItemContainer
and from thereon normal scope rules apply, thus making it visible to any TextBlock
s created under the hood by the ItemsControl
.
The above definitions will result in following visuals:
Some ItemControl
derived controls have a custom ItemContainer
. An example is the ListBox
which creates ListBoxItem
s. Using following we can thus set it's Background
property. Notice however this succeeds for the Background
property because the ListBoxItem
has one; but will fail for any TextBlock
properties the ListBoxItem
does not have.
<Style x:Key="myListBoxItemsStyle">
<Setter Property="Control.Background" Value="Red" />
</Style>
<!-- A ListBow wraps its data inside a ListBoxItem which does have a background property -->
<ListBox Grid.Row="4" ItemContainerStyle="{StaticResource myListBoxItemsStyle}">
<ItemsControl.ItemsSource>
<Int32Collection>100,200,300,400,500</Int32Collection>
</ItemsControl.ItemsSource>
</ListBox>
The above definitions will result in following visuals:
Setting eventhandlers: the EventSetter
Concepts
Allthough the articles intend is more about visual customization, following immediately comes to mind: if we can set properties so easily it would be nice if we could also set eventhandlers this easily. And do we set eventhandlers, or do we attach eventhandlers? In other words: is the addeventhandler invoked or is some special mechanism doing its magical stuff here?
It turns out we actually do attach eventhandlers preserving any allready existing and making it possible to attach multiple handlers.
How to do it?
The most basic case is when you have a Button on a Window and want to attach an eventhandler defined in the code-behind of the Window: you simply use the EventSetter
.
<!-- we can also set eventhandlers through styles -->
<Style x:Key="myStyleWithEvent" TargetType="Button">
<EventSetter Event="Click" Handler="BrownButton_Click" />
</Style>
<Button Content="Button (with action from style)"
Style="{StaticResource myStyleWithEvent}" />
<Button Content="Button (with action from style but overridden)"
Style="{StaticResource myStyleWithEvent}" Click="Button_Click"/>
Even if you define the Style
in a container in the Window
, the eventhandler is still defined in the code-behind of the Window
<StackPanel.Resources>
<Style x:Key="myStyleWithEventInContainer" TargetType="Button">
<EventSetter Event="Click" Handler="ButtonInContainer_Click"/>
</Style>
</StackPanel.Resources>
<Button Content="Button with event from StackPanel"
Style="{StaticResource myStyleWithEventInContainer}"></Button>
You can thus also attach eventhandlers from Style
s defined in the Resources
section of the Application
, provided you defined the eventhandler in the code-behind. The real question here is of course if that is a good idea?
The same scoping rules apply is for regular properties. Following will thus also set the eventhandler inside the UserControl
<StackPanel.Resources>
<Style TargetType="Button">
<EventSetter Event="Click" Handler="WowButton_Click" />
</Style>
</StackPanel.Resources>
<Button Content="A Button outside the usercontrol: itseventhandler set by style"></Button>
<!-- the button inside the usercontrol get's the window button style !!! -->
<UserControlLib:UserControlNoStyle/>
Conclusion
A lot allready has been written about styling in WPF and I hold no illusion as to have written something new here. I wrote this article partly for myself as explaining something to someone else always helps me to understand the subject myself. Also, allthough a lot has been written, I wanted to add a twist: supply examples on what works, but also on what doesn't. Hence some xml in comments which wouldn't compile. But I invite you to uncomment thise sectioins to see what happens.
Anyway, I hope you learned something by reading this article, I know I did explaining the concepts.
Version history
- Version 1.0: Initial version
- Version 1.1: Following changes:
- Added TOC
- Added Version History section
- Expanded section on ComponentResourceKey after comment from Pete O'Hanlon
- Version 2.0: Following changes:
- Added reference to Part 2 of the series
- Added section on defining global styles in a library project
- Added section on why in a style for an itemscontrol, a foreground property setter gets used
- Version 2.1: Following changes:
- Added reference to other articles the series