WPF A Beginners Guide part 6 - It works!

fishglugΛογισμικό & κατασκευή λογ/κού

13 Δεκ 2013 (πριν από 3 χρόνια και 7 μήνες)

83 εμφανίσεις

WPF A Beginners Guide part 6

Sacha Barber

Preface And Thanks

I am a .NET programmer, but a busy one, I do VB .NET and C#, ASP .NET / Winforms /
WPF / WCF Flash Silverlight the lot. Basically I keep my toe in. But when I started
writing this article series
I naturally chose my favourite language (which happens to be
C#). I since got an email from an individual who requested that I publish this series with
source code in VB .NET and C#. I simply stated I didn't have time. So this individual
(Robert Ranck) vol
unteered to help out, and do the translation to VB .NET, based on my
orginal C# projects

So for that and the subsequent VB .NET projects that you will find here I ask you to
thank Robert Ranck. Cheers Robert, your contributions will surely make this series

more
open to all .NET developers.

And another thanks also goes out
Karl Shifflett

(AKA the blog/article machine, also
known as the Molenator
) for answering my dumb VB .NET questions. And I'd also like
to mention that Karl has just started a more advanced series of WPF articles (which at
present will be in VB.NET, but will hopefully appear in C# as well). Karls new series
will be excellent and
I urge you all to encourage Karl on this series. Its not easy
obligating ones self to write an entire series in one language let alone 2. Karls 1st article
is located
right here
, and he has also published
part2

now, go have a look for yourself.
Personally I love them.

Introduction

This article is the 6th in my series of beginners articles for WPF. In

this article we will
discuss Styles/Templates. The proposed schedule for this series has been as follows:



Layout




XAML vs Code / Markup Extensions And Resources




Commands And Events




Dependency Propertie
s




DataBinding (previous article)




Styles/Templates (This article)


In this article I'm aiming to cover, is a brief introduction into the following:



What are Styles




Example Styles Within The Demo App




What are Templates




Example Templates Within The Demo App




Lookless contr
ols


I will NOT be covering the usage on animations within Styles/Templates. Josh Smith has
an excellent usage of animations within a Style, within
this article
. There is a
lso a good
MSDN article about this right
here

should you want to do that

What Is This Article All About

If you are reading this and have ever tried to create an owner drawn
tab/customised a
button (you know override the OnPaint() and OnPaintBackGround()) then you will
probably know that creating custom controls that look different to the standard controls is
do
-
able but just not that much fun.

I've done a fair ammount of WinF
orms custom/user controls, and I have no love for all
that method overriding and mouse handling, and have often thought that there must be a
better way.

WPF addressed all of this, by creating 2 UI design pillars, one called STYLES and one
called TEMPLATING
. This article covers both of these within a WPF environment.

What are Styles

Overview

Simply put
Style
s allow a WPF developer to maintain a common list of property values
within a convenient place to store all these property values. It is somewhat similar

to how
CSS works within web based development. Typically
Style
s will be maintained within a
Resource section or a seperate Resource dictionary. It is also by using Styles that WPF is
able to cater for theme aware controls. There is an excellent post
Chazs blog

about how
to do this.

In this article I dont want to get to bogged down in how to create themes, I just want to
cover
the basics, so I'm going to show you a few things that are available within a
Style
,
but after that, I shall be concentrating on the main areas of
Style
s that you will use most
often.

For a
Style

the following properties are available

Name

Description

Bas
edOn

Gets or sets a defined style that is the basis of the current style

Dispatcher

Gets the Dispatcher this DispatcherObject is associated with. (Inherited from
DispatcherObject.)

IsSealed

Gets a value that indicates whether the style is read
-
only and c
annot be
changed.

Resources

Gets or sets the collection of resources that can be used within the scope of
this style.

TargetType

Gets or sets the type for which this style is intended.

Setters

Gets a collection of Setter and EventSetter objects.

Trigge
rs

Gets a collection of TriggerBase objects that apply property values based on
specified conditions.

Out of these by far the most import properties are



BasedOn



TargetType



Setters



Triggers

So I think it's worth have a quick look into each of these bi
ts on syntax.

BasedOn

This is like inheritence. Where one
Style

inherits common properties from another
Style
. Each style only supports one BasedOn value. Here is a small example

Collapse

<
Style

x:Key
="Style1">

...

</
Style
>


<
Style

x:Key
="Style2"

BasedOn
="{StaticResource Style1}">

...

</
Style
>

TargetType

The Target type property is used to limit which controls may use a particular style. For
example if we had a
Style

with
a
TargetType
property set to
Button
this
Style

could
not be used against a
TextBox
type control.

Setting a valid
TargetType
property is as simple as the following:

Collaps
e

<
Style

TargetType
="{x:Type Button}">

....

</
Style
>

Setters

Setter
s are simply things really. They simply set a event or a property to some value. In
the case of setting an event, they wire up an event. In the case of setting a property they
set a propert
y to a value.

EventSetter
s for events, would be something like this, where a
Style
d
Button
s
Click

event is getting wired up.

Collapse

<
Style

TargetType
="{x:Type Button}">


<
EventSetter

Event
="Click"

Handler
="b1SetColor"/>

</
Style
>

However, much more typically
Setter
s are simply used to set a property to a value.
Maybe something like this:

Collapse

<
Style

TargetType
="{x:Type Button}">


<
Setter

Property
="BackGround"

Value
="Yellow"/>

</
Style
>

Property Element Syntax

There are also occassions where you dont want the value to be a single value, but rather a
complex chunk of XAML, comprisin
g many elements. In order to do this, XAML allows
developers to use the Property Element syntax. The most likely place you will see this in
Style
s, is within a
Template Setter
. Something like the following:

Collapse

<!
--

Tab Item Style
--
>

<
Style

x:Key
="TabItemStyle1"

TargetType
="{x:Type TabItem}">

<
Setter

Property
="FocusVisualStyle"

Value
="{StaticResource
TabItemFocusVisual}"/>

<
Setter

Property
="Foreground"

Value
="Black"/
>

<
Setter

Property
="Padding"

Value
="6,1,6,1"/>

<
Setter

Property
="BorderBrush"

Value
="{StaticResource
TabControlNormalBorderBrush}"/>

<
Setter

Property
="Background"

Value
="{StaticResource
ButtonNormalBackground}"/>

<
Setter

Property
="HorizontalContentAlignmen
t"

Value
="Stretch"/>

<
Setter

Property
="VerticalContentAlignment"

Value
="Stretch"/>

<
Setter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

TargetType
="{x:Type TabItem}">


<
Grid

SnapsToDevicePixels
="true"

Margin
="0,5,0,0">



.....


.....


.....


</
Grid
>


</
ControlTemplate
>


</
Setter.Value
>

</
Setter
>

</
Style
>

The important part here is the part where the
Setter

is split over several lines, by using
the property value
syntax.

Collapse

<
Setter

Property
="Template">


<
Setter.Value
>


.....


.....


.....


</
Setter.Value
>

</
Setter
>

Triggers

The WPF styling and templa
ting model enables you to specify
Trigger
s within your
Style
. Essentially,
Trigger
s are objects that enable you to apply changes when certain
conditions (such as when a certain property value becomes true, or when an event occurs)
are satisfied.

The follow
ing example shows a named
Style

available to Button controls. The Style
defines a
Trigger

element that changes the Foreground property of a
Button

when the
IsPressed

property is true.

Collapse

<
Style

x:Key
="Triggers"

TargetType
="Button">


<
Style.Triggers
>


<
Trigger

Property
="IsPressed"

Value
="true">


<
Setter

Property

= "Foreground"

Value
="Green"/>


</
Trigger
>


</
Style.Triggers
>

</
Style
>

There are
some more types of
Trigger
s that may be used in
Style
s

DataTriggers

Represents a
Trigger

that applies property values or performs actions when the bound
data meets a specified condition.

The
DataTrigger

is specified such that if the State of the Place dat
a item is "WA" then
the foreground of the corresponding ListBoxItem is set to Red.

Collapse

<
Style

TargetType
="ListBoxItem">


<
Style.Triggers
>


<
DataTrigger

Bindin
g
="{Binding Path=State}"

Value
="WA">


<
Setter

Property
="Foreground"

Value
="Red"

/>


</
DataTrigger
>



</
Style.Triggers
>

</
Style
>

There is also a special type of
Trigger

which uses more than 1 value for its consitional
test. This is known a
s a
Multitrigger
. And all this does is use several conditions within
a single
MultiDataTrigger
. Here is an example

Collapse


<
Style

TargetType
="ListBoxItem">


<
Style.T
riggers
>


<
MultiDataTrigger
>


<
MultiDataTrigger.Conditions
>


<
Condition

Binding
="{Binding Path=Name}"

Value
="Portland"

/>


<
Condition

Binding
="{Binding Path=State}"

Value
="OR"

/>


</
MultiDataTrigger.Conditions
>


<
Setter

Property
="Background"

