Click here to Skip to main content
14,978,609 members
Articles / Desktop Programming / WPF
Article
Posted 10 Feb 2015

Stats

36K views
419 downloads
70 bookmarked

WPF Custom Visualization Part 1 of some: Styling

Rate me:
Please Sign up or sign in to vote.
4.71/5 (28 votes)
24 Aug 2016CPOL16 min read
(Yet Another) Investigation of WPF styling

Content

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:

  1. Styling: with Styling you can set various properties on the controls.
  2. 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:

  1. WPF Custom Visualization Part 1 of N: Styling (this article)
  2. WPF Custom Visualization Part 2 of N: Styling with Triggers
  3. WPF Custom Visualization Intermezzo: Resources
  4. WPF Custom Visualization Intermezzo 2: Binding
  5. WPF Custom Visualization Part 3 of N: Templating
  6. 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:

  1. When is the style composed.
  2. How to reference the style we want to apply.
  3. 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:

  1. Define the style directly on the button itself.
  2. 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.
  3. 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
  4. .

 

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 Buttons in the container in which the Style is defined to Yellow.

XML
<!-- 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 Buttons, we specify a key in the style and reference this key in the button

XML
<!-- 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:

XML
<!-- 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:

Image 1

If we want a style to be applicable to multiple control types, we have two options:

  1. Specify a TargetType with the class name of a common base class on which the property is defined
  2. Specify the name of a common base class in the property-setter

Following is an example of applying the TargetType attribute

XML
<!-- 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:

Image 2

Following is an example of specifying the type in the Setter definition

XML
<!-- 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:

XML
<!-- 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:

XML
<!-- 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 Setters in a single Style

XML
<!-- 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:

Image 3

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:

XML
<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:

Image 4

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:

  1. Application
  2. Window
  3. (Parent) Control
  4. 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:

XML
<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:

Image 5

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:

Image 6

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

XML
<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:

Image 7

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:

XML
<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:

XML
<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:

Image 8

The first example will probably be not much of a surprise: the labels receive their styling from the style defined in the Windows 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:

  1. Applying a style defined in a library project onto controls in our project.
  2. 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:

  1. The component/DLL in which the Style is defined: this is done by providing a Type of a class inside the component.
  2. 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:

XML
<!-- 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:

XML
<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:

XML
<!-- 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:

Image 9

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:

XML
<!-- 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:

Image 10

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:

XML
<!-- 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:

Image 11

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 a x:Key defined in the DLL and applied to the control in this DLL.
  • A Style with a x: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:

XML
<!-- 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:

Image 12

Also, what happens if we use the same resourcekey in our client as used in the UserControl?

XML
<!-- 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:

XML
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:

Image 13

A similar thing happens when we have a CustomControl with a style:

XML
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:

Image 14

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 Styles 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:

XML
<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:

XML
<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:

XML
<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 !

XML
<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:

Image 15

If you uncomment the Foreground setter, you might be surprised that the color of the text in the first ItemsControl does get set while you might expect it not to be set neither! What is going on here? The ContentPresenter control does have a Foreground attached property from TextElement, thus we are able to set the foregound color for the ContentPresenter. But how does this end up on our numbers which are shown in a TextBlock? 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, the Foreground property of the ContentPresenter is also used on the TextBlock 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 Controls, 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 Buttons: hence red buttons.

XML
<!-- 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:

Image 16

Following however does succeed:

XML
<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 Labels 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 TextBlocks created under the hood by the ItemsControl.

The above definitions will result in following visuals:

Image 17

Some ItemControl derived controls have a custom ItemContainer. An example is the ListBox which creates ListBoxItems. 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.

XML
<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:

Image 18

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.

Java
<!-- 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

Java
<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 Styles 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

XML
<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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Serge Desmedt
Software Developer (Senior)
Belgium Belgium
No Biography provided

Comments and Discussions

 
QuestionThank you Pin
phil.o29-Sep-15 2:58
mvephil.o29-Sep-15 2:58 
AnswerRe: Thank you Pin
Serge Desmedt29-Sep-15 7:36
professionalSerge Desmedt29-Sep-15 7:36 
QuestionIt's a good start Pin
Pete O'Hanlon11-Feb-15 2:59
mvePete O'Hanlon11-Feb-15 2:59 
AnswerRe: It's a good start Pin
Serge Desmedt11-Feb-15 6:24
professionalSerge Desmedt11-Feb-15 6:24 
GeneralRe: It's a good start Pin
Pete O'Hanlon11-Feb-15 8:29
mvePete O'Hanlon11-Feb-15 8:29 
AnswerDynamic-/Static-Resource Pin
Mr.PoorEnglish28-Sep-15 23:14
MemberMr.PoorEnglish28-Sep-15 23:14 
GeneralRe: Dynamic-/Static-Resource Pin
Pete O'Hanlon29-Sep-15 0:01
mvePete O'Hanlon29-Sep-15 0:01 
GeneralRe: Dynamic-/Static-Resource Pin
Mr.PoorEnglish29-Sep-15 1:05
MemberMr.PoorEnglish29-Sep-15 1:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.