Value
="Cyan"

/>


</
MultiDataTrigger
>


</
Style.Triggers
>


</
Style
>

In this example the bound object must have a Name="Portland" and a State="OR", then
the foreground of the corresponding ListBoxItem is set to Red.

Even
tTriggers

Are special
Trigger
s which represents a
Trigger

that applies a set of actions in response
to an event. These
Eventrigger
s are strane in that they ONLY allow animations to be
triggered. They do not allow normal properties to be set based, that is
what the normal
Trigger
s are for. And example
Eventrigger

may be something like the following

Collapse

<
EventTrigger

RoutedEvent
="Mouse.MouseEnter">


<
EventTrigger.Action
s
>


<
BeginStoryboard
>


<
Storyboard
>


<
DoubleAnimation


Duration
="0:0:0.2"


Storyboard.TargetProperty
="MaxHeight"


To
="90"

/>


</
Storyboard
>


</
BeginStoryboard
>


</
EventTrigger.Actions
>

</
EventTrigger
>

<
Ev
entTrigger

RoutedEvent
="Mouse.MouseLeave">


<
EventTrigger.Actions
>


<
BeginStoryboard
>


<
Storyboard
>


<
DoubleAnimation


Duration
="0:0:1"


Storyboard.TargetProperty
="MaxHeight"

/>


</
Storyboard
>


</
BeginStoryboard
>


</
EventTrigger.Actions
>

</
EventTrigger
>

Example Styles Within The Demo App

The attached demo app uses a fair few
Style
s. Typically these are mixed with the usage
of
Template
s, so its a bit tricky to isolate a single example.

Here is an example of a
Styl
e

that has been setup and targets
TabItem
s, this is a full
Style

so you can see that there is also
Template
s in here as well as the things we just
discussed (you know
Setter
s /
TargetType
s and property element syntax...no triggers in
this one)

Collapse

<!
--

Tab Item Style
--
>

<
Style

x:Key
="TabItemStyle1"

TargetType
="{x:Type TabItem}">


<
Setter

Property
="FocusVisualStyle"

Value
="{StaticResource
TabItemFocusVisual}"/>


<
Setter

Property
="Foreground"

Value
="Black"/>


<
Setter

Property
="Padding"

Value
="6,1,6,1"/>


<
Setter

Property
="BorderBrush"

Value
="{StaticResource
TabControlNormalBorderBrush}"/>


<
Setter

Property
="Background"

Value
="{StaticResource
ButtonNormalBa
ckground}"/>


<
Setter

Property
="HorizontalContentAlignment"

Value
="Stretch"/>


<
Setter

Property
="VerticalContentAlignment"

Value
="Stretch"/>


<
Setter

Property
="HeaderTemplate">


<
Setter.Value
>


<
DataTemplate
>


<
Sta
ckPanel

Orientation
="Horizontal">


<
TextBlock

Text
="{Binding}"/>


<
Button

x:Name
="btnClose"

Margin
="10,3,3,3"



Template
="{StaticResource
closeButtonTemplate}"




Background
="{StaticResource
buttonNormalBrush}"



IsEnabled
="True"/>


</
StackPanel
>


</
DataTemplate
>


</
Setter.Value
>


</
Setter
>


<
Setter

Property
="Template">


<
Setter.V
alue
>


<
ControlTemplate

TargetType
="{x:Type TabItem}">


<
Grid

SnapsToDevicePixels
="true"

Margin
="0,5,0,0">


<
Border

x:Name
="Bd"

Background
="{TemplateBinding
Background}"



BorderBrush
=
"{TemplateBinding BorderBrush}"



BorderThickness
="1,1,1,0"

CornerRadius
="10,10,0,0"



Padding
="{TemplateBinding Padding}">


<
ContentPresenter

SnapsToDevicePixels
=



"{TemplateBinding SnapsToDevicePixels}"



HorizontalAlignment
="{Binding


Path=HorizontalContentAlignment,


RelativeSource={RelativeSource



AncestorType={x:Type ItemsControl}}}"



x:Name
="Content"

VerticalAlignment
="


{Binding Path=VerticalContentAlignment,


RelativeSource={RelativeSource



AncestorType={x:Type ItemsControl}}}"



ContentSource
="Header"

RecognizesAccessKey
="True"/>


</
Border
>


</
Grid
>


<
ControlTemplate.Triggers
>


<
Trigger

Property
="IsMouseOver"

Value
="true">


<
Setter

Property
="Background"

TargetName
="Bd"



Value
="{StaticResource
TabItemHotBackground}"/>


</
Trigger
>


<
Trigger

Property
="I
sSelected"

Value
="true">


<
Setter

Property
="Panel.ZIndex"

Value
="1"/>


<
Setter

Property
="Background"

TargetName
="Bd"



Value
="{StaticResource
TabItemSelectedBackground}"/>



</
Trigger
>


<
MultiTrigger
>


<
MultiTrigger.Conditions
>


<
Condition

Property
="IsSelected"

Value
="false"/>


<
Condition

Property
="IsMouseOver"

Value
="true"
/>


</
MultiTrigger.Conditions
>


<
Setter

Property
="BorderBrush"

TargetName
="Bd"



Value
="{StaticResource
TabItemHotBorderBrush}"/>


</
MultiTrigger
>



<
Trigger

Property
="TabStripPlacement"

Value
="Bottom">


<
Setter

Property
="BorderThickness"

TargetName
="Bd"

Value
="1,0,1,1"/>


</
Trigger
>


<
Trigger

Property
="TabStripPlacement"

Value
="Left">


<
Setter

Property
="BorderThickness"

TargetName
="Bd"

Value
="1,1,0,1"/>


</
Trigger
>


<
Trigger

Property
="TabStripPlacement"

Value
="Right">


<
Setter

Property
="BorderThickness"

TargetName
="Bd"

Value
="0,1,1,1"/>


</
Trigger
>


<
MultiTrigger
>


<
MultiTrigger.Conditions
>


<
Condition

Property
="IsSelected"

Value
="true"/>


<
Condition

Property
="TabStripPlacement"

Value
="Top"/>


</
MultiTrigger.Conditions
>


<
Setter

Property
="Margin"

Value
="
-
2,
-
2,
-
2,
-
1"/>


<
Setter

Property
="Margin"

TargetName
="Content"

Value
="0,
0,0,1"/>


</
MultiTrigger
>


<
MultiTrigger
>


<
MultiTrigger.Conditions
>


<
Condition

Property
="IsSelected"

Value
="true"/>


<
Condition

Property
="T
abStripPlacement"

Value
="Bottom"/>


</
MultiTrigger.Conditions
>


<
Setter

Property
="Margin"

Value
="
-
2,
-
1,
-
2,
-
2"/>


<
Setter

Property
="Margin"

TargetName
="Content"

Value
="0,1,0,0"/>



</
MultiTrigger
>


<
MultiTrigger
>


<
MultiTrigger.Conditions
>


<
Condition

Property
="IsSelected"

Value
="true"/>


<
Condition

Property
="TabStripPlacement"

Value
="Left"/>


</
MultiTrigger.Conditions
>


<
Setter

Property
="Margin"

Value
="
-
2,
-
2,
-
1,
-
2"/>


<
Setter

Property
="Margin"

TargetName
="Content"

Value
="0,0,1,0"/>


</
MultiTr
igger
>


<
MultiTrigger
>


<
MultiTrigger.Conditions
>


<
Condition

Property
="IsSelected"

Value
="true"/>


<
Condition

Property
="TabStripPlacement"

Value
="Right"/>



</
MultiTrigger.Conditions
>


<
Setter

Property
="Margin"

Value
="
-
1,
-
2,
-
2,
-
2"/>


<
Setter

Property
="Margin"

TargetName
="Content"

Value
="1,0,0,0"/>


</
MultiTrigger
>



<
Trigger

Property
="IsEnabled"

Value
="false">


<
Setter

Property
="Background"

TargetName
="Bd"



Value
="{StaticResource
TabItemDisabledBackground}"/>


<
Setter

Property
="Borde
rBrush"

TargetName
="Bd"



Value
="{StaticResource
TabItemDisabledBorderBrush}"/>


<
Setter

Property
="Foreground"



Value
="{DynamicResource


{x:St
atic
SystemColors.GrayTextBrushKey}}"/>


</
Trigger
>


</
ControlTemplate.Triggers
>


</
ControlTemplate
>


</
Setter.Value
>


</
Setter
>

</
Style
>

This
Style

and its associated
Template
s (which are not all show
n here) are enough to
change the
TabItem
s from the standard visual representation into ones with round corners
and close buttons like this. The top tabs are the standard un
-
styled tabs, and the other
ones are the ones that have been
Style
d by yours truly.


The demo app also contains a
Style
s and associated
Template
s to create a totally new

ScrollBar

and
ScrollViewer

control representations, which are shown
in the figure
below

ScrollBar

on the left
ScrollViewer

on the right.


In order to do this, there needed to be quite a few
Style
s and associated
Template
s.
Have a look at how much XAML it took. I have removed the guts of the the
Template
s
as I have not discussed how they work yet. I just wanted to show you what it takes to
totally re
-
Style

a standard control. Obviously some are more compliacted than others
. A
Button

control for example is trivial.

Collapse

<!
--

Brushses
--
>

<
LinearGradientBrush

x:Key
="VerticalScrollBarBackground"



EndPoint
="1,0"

StartPoi
nt
="0,0">


<
GradientStop

Color
="#E1E1E1"

Offset
="0"/>


<
GradientStop

Color
="#EDEDED"

Offset
="0.20"/>


<
GradientStop

Color
="#EDEDED"

Offset
="0.80"/>


<
GradientStop

Color
="#E3E3E3"

Offset
="1"/>

</
LinearGradientBrush
>


<
LinearGradientBrush

x:Key
="
HorizontalScrollBarBackground"



EndPoint
="0,1"

StartPoint
="0,0">


<
GradientStop

Color
="#E1E1E1"

Offset
="0"/>


<
GradientStop

Color
="#EDEDED"

Offset
="0.20"/>


<
GradientStop

Color
="#EDEDED"

Offset
="0.80"/>


<
GradientStop

Color
="#E3E3E3"

Offset
="1"/>

</
LinearGradientBrush
>


<
LinearGradientBrush

x:Key
="ListBoxBackgroundBrush"

StartPoint
="0,0"

EndPoint
="1,0.001">


<
GradientBrush.GradientStops
>


<
GradientStopCollection
>


<
GradientStop

Color
="White"

Offset
="0.0"

/>


<
GradientStop

Color
="White"

Offset
="0.6"

/>


<
GradientStop

Color
="#DDDDDD"

Offset
="1.2"/>


</
GradientStopCollection
>


</
GradientBrush.GradientStops
>

</
LinearGradientBrush
>


<
LinearGradientBrush

x:Key
="StandardBrush"

Sta
rtPoint
="0,0"

EndPoint
="0,1">


<
GradientBrush.GradientStops
>


<
GradientStopCollection
>


<
GradientStop

Color
="#FFF"

Offset
="0.0"/>


<
GradientStop

Color
="#CCC"

Offset
="1.0"/>


</
GradientStopCollection
>


</
GradientBru
sh.GradientStops
>

</
LinearGradientBrush
>


<
LinearGradientBrush

x:Key
="PressedBrush"

StartPoint
="0,0"

EndPoint
="0,1">


<
GradientBrush.GradientStops
>


<
GradientStopCollection
>


<
GradientStop

Color
="#BBB"

Offset
="0.0"/>


<
Gradi
entStop

Color
="#EEE"

Offset
="0.1"/>


<
GradientStop

Color
="#EEE"

Offset
="0.9"/>


<
GradientStop

Color
="#FFF"

Offset
="1.0"/>


</
GradientStopCollection
>


</
GradientBrush.GradientStops
>

</
LinearGradientBrush
>


<
SolidColorBrush

x:
Key
="ScrollBarDisabledBackground"

Color
="#F4F4F4"/>

<
SolidColorBrush

x:Key
="StandardBorderBrush"

Color
="#888"

/>

<
SolidColorBrush

x:Key
="StandardBackgroundBrush"

Color
="#FFF"

/>

<
SolidColorBrush

x:Key
="HoverBorderBrush"

Color
="#DDD"

/>

<
SolidColorBrush

x:K
ey
="SelectedBackgroundBrush"

Color
="Gray"

/>

<
SolidColorBrush

x:Key
="SelectedForegroundBrush"

Color
="White"

/>

<
SolidColorBrush

x:Key
="DisabledForegroundBrush"

Color
="#888"

/>

<
SolidColorBrush

x:Key
="NormalBrush"

Color
="#888"

/>

<
SolidColorBrush

x:Key
="Nor
malBorderBrush"

Color
="#888"

/>

<
SolidColorBrush

x:Key
="HorizontalNormalBrush"

Color
="#888"

/>

<
SolidColorBrush

x:Key
="HorizontalNormalBorderBrush"

Color
="#888"

/>

<
SolidColorBrush

x:Key
="GlyphBrush"

Color
="#444"

/>


<!
--

ScrollBarButton Vertical
--
>

<
Styl
e

x:Key
="VerticalScrollBarPageButton"

TargetType
="{x:Type
RepeatButton}">


<
Setter

Property
="OverridesDefaultStyle"

Value
="true"/>


<
Setter

Property
="Background"

Value
="Transparent"/>


<
Setter

Property
="Focusable"

Value
="false"/>


<
Setter

Prope
rty
="IsTabStop"

Value
="false"/>


<
Setter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

TargetType
="{x:Type RepeatButton}">


...


...


</
ControlTemplate
>


</
Setter.Value
>


</
Setter
>

</
Style
>


<!
--

ScrollBarButton Horizontal
--
>

<
Style

x:Key
="HorizontalScrollBarPageButton"

TargetType
="{x:Type
RepeatButton}">


<
Setter

Property
="OverridesDefaultStyle"

Value
="true"/>


<
Setter

Property
="Background"

Value
="Transparent"/>


<
Setter

Property
="Focusable"

Value
="false"/>


<
Setter

Property
="IsTabStop"

Value
="false"/>


<
Setter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

TargetType
="{x:Type RepeatButton}">


...


...



</
ControlTemplate
>


</
Setter.Value
>


</
Setter
>

</
Style
>


<!
--

Scroll Buttons Down
--
>

<
Style

x:Key
="RepeatButtonStyleDown"

TargetType
="{x:Type
RepeatButton}">


<
Setter

Property
="OverridesDefaultStyle"

Value
="true"/>


<
Setter

Proper
ty
="Focusable"

Value
="false"/>


<
Setter

Property
="IsTabStop"

Value
="false"/>


<
Setter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

TargetType
="{x:Type RepeatButton}">


...


...


</
C
ontrolTemplate
>


</
Setter.Value
>


</
Setter
>

</
Style
>


<!
--

Scroll Buttons Up
--
>

<
Style

x:Key
="RepeatButtonStyleUp"

TargetType
="{x:Type RepeatButton}">


<
Setter

Property
="OverridesDefaultStyle"

Value
="true"/>


<
Setter

Property
="Focusable"

Va
lue
="false"/>


<
Setter

Property
="IsTabStop"

Value
="false"/>


<
Setter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

TargetType
="{x:Type RepeatButton}">


...


...


</
ControlTemplate
>


</
Setter.Value
>


</
Setter
>

</
Style
>


<!
--

Scroll Thumb Style
--
>

<
Style

x:Key
="ThumbStyle1"

TargetType
="{x:Type Thumb}">


<
Setter

Property
="OverridesDefaultStyle"

Value
="true"/>


<
Setter

Property
="IsTabStop"

Value
="false"/>


<
Setter

Pro
perty
="Template">


<
Setter.Value
>


<
ControlTemplate

TargetType
="{x:Type Thumb}">


...


...


</
ControlTemplate
>


</
Setter.Value
>


</
Setter
>

</
Style
>


<!
--

ScrollBar Style
--
>

<
Style

x:Ke
y
="ScrollBarStyle1"

TargetType
="{x:Type ScrollBar}">


<
Setter

Property
="Background"



Value
="{StaticResource VerticalScrollBarBackground}"/>


<
Setter

Property
="Stylus.IsPressAndHoldEnabled"

Value
="false"/>


<
Setter

Property
="Stylus.IsFl
icksEnabled"

Value
="false"/>


<
Setter

Property
="Foreground"



Value
="{DynamicResource


{x:Static SystemColors.ControlTextBrushKey}}"/>


<
Setter

Property
="Width"



Value
="{DynamicResource


{x:Static SystemParamete
rs.VerticalScrollBarWidthKey}}"/>


<
Setter

Property
="MinWidth"



Value
="{DynamicResource


{x:Static SystemParameters.VerticalScrollBarWidthKey}}"/>


<
Setter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

T
argetType
="{x:Type ScrollBar}">


...


...


</
ControlTemplate
>


</
Setter.Value
>


</
Setter
>


<
Style.Triggers
>


<
Trigger

Property
="Orientation"

Value
="Horizontal">


<
Setter

Property
="W
idth"

Value
="Auto"/>


<
Setter

Property
="MinWidth"

Value
="0"/>


<
Setter

Property
="Height"

Value
="{DynamicResource


{x:Static
SystemParameters.HorizontalScrollBarHeightKey}}"/>


<
Setter

Property
="MinHeight"




Value
="{DynamicResource


{x:Static
SystemParameters.HorizontalScrollBarHeightKey}}"/>


<
Setter

Property
="Background"



Value
="{StaticResource
HorizontalScrollBarBackground}"/>


<
Se
tter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

TargetType
="{x:Type ScrollBar}">


...


...


</
ControlTemplate
>


</
Setter.Value
>



</
Setter
>


</
Trigger
>


</
Style.Triggers
>

</
Style
>


<!
--

ScrollBar Repeat Button
--
>

<
Style

x:Key
="ScrollBarLineButton"

TargetType
="{x:Type RepeatButton}">


<
Setter

Property
="SnapsToDevicePixels"

Value
="True"/>


<
Setter

Property
="
OverridesDefaultStyle"

Value
="true"/>


<
Setter

Property
="Focusable"

Value
="false"/>


<
Setter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

TargetType
="{x:Type RepeatButton}">


...


...



</
ControlTemplate
>


</
Setter.Value
>


</
Setter
>

</
Style
>


<!
--

ScrollBar Repeat Button
--
>

<
Style

x:Key
="ScrollBarPageButton"

TargetType
="{x:Type RepeatButton}">


<
Setter

Property
="SnapsToDevicePixels"

Value
="True"/>


<
Setter

Property
="OverridesDefaultStyle"

Value
="true"/>


<
Setter

Property
="IsTabStop"

Value
="false"/>


<
Setter

Property
="Focusable"

Value
="false"/>


<
Setter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

TargetType
="{x:Type RepeatButton}
">


...


...


</
ControlTemplate
>


</
Setter.Value
>


</
Setter
>

</
Style
>


<!
--

Scroll Thumb Style
--
>

<
Style

x:Key
="ScrollBarThumb"

TargetType
="{x:Type Thumb}">


<
Setter

Property
="SnapsToDevicePixels"

Val
ue
="True"/>


<
Setter

Property
="OverridesDefaultStyle"

Value
="true"/>


<
Setter

Property
="IsTabStop"

Value
="false"/>


<
Setter

Property
="Focusable"

Value
="false"/>


<
Setter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

T
argetType
="{x:Type Thumb}">


...


...


</
ControlTemplate
>


</
Setter.Value
>


</
Setter
>

</
Style
>


<!
--

Vertical ScrollBar Template
--
>

<
ControlTemplate

x:Key
="VerticalScrollBar"

TargetType
="{x:Type
ScrollB
ar}">


...


...

</
ControlTemplate
>


<!
--

ScrollBar Style
--
>

<
Style

x:Key
="{x:Type ScrollBar}"

TargetType
="{x:Type ScrollBar}">


<
Setter

Property
="SnapsToDevicePixels"

Value
="True"/>


<
Setter

Property
="OverridesDefaultS
tyle"

Value
="true"/>


<
Style.Triggers
>


<
Trigger

Property
="Orientation"

Value
="Horizontal">


<
Setter

Property
="Width"

Value
="Auto"/>


<
Setter

Property
="Height"

Value
="18"

/>


<
Setter

Property
="Template"


Va
lue
="{StaticResource HorizontalScrollBar}"

/>


</
Trigger
>


<
Trigger

Property
="Orientation"

Value
="Vertical">


<
Setter

Property
="Width"

Value
="18"/>


<
Setter

Property
="Height"

Value
="Auto"

/>


<
Setter

Property
=
"Template"


Value
="{StaticResource VerticalScrollBar}"

/>


</
Trigger
>


</
Style.Triggers
>

</
Style
>


<!
--

ScrollViewer Template
--
>

<
ControlTemplate

x:Key
="ScrollViewerControlTemplate"

TargetType
="{x:Type ScrollViewer}">


...


...

</
ControlTemplate
>

Now only a truly truly crazy person (Josh/Karl we know where you are) would attempt
this by hand. I didn't, and wouldn't. I dont like tools generally, but sometimes you need
them. To do this sort of thing manually would drive you NUT
S. The best way is to fire
Expression Blend up and use that to modify your
Template
s. You still need to know your
XAML but Expression Blend helps out in this area for sure.


There is a fair ammount of markup to do this, for sure. But the principle is the same
throughout. Within a
Style

there is just
Style Setter
s and
Trigger
s, which we have
talked about, so these should be clearer now.

The Demo App Organization

N
ow I just quickly want to talk about the demo app organization for a minute before we
go on to
Template
s.

The demo application is organized as follows:

VariousControlTemplatesWindow.xaml

contains examples on how to re
-
style Button/Tabs/Scrollbars

Here the
last screen shot you havent seen


HierarchicalDataTemplateWindow.xaml

Contains examples on how to re
-
style Hierarchical data, here is a screen shot


DemoLauncherWindow.xaml

Contains example on styling item based data


Beatriz Costa Planet ListBox/PlanetsListBoxWindow.xaml

Contains examples of a real neat use of
Style
s and
Template
s. This is used by the
permission of Beatriz Costa (Microsoft). I could have done this myself, but Bea did such
a bang up job, and it does

such a great job of illustrating the power of
Style
s and
Template
s, I just had to use it here also. So thanks for the permission Bea, I owe you
one.


Dont wor
ry, I am going to talk about each of these parts in more detail as we come on to
Template
s. I just wanted you to know what the demo app actually does.

What are Templates

Every control has a standard look and feel....can you guess what this is, yes thats ri
ght its
a
Template
. But what does it look like. Well in Expression blend you get to see this
when you edit a controls
Template
, as shown in the screen shot below where I am editing
a scrollviewer control.


Now if you look at this and go back and have a look at the large section of XAML code I
included, it may become a little clearer. We can see that a ScrollViewer control is
actually made up of several elements
. This forms its VisualTree. Now what we have to
do to change this is either change parts of this tree, or swap it out entirely. Believe it or
not we can swap out pretty much all of what a control was supposed to look like as long
as a few rules are adhere
d to, but more on this later. I just wanted you to be able to see
why all the XAML that is required to create the
Style

or
Template

for a control is
required.

I did a reasonably thorough (we can ALWAYS do better) talk about the ScrollViewer
control on my b
log some time ago, you can have a look at that right
here
, also of note is
the
Standard ControlTemplate E
xamples

MSDN link where you can see what each of the
default WPF controls looks like.

Overview

As I just mentioned, we can use a
Template

to change how a control looks. You will
often see
Template
s used within a
Style

but that's ok, we've seen examples of

this, and
it didnt scare us. In fact we say bring on the
Template
s, those
Style
s were easy man!

Its not quite as easy to nail down exactly what will be within a
Template
, as it may
depend on what sort of
Template

we are dealing with, there are a fair few
different
Template
s out there.

I think what I'll do is cover some of the basic sytax and then talk about some of the
different
Template
s you may run into. And then I'll explain the demo apps
Template
s.

Dont worry to much if I mention things like DataTempat
e/HierarchicalDataTemplate and
you have not got a clue. I will be covering this after the basic syntax of
Template
s. Got
to learn to crawl before you can get totally Fecking lost with WPF, pack it up all up,
shave your hair off, and become a monk. At least

they get wine. Right i'm off.

Triggers

Style, ControlTemplate, and DataTemplate all have a Triggers property that can contain a
set of triggers. A trigger sets properties or starts actions such as animation when a
property value changes or when an event i
s raised. We have already seen Triggers within
a
Style
s, but what about
Template
s. Well in
Template
s its nearly the same. There are a
few different types of triggers. Lets see them shall we:

Property Triggers

Are used to set a property to a value when a ce
rtain condition occurs. This example sets a
Border.opacity property to 0.4 when the Button.IsEnabled property is false.

Collapse

<!
--

Simple Button with some Mouse events/
Property Hi
-
Jacking
--
>

<
ControlTemplate

x:Key
="bordereredButtonTemplateWithMouseAndPropHiJacking"

TargetType
="{x:Type Button}">


<
Border

x:Name
="border"

CornerRadius
="3"

Background
="{TemplateBinding Background}"



BorderBrush
="{TemplateBindin
g Foreground}"



BorderThickness
="2"

Width
="auto"

Visibility
="Visible">


....


....


</
Border
>


<
ControlTemplate.Triggers
>


<
Trigger

Property
="IsEnabled"

Value
="false">


<
Setter

TargetName
="border"

Property
=
"Opacity"

Value
="0.4"/>


</
Trigger
>


</
ControlTemplate.Triggers
>

</
ControlTemplate
>




Event Triggers

Work exactly they way they do for
Style
s, which we outlined above.

MultiTriggers, DataTriggers, and MultiDataTriggers

In addition to Trigger a
nd EventTrigger, there are other types of triggers. MultiTrigger
allows you to set property values based on multiple conditions. You use DataTrigger and
MultiDataTrigger when the property of your condition is data
-
bound.

Collapse

<!
--

Listbox DemoListItem Type Template
--
>

<
DataTemplate

x:Key
="demoItemTemplate"

DataType
="x:Type
local:DemoListItem">


<
StackPanel

Orientation
="Horizontal"

Margin
="10">


<
Path

Name
="pa
thSelected"

Fill
="White"

Stretch
="Fill"

Stroke
="White"

Width
="15"



Height
="20"

Data
="M0,0 L 0,15 L 7.5,7.5"



Visibility
="Hidden"/>


<
Border

BorderBrush
="White"

BorderThickness
="4"

Margin
="5">


<
Image

Source
="Images
/DataLogo.png"

Width
="45"

Height
="45"/>


</
Border
>


<
StackPanel

Orientation
="Vertical"

VerticalAlignment
="Center">


<
TextBlock

FontFamily
="Arial Black"

FontSize
="20"



FontWeight
="Bold"


Width
="a
uto"

Height
="auto"


Text
="{Binding Path=DemoName}"

/>


<
TextBlock

FontFamily
="Arial"

FontSize
="10"



FontWeight
="Normal"


Width
="auto"

Height
="auto"


Text
="{Binding Path=
WindowName}"

/>



</
StackPanel
>


</
StackPanel
>


<
DataTemplate.Triggers
>


<
DataTrigger



Binding
="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,


AncestorType={x:Type ListBoxItem}, AncestorLevel=1},
Pa
th=IsSelected}"

Value
="True">


<
Setter

TargetName
="pathSelected"

Property
="Visibility"

Value
="Visible"

/>


</
DataTrigger
>


</
DataTemplate.Triggers
>

</
DataTemplate
>

</
Window.Resources
>




As this is a DataTemplate its a template for
some bound data, so we can use DataTriggers
here. So this DataTrigger checks whether the bound objects IsSelected property is true
and if it is, sets the Visibility of another element within the DataTemplate.

And here is an example of a MultiTrigger, which

basically uses more than 1 property in
the conditional evaluation before the trigger is run.

Collapse

<
ControlTemplate

TargetType
="{x:Type TabItem}">


<
Grid

SnapsToDev
icePixels
="true"

Margin
="0,5,0,0">


<
Border

x:Name
="Bd"

Background
="{TemplateBinding Background}"



BorderBrush
="{TemplateBinding BorderBrush}"

BorderThickness
="1,1,1,0"



CornerRadius
="10,10,0,0"

Padding
="{TemplateBind
ing
Padding}">


<
ContentPresenter

SnapsToDevicePixels
="{TemplateBinding
SnapsToDevicePixels}"



HorizontalAlignment
="{Binding
Path=HorizontalContentAlignment,


RelativeSource={RelativeSource AncestorType={x:Type
It
emsControl}}}"



x:Name
="Content"

VerticalAlignment
="{Binding
Path=VerticalContentAlignment,


RelativeSource={RelativeSource AncestorType={x:Type
ItemsControl}}}"



ContentSource
="Header"

RecognizesAccessKey
="T
rue"/>


</
Border
>


</
Grid
>


<
ControlTemplate.Triggers
>


<
MultiTrigger
>


<
MultiTrigger.Conditions
>


<
Condition

Property
="IsSelected"

Value
="false"/>


<
Condition

Property
="IsMouseOver"

Value
="t
rue"/>


</
MultiTrigger.Conditions
>


<
Setter

Property
="BorderBrush"

TargetName
="Bd"



Value
="Black"/>


</
MultiTrigger
>


</
ControlTemplate.Triggers
>

</
ControlTemplate
>




In this example the TabItem mu
st have its IsSelected property false, and its IsMouseOver
true before the trigger will run and set the BorderBrush of the element called Bd to a
Black Brush.

TemplateBinding

Now we know about databinding (remember
part5
) we know how to bind things to one
another. Well TemplateBinding is just a different strain of Binding, which links the value
of a property in a control template to be the value of some other exposed property on th
e
templated control.

Have a look the
MSDN page
, but its very simple. What we are trying to do is make sure
our controls are responsive to users demands on the them. For exam
ple if a user sets a
BackGround of a control to Blue and we supply a control
Template

that set the
BackGround to Green, thats not what the user wanted. Surely there is a better way. Well
there is, we simply use the
{TemplateBinding }

markup extension to te
ll the control
Template

to gets its value from the templated parent control. Something like

Collapse

<!
--

Simple Button with some Mouse events/Property Hi
-
Jacking
--
>

<
Cont
rolTemplate

x:Key
="bordereredButtonTemplateWithMouseAndPropHiJacking"

TargetType
="{x:Type Button}">


<
Border

x:Name
="border"

CornerRadius
="3"

Background
="{TemplateBinding Background}"



.....


</
Border
>


<
ControlTemplate.Triggers
>


....


....


</
ControlTemplate.Triggers
>

</
ControlTemplate
>




The important part is
Background=
"{TemplateBinding Background}"

this is enough
to ensure that the conrtol
Template

uses same value as the templated parent control.

Hi
-
Jacking Properties

Somet
imes you may have a situation where you want to use more than 1 property from
the original source control being templated, but there is not a property for the item you
are trying to use. For example imagine you want to create a Button
Template

that
contain
s both text and an image. The text is easy you can use the Button.Content
property, but if that is being used for the text. Where could we also get a value for the
image. We could of course use an Attached property as described in my previous
article
on Dependancy Properties

but we could also look out for any unused properties and hi
jack them to be used in the control
Template
. For example the Tag property of all
controls

take
object

as a value which makes it very handy.

We can then use this in the binding for example have a look at this

Collapse

<!
--

Simple Button with some Mouse events/
Property Hi
-
Jacking
--
>

<
ControlTemplate

x:Key
="bordereredButtonTemplateWithMouseAndPropHiJacking"

TargetType
="{x:Type Button}">


<
Border

x:Name
="border"

CornerRadius
="3"

Background
="{TemplateBinding Background}"



BorderBrush
="{TemplateBindin
g Foreground}"



BorderThickness
="2"

Width
="auto"

Visibility
="Visible">


<
StackPanel

Orientation
="Horizontal">


<
Image

Source
="{Binding RelativeSource={RelativeSource
TemplatedParent},


Path=Tag}"

Width
="20"

Hei
ght
="20"

HorizontalAlignment
="Left"


Margin
="{TemplateBinding Padding}"

/>


<
ContentPresenter



Margin
="{TemplateBinding Padding}"



Content
="{TemplateBinding Content}"



Width
="auto"

Height
="auto"/>


</
StackPanel
>


</
Border
>


<
ControlTemplate.Triggers
>


....


....


</
ControlTemplate.Triggers
>

</
ControlTemplate
>




Notice that I am actually using both the original control (Button) Content for the Text
(which wil
l be shown within a ContentPresenter, which is capable of showing any single
peice of content. The original Button.Context text in this case) and the original control
(Button) Tag for the
Template
s Image.

If you are interested in seeing how you could have
used an Attached Property in a
Template

instead of hi
-
jacking a unused property. Have a look at
this excellent blog
entry by Josh Smith
.

Exa
mple Templates

Ok so we've done really well, just a bit more. I just want to describe a few different
Template

types and ill also be going through a rather cool example at the end.

ControlTemplate

ControlTemplate is the most common type of
Template

which i
s used to control how a
control is rendered and baves by specifying ies the visual structure and behavioral aspects
of a Control.

Here is an extremely simple example that simply replaces a standard Buttons Controls
Template

with an Ellipse and a ContentPre
senter to present the Button.Content.

Collapse


<
ControlTemplate

TargetType
="Button">


<
Grid
>


<
Ellipse

Fill
="{TemplateBinding Background}"/>



<
ContentPresenter

HorizontalAlignment
="Center"


VerticalAlignment
="Center"/>


</
Grid
>


</
ControlTemplate
>

And here another couple (again for a Button) that use most of things we talked about,
Property Triggers, Prop
erty Hi
-
Jacking (my term not official one, so dont look it up),
TemplateBinding

Collapse

<!
--

Simple Button with simple properties
--
>

<
ControlTemplate

x:Key
="bordereredBut
tonTemplate"

TargetType
="{x:Type
Button}">


<
Border

x:Name
="border"

CornerRadius
="3"

Background
="Transparent"



BorderBrush
="{TemplateBinding Foreground}"

BorderThickness
="2"



Width
="auto"

Visibility
="Visible">


<
ContentPre
senter

Margin
="3"



Content
="{TemplateBinding Content}"

Width
="auto"

Height
="auto"/>


</
Border
>


<
ControlTemplate.Triggers
>


<
Trigger

Property
="IsEnabled"

Value
="false">


<
Setter

TargetName
="border"

Property
="Opacity"

Va
lue
="0.4"/>


</
Trigger
>


</
ControlTemplate.Triggers
>

</
ControlTemplate
>



<!
--

Simple Button with some Mouse events
--
>

<
ControlTemplate

x:Key
="bordereredButtonTemplateWithMouseEvents"

TargetType
="{x:Type Button}">


<
Border

x:Name
="border"

Corn
erRadius
="3"

Background
="Transparent"



BorderBrush
="{TemplateBinding Foreground}"



BorderThickness
="2"

Width
="auto"

Visibility
="Visible">


<
ContentPresenter

Margin
="3"

Content
="{TemplateBinding
Content}"



Width
="
auto"

Height
="auto"/>


</
Border
>


<
ControlTemplate.Triggers
>


<
Trigger

Property
="IsEnabled"

Value
="false">


<
Setter

TargetName
="border"

Property
="Opacity"

Value
="0.4"/>


</
Trigger
>


<
Trigger

Property
="IsMouseOver"

Valu
e
="true">


<
Setter

TargetName
="border"

Property
="Background"

Value
="Orange"/>


</
Trigger
>


</
ControlTemplate.Triggers
>

</
ControlTemplate
>



<!
--

Simple Button with some Mouse events/Property Hi
-
Jacking
--
>

<
ControlTemplate

x:Key
="border
eredButtonTemplateWithMouseAndPropHiJacking"



TargetType
="{x:Type Button}">


<
Border

x:Name
="border"

CornerRadius
="3"

Background
="{TemplateBinding Background}"



BorderBrush
="{TemplateBinding Foreground}"



BorderThickn
ess
="2"

Width
="auto"

Visibility
="Visible">


<
StackPanel

Orientation
="Horizontal">


<
Image

Source
="{Binding RelativeSource={RelativeSource
TemplatedParent},


Path=Tag}"

Width
="20"

Height
="20"

HorizontalAlignment
="Left"



Margin
="{TemplateBinding Padding}"

/>


<
ContentPresenter



Margin
="{TemplateBinding Padding}"



Content
="{TemplateBinding Content}"



Width
="auto"

Height
="auto"/>


</
StackPanel
>



</
Border
>


<
ControlTemplate.Triggers
>


<
Trigger

Property
="IsEnabled"

Value
="false">


<
Setter

TargetName
="border"

Property
="Opacity"

Value
="0.4"/>


</
Trigger
>


<
Trigger

Property
="IsMouseOver"

Value
="true">


<
Setter

TargetName
="border"

Property
="Background"

Value
="Orange"/>


</
Trigger
>


</
ControlTemplate.Triggers
>

</
ControlTemplate
>

These control Templates are available within the demo app
VariousControlTemplatesWindow.xaml file, and look like this


You can read more about
ControlTemplate
s right
her
e

DataTemplate

You use a DataTemplate to specify the visualization of your data objects. DataTemplate
objects are particularly useful when you are binding an ItemsControl such as a ListBox to
an entire collection. Without specific instructions, a ListBox
displays the string
representation of the objects in a collection. In that case, you can use a DataTemplate to
define the appearance of your data objects. The content of your DataTemplate becomes
the visual structure of your data objects.

Within the demo a
pp DemoLauncherWindow.xaml file, I add a number of custom object
of type DemoListItem objects to a ListBox.

The DemoListItem objects look like this

Collapse

using

System;

using

System.Collections.Generic;

using

System.Linq;

using

System.Text;

using

System.Windows;



namespace

Styles_And_Templates

{


///

<summary>


///

Is used within the
<see

cref="DemoLauncherWindow">


///

demo launcher window
</see>

as individual l
istBox


///

items


///

</summary>


public

class

DemoListItem


{


#region

Public Properties


public

string

WindowName {
get
;
set
; }


public

string

DemoName {
get
;
set
; }


#endregion


}

}

And in VB .NET

Collapse

Imports

System

Imports

System.Collections.Generic

Imports

System.Linq

Imports

System.Text

Imports

System.Windows


'''

<summary>


'''

Is used within the
<see

cref="DemoLaunche
rWindow">


'''

demo launcher window
</see>

as individual listBox

'''

items

'''

</summary>


Public

Class

DemoListItem

#Region

"Public Properties"


Private

m_WindowName
As

String


Public

Property

WindowName()
As

String


Get


Return

m
_WindowName


End

Get


Set
(
ByVal

value
As

String
)


m_WindowName = value


End

Set



End

Property



Private

m_DemoName
As

String


Public

Property

DemoName()
As

String


Get


Return

m_DemoName


E
nd

Get


Set
(
ByVal

value
As

String
)


m_DemoName = value


End

Set


End

Property

#End

Region

End

Class

So with this knowledge, we can create a
DataTemplate

in the Window host the ListBox
that contains these objects. Lets see that s
hall we

Collapse

<!
--

Listbox DemoListItem Type Template
--
>

<
DataTemplate

x:Key
="demoItemTemplate"

DataType
="x:Type
local:DemoListItem">


<
StackPanel

Orientation
="Hori
zontal"

Margin
="10">


<
Path

Name
="pathSelected"

Fill
="White"

Stretch
="Fill"

Stroke
="White"

Width
="15"



Height
="20"

Data
="M0,0 L 0,15 L 7.5,7.5"



Visibility
="Hidden"/>


<
Border

BorderBrush
="White"

BorderThickness
="4"

Ma
rgin
="5">


<
Image

Source
="Images/DataLogo.png"

Width
="45"

Height
="45"/>


</
Border
>


<
StackPanel

Orientation
="Vertical"

VerticalAlignment
="Center">


<
TextBlock

FontFamily
="Arial Black"

FontSize
="20"



Fo
ntWeight
="Bold"


Width
="auto"

Height
="auto"


Text
="{Binding Path=DemoName}"

/>


<
TextBlock

FontFamily
="Arial"

FontSize
="10"



FontWeight
="Normal"


Width
="auto"

Height
="au
to"


Text
="{Binding Path=WindowName}"

/>



</
StackPanel
>


</
StackPanel
>


<
DataTemplate.Triggers
>


<
DataTrigger



Binding
="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,


AncestorType
={x:Type ListBoxItem}, AncestorLevel=1},
Path=IsSelected}"

Value
="True">


<
Setter

TargetName
="pathSelected"

Property
="Visibility"

Value
="Visible"

/>


</
DataTrigger
>


</
DataTemplate.Triggers
>

</
DataTemplate
>

You will notice that this m
akes use of
{Binding }
markup extensions, in order to pick
out the properties of the underyling bound data object to use in the
DataTemplate
. Also
note that is this is a
Template

for a bound data object we need to use DataTriggers.

This results in a ListBo
x item that looks like this


You can read more at the Data Templating Overview which can be found right
here

HierarchicalDataTemplate

The
HierarchichalDataTemplate

is really just a
DataTemplate

that may be used over
Hierarchichal structure like a TreeView or Menu.

I have stolen the example that you will find within the demo app
Hiera
rchicalDataTemplateWindow.xaml file from the MSDN documentation. The basic
idea is that you have a Hierarchichal list that you are using as a data source for both a
TreeView and a Menu. Then you apply some
HierarchichalDataTemplate

to get the
bound Hierarc
hichal list data to be rendered correctly.

Here is the source list in C#

Collapse

using

System;

using

System.Windows;

using

System.Windows.Controls;

using

System.Windows.D
ata;

using

System.Windows.Documents;

using

System.Windows.Media;

using

System.Windows.Shapes;

using

System.Collections.ObjectModel;

using

System.Collections.Generic;



namespace

Styles_And_Templates

{


#region

Inner data classes



public

class

League


{


public

string

Name {
get
;
private

set
; }


public

List<Division> Divisions {
get
;
private

set
; }



public

League(
string

name)


{


Name = name;


Divisions =
new

List<Division>();


}


}




public

class

Division


{



public

string

Name {
get
;
private

set
; }


public

List<Team> Teams {
get
;
private

set
; }



public

Division(
string

name)


{


Name = name;


Teams =
new

List<Team>();



}


}





public

class

Team


{


public

string

Name {
get
;
private

set
; }



public

Team(
string

name)


{


Name = name;


}


}


#endregion



#region

LeagueList




///

<summary>


///

Provides a si
mple LeagueList, holding dummy


///

data to demonstrate binding to Hierarchical


///

data structures


///

</summary>


public

class

LeagueList : List<League>


{


public

LeagueList()


{


League l;


Division
d;



Add(l =
new

League(
"League A"
));


l.Divisions.Add((d =
new

Division(
"Division A"
)));


d.Teams.Add(
new

Team(
"Team I"
));


d.Teams.Add(
new

Team(
"Team II"
));


d.Teams.Add(
new

Team(
"Team III"
));



d.Teams.Add(
new

Team(
"Team IV"
));


d.Teams.Add(
new

Team(
"Team V"
));


l.Divisions.Add((d =
new

Division(
"Division B"
)));


d.Teams.Add(
new

Team(
"Team Blue"
));


d.Teams.Add(
new

Team(
"Team Red"
));


d
.Teams.Add(
new

Team(
"Team Yellow"
));


d.Teams.Add(
new

Team(
"Team Green"
));


d.Teams.Add(
new

Team(
"Team Orange"
));


l.Divisions.Add((d =
new

Division(
"Division C"
)));


d.Teams.Add(
new

Team(
"Team East"
));



d.Teams.Add(
new

Team(
"Team West"
));


d.Teams.Add(
new

Team(
"Team North"
));


d.Teams.Add(
new

Team(
"Team South"
));


Add(l =
new

League(
"League B"
));


l.Divisions.Add((d =
new

Division(
"Division A"
)));



d.Teams.Add(
new

Team(
"Team 1"
));


d.Teams.Add(
new

Team(
"Team 2"
));


d.Teams.Add(
new

Team(
"Team 3"
));


d.Teams.Add(
new

Team(
"Team 4"
));


d.Teams.Add(
new

Team(
"Team 5"
));


l.Divisions.Add((d =
new

Divis
ion(
"Division B"
)));


d.Teams.Add(
new

Team(
"Team Diamond"
));


d.Teams.Add(
new

Team(
"Team Heart"
));


d.Teams.Add(
new

Team(
"Team Club"
));


d.Teams.Add(
new

Team(
"Team Spade"
));


l.Divisions.Add((d =
new

D
ivision(
"Division C"
)));


d.Teams.Add(
new

Team(
"Team Alpha"
));


d.Teams.Add(
new

Team(
"Team Beta"
));


d.Teams.Add(
new

Team(
"Team Gamma"
));


d.Teams.Add(
new

Team(
"Team Delta"
));


d.Teams.Add(
new

Team(
"Te
am Epsilon"
));


}



public

League
this
[
string

name]


{


get


{


foreach

(League l
in

this
)


if

(l.Name == name)


return

l;



return

null
;



}


}


}


#endregion

}

And in VB .NET

Collapse

Imports

System

Imports

System.Windows

Imports

System.Windows.Controls

Imports

System.Windows.Data

Impor
ts

System.Windows.Documents

Imports

System.Windows.Media

Imports

System.Windows.Shapes

Imports

System.Collections.ObjectModel

Imports

System.Collections.Generic


#Region

"Inner data classes"

Public

Class

League


Private

m_Name
As

String


Public

Prope
rty

Name()
As

String


Get


Return

m_Name


End

Get


Private

Set
(
ByVal

value
As

String
)


m_Name = value


End

Set


End

Property



Private

m_Divisions
As

List(
Of

Division)


Public

Property

Divisions()
As

List(
Of

Division)


Get


Return

m_Divisions


End

Get


Private

Set
(
ByVal

value
As

List(
Of

Division))


m_Divisions = value


End

Set


End

Property



Public

Sub

New
(
ByVal

newname
As

String
)


Name

= newname


Divisions =
New

List(
Of

Division)()


End

Sub

End

Class


Public

Class

Division


Private

m_Name
As

String


Public

Property

Name()
As

String


Get


Return

m_Name


End

Get


Private

Set
(
ByVal

value
As

S
tring
)


m_Name = value


End

Set


End

Property


Private

m_Teams
As

List(
Of

Team)


Public

Property

Teams()
As

List(
Of

Team)


Get


Return

m_Teams


End

Get


Private

Set
(
ByVal

value
As

List(
Of

Team))



m_Teams = value


End

Set


End

Property



Public

Sub

New
(
ByVal

newname
As

String
)


Name = newname


Teams =
New

List(
Of

Team)()


End

Sub

End

Class




Public

Class

Team


Private

m_Name
As

String


Public

Property

N
ame()
As

String


Get


Return

m_Name


End

Get


Private

Set
(
ByVal

value
As

String
)


m_Name = value


End

Set


End

Property



Public

Sub

New
(
ByVal

newname
As

String
)


Name = newname


End

Sub

End

Class

#End

Region


#Region

"LeagueList"


'''

<summary>


'''

Provides a simple LeagueList, holding dummy

'''

data to demonstrate binding to Hierarchical

'''

data structures

'''

</summary>


Public

Class

LeagueList


Inherits

List(
Of

League)


Public

Sub

New
()


Dim

l
As

League


Dim

d
As

Division


l =
New

League(
"League A"
)


Add(l)


d =
New

Division(
"Division A"
)


l.Divisions.Add(d)


d.Teams.Add(
New

Team(
"Team I"
))


d.Teams.Add(
New

Team(
"Team II"
))


d.Teams.Add(
New

Team(
"Team III"
))


d.Teams.Add(
New

Team(
"Team IV"
))


d.Teams.Add(
New

Team(
"Team V"
))


d =
New

Division(
"Division B"
)


l.Divisions.Add(d)


d.Teams.Add(
New

Team(
"Team Blue"
))


d.Teams.Add(
New

Team(
"Team Red"
))


d.Teams.Add(
New

Team(
"Team Yellow"
))


d.Teams.Add(
New

Team(
"Team Green"
))


d.Teams.Add(
New

Team(
"Team Orange"
))


d =
New

Division(
"Division C"
)


l.Divisions.Add(d)


d.Teams.Add(
New

Team(
"Team Ea
st"
))


d.Teams.Add(
New

Team(
"Team West"
))


d.Teams.Add(
New

Team(
"Team North"
))


d.Teams.Add(
New

Team(
"Team South"
))


l =
New

League(
"League B"
)


Add(l)


d =
New

Division(
"Division A"
)


l.Divisions.Add(d)



d.Teams.Add(
New

Team(
"Team 1"
))


d.Teams.Add(
New

Team(
"Team 2"
))


d.Teams.Add(
New

Team(
"Team 3"
))


d.Teams.Add(
New

Team(
"Team 4"
))


d.Teams.Add(
New

Team(
"Team 5"
))


d =
New

Division(
"Division B"
)


l.Divisions.
Add(d)


d.Teams.Add(
New

Team(
"Team Diamond"
))


d.Teams.Add(
New

Team(
"Team Heart"
))


d.Teams.Add(
New

Team(
"Team Club"
))


d.Teams.Add(
New

Team(
"Team Spade"
))


d =
New

Division(
"Division C"
)


l.Divisions.Add(d)



d.Teams.Add(
New

Team(
"Team Alpha"
))


d.Teams.Add(
New

Team(
"Team Beta"
))


d.Teams.Add(
New

Team(
"Team Gamma"
))


d.Teams.Add(
New

Team(
"Team Delta"
))


d.Teams.Add(
New

Team(
"Team Epsilon"
))


End

Sub



Default

Public

Overloads

ReadOnly

Property

Item(
ByVal

name
As

String
)
As

League


Get


For

Each

l
As

League
In

Me


If

l.Name = name
Then


Return

l


End

If


Next



Return

Nothing


End

Ge
t


End

Property

End

Class

#End

Region

And here is the
HierarchichalDataTemplate

Collapse

<!
--

League matched template
--
>

<
HierarchicalDataTemplate

DataType
="{x:Type loc
al:League}"


ItemsSource

= "{Binding Path=Divisions}">


<
TextBlock

Text
="{Binding Path=Name}"

Background
="Red"/>

</
HierarchicalDataTemplate
>


<!
--

Division matched template
--
>

<
HierarchicalDataTemplate

DataType
="{x:Type local:Division
}"


ItemsSource

= "{Binding Path=Teams}">


<
TextBlock

Text
="{Binding Path=Name}"

Background
="Green"/>

</
HierarchicalDataTemplate
>


<!
--

Division matched Team
--
>

<
DataTemplate

DataType
="{x:Type local:Team}">


<
TextBlock

Text
="{Bindi
ng Path=Name}"

Background
="CornflowerBlue"/>

</
DataTemplate
>

This results in something like this, where each of the underlying bound data objects get
its own
Template


You can read more about this right
here

and if you want a really cool advanced look at
what can be done with the
HierarchichalDat
aTemplate

have a look at Codeproject
MVP Karl Shiffletts excellent blog entry right
here

where Karl uses a
HierarchichalDataTemplate

to c
reate a simple explorer type TreeView (which was
actually a reaction to Joshes reaction, to my orginal simple explorer type TreeView
article)

A Small Demo Discussion (Cos its cool)

Now for a good while now, I have been reading blogs about WPF, and for Data
binding
they dont come any better than
Beatriz Costa

from Microsoft. She really kicks it man. I
was really impressed by one of her samples in particular and I think it really impressed
just how powe
rful Binding and Templating can be in WPF.

Her original post is
The power of Styles and Templates in WPF
, I have asked Bea if I
could use this in this article. She kindly agreed (she told me I
have to buy her a
beer..sounds good to me) So thanks Bea.

First a screen shot.


Now what would you say if I told you this was a ListBox. Cool huh. To see how i
t works,
lets disect it a bit. I'm not going to include all the code here, you can check out my code
or go straight to the source and see Beas Blog. But I did just want to show you what you
can do with a clever
Style

and
Template

or 2. Lets see the impor
tant stuff, the XAML

Collapse

<
Window

x:Class
="PlanetsListBox.PlanetsListBoxWindow"


xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


xmlns:x
="http
://schemas.microsoft.com/winfx/2006/xaml"


xmlns:local
="clr
-
namespace:PlanetsListBox"



Title
="PlanetsListBox"

Height
="700"

Width
="700"


>


<
Window.Resources
>


<
local:SolarSystem

x:Key
="solarSystem"

/>


<
local:ConvertOrbit

x:Key
="
convertOrbit"

/>



<
DataTemplate

DataType
="{x:Type local:SolarSystemObject}">


<
Canvas

Width
="20"

Height
="20"

>


<
Ellipse



Canvas.Left
="{Binding Path=Orbit,


Converter={StaticReso
urce convertOrbit},


ConverterParameter=
-
1.707}"



Canvas.Top
="{Binding Path=Orbit,


Converter={StaticResource convertOrbit},


ConverterParameter=
-
0.293}"




Width
="{Binding Path=Orbit,


Converter={StaticResource convertOrbit},


ConverterParameter=2}"



Height
="{Binding Path=Orbit,


Converter={StaticResource co
nvertOrbit},


ConverterParameter=2}"



Stroke
="White"



StrokeThickness
="1"/>


<
Image

Source
="{Binding Path=Image}"

Width
="20"

Height
="20">


<
Image.ToolTip
>



<
StackPanel

Width
="250"

TextBlock.FontSize
="12">


<
TextBlock

FontWeight
="Bold"

Text
="{Binding
Path=Name}"

/>


<
StackPanel

Orientation
="Horizontal">


<
TextBlock

Text
="Orbit: "

/>


<
TextBlock

Text
="{Binding Path=Orbit}"

/>


<
TextBlock

Text
=" AU"

/>


</
StackPanel
>


<
TextBlock

Text
="{Binding P
ath=Details}"



TextWrapping
="Wrap"/>


</
StackPanel
>


</
Image.ToolTip
>


</
Image
>


</
Canvas
>


</
DataTemplate
>



<
Style

TargetType
="ListBoxItem">


<
Setter

Property
="Canvas.Left"

Value
="{Binding Path=Orbit,


Converter={StaticResource convertOrbit},
ConverterParameter=0.707}"/>


<
Setter

Property
="Canvas.Bottom"

Value
="{Binding
Path=Orbit,



Converter={StaticResource convertOrbit},
ConverterParameter=0.707}"/>


<
Setter

Property
="Template">


<
Setter.Value
>


<
ControlTemplate

TargetType
="{x:Type ListBoxItem}">


<
Grid
>



<
Ellipse

x:Name
="selectedPlanet"

Margin
="
-
10"

StrokeThickness
="2"/>


<
ContentPresenter

SnapsToDevicePixels
="{TemplateBinding SnapsToDevicePixels}"



HorizontalAlignment
="{TemplateB
inding
HorizontalContentAlignment}"



VerticalAlignment
="{TemplateBinding
VerticalContentAlignment}"/>


</
Grid
>


<
ControlTemplate.Triggers
>


<
Trigger

Pr
operty
="IsSelected"

Value
="true">


<
Setter

Property
="Stroke"

TargetName
="selectedPlanet"

Value
="Yellow"/>


</
Trigger
>


</
ControlTemplate.Triggers
>


</
Contro
lTemplate
>


</
Setter.Value
>


</
Setter
>


</
Style
>



<
Style

TargetType
="ListBox">


<
Setter

Property
="ItemsPanel">


<
Setter.Value
>


<
ItemsPanelTemplate
>



<
Canvas

Width
="590"

Height
="590"

Background
="Black"

/>


</
ItemsPanelTemplate
>


</
Setter.Value
>


</
Setter
>


</
Style
>


</
Window.Resources
>



<
Grid

HorizontalAlignment
="Center"

VerticalAlignment
="Ce
nter"

ClipToBounds
="True">


<
ListBox

ItemsSource
="{Binding Source={StaticResource
solarSystem},


Path=SolarSystemObjects}"

Focusable
="False"

/>


</
Grid
>

</
Window
>

Now there is nothing in here that we have not talked about either in th
is article or the
other 5 that go with this one. I think its very impressive.

Great work Bea. Thanks.

Lookless Controls

Now that you have nearly read this article I want to put something to you. We now know
that we can totally change how a control looks us
ing
Style
s and
Template
s. We can
even imagine a sitaution where a control designer creates some control, let say an image
picker that has a single button, but beacuse we can
Style
s and
Template

this control any
way we want. Which is cool, but there is no g
aruentee that the person who designs the
Style
s and
Template
s knows what the control does, and how it works. Microsofts view
on this, is that these are 2 roles, designer and developer. And neither should care about
what the other does.

They should be able

to write working controls that a designer can restyle and way they
see fit. This is known as lookless controls.

Well thats fine, but in practice of course they both need to know a bit about each others
work.

So in order for a developer to help a designer,

the developer can at least expose certain
meta data that expresses the developers intentions for the control. The designer can of
course ignore this, but the
Style
ed and
Template
d control probably wont work correctly.
But in theory if the designer plays f
air and follows the next bit of advice, the control
should work (providing the developer knows what they are doing and actually does their
part).

So imagine this.

The developer creates a simple control such as :


Where we have an Image and a Button to assign a new Image.

Now the designer knows a few things about WPF and Expression Blend, so decides that
the control should look like this :


Now from a
Style

and
Template

view point, there is nothing wrong with this.
Remember we can create a
Template

for a Control and make it look how we like.

So thats fine. What i
s NOT fine, is the fact that the control just won't work as the
developer code it to work anymore.

Why not...Well there is no Button, so how does a new picture get picked. Well the
answer lies in code, developer code and designer code both have roles to p
lay.

The recommended procedure is as follows:

Firstly the developer must put across their intent for the control by adorning it with some
attributes (you can have as many as you need), with the
TemplatePartAttribute

which
tells the designer what he is expected to include within the
Template

for a Control. The
other thing the developer MUST do is to either use an override of OnApplyTemplate()
and look for the Template PARTS that they
were expecting the designer to provide and
hook up these events within the code behind, or use routed commands.

Collapse

using

System;

using

System.Collections.Generic;

u
sing

System.Text;

using

System.Windows;

using

System.Windows.Controls;

using

System.Windows.Data;

using

System.Windows.Documents;

using

System.Windows.Input;

using

System.Windows.Media;

using

System.Windows.Media.Imaging;

using

System.Windows.Navigation;

u
sing

System.Windows.Shapes;


namespace

UntitledProject1

{


///

<summary>


///

Interaction logic for UserControl1.xaml


///

</summary>


///




[TemplatePart(Name=
"PART_PickNew"
,Type=typeof(Button))]


public

partial

class

UserControl1 : Use
rControl


{


public

UserControl1()


{


InitializeComponent();


}



public

override

void

OnApplyTemplate()


{


base
.OnApplyTemplate();


Button PART_PickNew =
this
.Template.FindName(
"PART
_PickNew"
)
as

Button;


if

(PART_PickNew !=
null
)


{


PART_PickNew.Click +=
new

RoutedEventHandler(PART_PickNew_Click);


}


}



void

PART_PickNew_Click(
object

sender, RoutedEventArgs e)


{


//do the stuff here that handles the PART_PickNew logic


}


}

}

So from a developer point of view, all is catered for. Over to the designer part. As long as
they actually use a Button and give it a name that matches what the develope
r coded
against (the clue is in the
TemplatePartAttribute)
everything should be fine, and work as
expected. A god example is shown here.

Collapse

<
Image

Source
="C:
\
Users
\
sacha
\
Pictures
\
BLACK_OR_WHITE.jpg"



Stretch
="UniformToFill"

Width
="50"

Height
="50"/>

<
Button

x:Name
="PART_PickNew"

Content
="Pick New Image"/>

ALWAYS

The code behind can happily

link into the Buttons events when the
Template

gets
applied, and the control will work even though it looks totaly different thanks to a cool
Template

being applied. Groovy.

This is what lookless controls are all about. Give the designer the ability to ma
ke it look
how they want, but always ensure that it still works as expected.

This could also have been done with Routed UI Commands, in fact I have written a
whole article in the past just on this concept so if you are interested, that article can be
found

right
here

But Wait There Will Be 1 More Article!!!

The more eagle eyed amongst you, may have noticed that this was actually the last in my
proposed beginners series.

But...There

will be 1 more article after this one, which will be the final episode in this
series. This article will cover everything we have learned along the way throughout this
beginners series. I have done the code already, and am really really proud of it, and c
an't
wait to share it with you all. But for now we must contain ourselves to the task at hand,
learning this little lot.

I'm also planning on one more WPF after this final one. So thats 2 more in total, then I
really must move onto something else. Patterns
/Threading/WF /PLINQ/Dynamic
Queryies/Entity Framework who knows. We'll see.

References

1.

Beatriz "The Binding Queen" Costa : Blog


2.

Bea Costas :
The power of Styles and Templates in WPF

(PLanetListBox) that
I've used with her kind permission.

Cheers Bea, Ill grab you a beer at some point

Other good sources

1.

Josh Smith :
A Guided Tour of WPF


Part 4 (Data templates and triggers)


2.

Josh Smith :
A Guided Tour of WPF


Part 5 (Styles)


3.

Chaz :
Theming In WPF


4.

MSDN :
Style Class


5.

MSDN :
Styling And Templating


6.

MSDN :
How to: Trigger an Animation When Data Changes


History

09/03/08 : Initial release

License

This article,

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