WPF Guided Tour

advertisement
WPF Guided Tour
Josh Smith
Table of contents





Part 1 (XAML): Learn about XAML and how it is used in WPF applications.
Part 2 (Layout): Learn about layout panels and how they are used to construct
user interfaces.
Part 3 (Data binding): Learn how WPF data binding works and how it can be
used.
Part 4 (Data templates and triggers): Learn how data templates and triggers work
and how they can be used.
Part 5 (Styles): Learn about how UIs can be styled in WPF.
Part 1 - XAML
Introduction
This article is the first in a series which reviews some important features of Microsoft's
Windows Presentation Foundation (WPF). The entire series barely scratches the surface
of the immense WPF platform, and it does not dive too deeply into any particular topic.
The purpose of this series is to familiarize the reader with the basic WPF programming
model to the point where he/she is comfortable enough with WPF to fully understand the,
rather silly, WPF Horse Race application (available for download via a link at the top of
this page).
The subject matter is presented in the context of a completely impractical horse racing
application, as seen in the screenshot above. The application was designed with the
intention of being used as the focal point of this series. I tried to find the right balance
between making the demo application simple enough to be understandable to a WPF
newcomer, yet complicated enough to actually do something interesting (or at least
mildly amusing).
Prerequisites
In order to run a WPF application you need the .NET Framework version 3.0, or greater.
Windows Vista has the .NET Framework v3.0 installed by default, so you only need to
install it if you have Windows XP SP2.
To develop WPF applications you should have Visual Studio 2005 with the "Orcas"
extensions, or a later version of Visual Studio, and also the Windows SDK. Refer to the
External Links section at the end of this article for links to relevant resources.
What is XAML?
XAML stands for eXtensible Application Markup Language. It is an all-purpose XMLbased language used for declaring object graphs, which are instantiated at runtime.
XAML is used by WPF developers to declare the layout of a user interface (UI), and the
resources used in that UI.
It is not at all necessary to use XAML when programming in WPF. Anything that can be
done in XAML can also be done in code. Using XAML makes many UI development
scenarios much easier and faster, such as creating the layout of a UI and configuring
styles, templates, and other WPF-specific entities discussed later in this series.
Performance impact
When you compile a WPF application in Visual Studio it will compile your XAML files
into a compressed representation known as Binary Application Markup Language
(BAML). The BAML is then saved as a resource in the resultant assembly. When that
assembly is loaded and the resource is requested, the BAML is streamed out and very
quickly turned into the object graph described by the original XAML.
This two-step process allows the XAML parsing to occur at compile time, thus mitigating
the performance overhead of using a text-based object instantiation technology. With that
said, it is possible to load XAML programmatically via the XamlReader.Load method.
Dynamically loading XAML can be useful in a variety of situations, such as when a
volatile portion of your UI is periodically downloaded from a server via XML Web
services.
Basic syntax
Since XAML is an XML-based language, it should be quite simple and straightforward
for anyone with XML experience. There are two different uses for XML elements in
XAML; elements that represent objects and elements that represent properties of objects.
An XML attribute always represents a property or event of an object. That's it; it is that
simple. There are a few other concepts built on top of those fundamentals, but XAML
never becomes complicated.
For example:
Collapse
<Button Content="Click Me" Click="OnButtonClick">
<Button.Background>
<LinearGradientBrush>
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Green" Offset="1" />
</LinearGradientBrush>
</Button.Background>
</Button>
That XAML results in a funky little button, which is rendered like this:
Let's dissect that XAML to see how it works. The <Button> element declares that an
instance of the Button class will be created. That Button object will have its Content
property set to the string "Click Me". The Button's Click event will be handled by a
method called OnButtonClick in the code-behind file (we'll get into code-behind files
soon).
The next element is <Button.Background>, which is said to use the "property element
syntax". That XML element represents the Background property of the Button being
configured. The child element of a property element is the value to which the property is
set. In this case, the Background property is set to a LinearGradientBrush object.
That brush has two <GradientStop> child elements, which might seem confusing at
first. What are those GradientStop objects doing there? What are they being assigned or
added to? The answer lies in the fact that XAML has some "shortcuts" built in which
allow you to specify a property on a class which is considered to be the "content
property." You do not need to use the property element syntax to assign a value to the
"content property" of a class. Since LinearGradientBrush's content property is the
GradientStops property, the following XAML is equivalent to that seen above:
Collapse
<Button Content="Click Me" Click="OnButtonClick">
<Button.Background>
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Green" Offset="1" />
<LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Button.Background>
</Button>
The C# version
In case you are curious about what that XAML would look like if it was written in C#,
here's the translation:
Collapse
Button b = new Button();
b.Content = "Click Me";
b.Click += this.OnButtonClick;
LinearGradientBrush lgb = new LinearGradientBrush();
lgb.GradientStops.Add( new GradientStop( Colors.Yellow, 0 ) );
lgb.GradientStops.Add( new GradientStop( Colors.Green, 1 ) );
b.Background = lgb;
Markup extensions
The XAML parser also knows how to work with a special construct known as the
"markup extension." Markup extensions allow you to compactly configure objects and
reference other objects defined elsewhere in the application. They are used when setting a
property on an object, either via an XML attribute or the property element syntax.
Markup extensions are used for things such as specifying a null value, declaring an
array, referencing an object held in a resource dictionary, binding one property to
another, and much more.
The following XAML is roughly equivalent to the example shown previously (with a few
minor changes):
Collapse
<Grid>
<Grid.Resources>
<LinearGradientBrush x:Key="FunkyBrush">
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Green" Offset="1" />
</LinearGradientBrush>
</Grid.Resources>
<Button Background="{StaticResource FunkyBrush}">Click Me</Button>
</Grid>
Notice that the Button's Background property is set, via an XML attribute, to
{StaticResource FunkyBrush}. When an attribute is set to a value which begins with {
and ends with }, then a markup extension is being used.
In this case the StaticResourceExtension class is being used to reference a
LinearGradientBrush stored in the Grid's resource dictionary. (Note: Grid is a layout
panel in WPF, not a tabular data presentation control like you might expect.)
Code-behind files
XAML is compiled down into a class. Unless you specify the class name it should use,
the compiler will generate a class name for you. However, when you apply the x:Class
attribute to the root XML element in a XAML file, you can create a partial class in C# or
VB.NET which will be merged with the XAML partial. This is how you can associate
behavior with the layout and visuals declared in XAML.
For example, in the demonstration above we created a Button in XAML and assigned an
event handling method to its Click event. The OnButtonClick method would be defined
in the code-behind file which contains another part of the class associated with the partial
class generated by the XAML.
Let's suppose that the Button we've been discussing is in a Window subclass called
MyWindow. Here would be the code-behind file (MyWindow.xaml.cs):
Collapse
public partial class MyWindow : Window
{
public MyWindow()
{
InitializeComponent();
}
void OnButtonClick( object sender, RoutedEventArgs e )
{
MessageBox.Show( "Click me again. That felt good." );
}
}
How the WPF Horse Race uses XAML
The WPF Horse Race application is almost entirely written in XAML. The application
uses three C# files: the code-behind for the main Window, a file for the RaceHorse class,
and another which contains some value converters (more on those in the article about
data binding). The rest of the application is all in XAML.
I encourage you to explore the WPF Horse Race source files and check out what XAML
looks like when it's being used to create a real program (as opposed to just a simple
demonstration of it). The rest of the articles in this series will examine how that entire
application works, but there is no substitute for just digging into the source files now and
exploring how it works on your own.
External links
Prerequisites


Microsoft .NET Framework v3.0
Visual Studio "Orcas" March 2007 CTP
XAML



XAML Overview
Markup Extensions and XAML
XamlPadX
General WPF Tips

Tips on how to debug and learn about WPF
History



April 1, 2007 - Created the article.
April 3, 2007 - Removed superfluous ColumnDefinitions from the Grid in
RaceHorseDataTemplate.xaml and updated the demo project download file.
April 18, 2007 - Fixed an incorrect statement about markup extensions. Originally
I asserted that they are only used in conjunction with XML attributes, but they can
be used with the property element syntax, as well.
License
This article, along with any associated source code and files, is licensed under The Code
Project Open License (CPOL)
About the Author
Josh Smith
Josh creates software, mostly with C# and XAML.
He works at IdentityMine as a Senior UX Developer.
He plays the music of J.S. Bach on the piano, but has started
branching into other composers recently.
Member
Get his runtime debugging and scripting tool, called Crack.NET,
right here[^].
Download his WPF.JoshSmith library here[^]
You can check out his WPF blog here[^].
You can take his guided tour of WPF here[^].
You can check out a powerful debugger visualizer he worked on
called Mole for Visual Studio here[^].
His Microsoft MVP profile can be viewed here[^].
Occupation: Software Developer (Senior)
Company: IdentityMine, Inc.
Location:
United States
Part 2 - Layout
Introduction
This is the second article in an introductory series about the Windows Presentation
Foundation. In the previous article, we discussed XAML and how it is used in WPF
application development. This article shifts focus to WPF's rich support for layout panels,
and how they are used in the WPF Horse Race demo application (which is available for
download at the top of the first article in this series).
This article is not intended to provide an encyclopedic review of the entire layout system
in WPF. The Windows SDK documentation provides ample material on how to use and
extend the WPF layout system, so there is no point in repeating it. Instead, we will briefly
cover the basics and then examine how the WPF Horse Race application makes use of the
two most common layout panels, Grid and StackPanel.
Background
Traditional desktop application user interface development is mostly based on absolute
positioning of visual elements, namely setting properties on controls to affect their
location and size. There are some higher-level concepts one can use to make it easier to
create UIs that adapt to a changing Window size, such as docking and anchoring of
controls to their respective containers.
However, as of Windows Forms 2.0 there was still much to be desired in the realm of
automatic UI layout. WPF addresses those issues very well, in many cases by borrowing
some of the better layout concepts from the world of HTML.
Layout via panels
The Panel class is the abstract base for all layout panels in WPF. It has a Children
property which contains references to the UIElements within the panel. If you add an
element to the Children of a panel, that element's size and/or location will be "managed"
by the panel.
All but one of the Panel subclasses provide automatic positioning of its children; Canvas
being the exception to that rule. This basically means that as the size of the Window
changes, panels automatically update the location of their child elements. Some panels,
such as DockPanel and Grid, can also affect the display size of their child elements. That
behavior is useful when you want visual elements to occupy as much screen space as
possible, such as when displaying photographs or a line chart.
If you are interested in learning about all of the built-in Panel subclasses and what they
have to offer, refer to the External links section at the end of this article for more
information.
Attached layout settings
The WPF architects decided that since the layout system is extensible (i.e. you can
subclass Panel) there needs to be a flexible way of communicating layout settings
between a panel and its children. Having all of the various layout properties on a base
class, such as UIElement, would pollute its API and make it impossible for custom panels
to follow suit (you can't add properties to the UIElement class). In short, they needed a
way to set a layout property on a visual element in a panel without that element ever
knowing about the panel or its properties.
This problem was solved by introducing attached properties into the WPF framework. An
attached property can be set on any object; it does not have to expose the property being
set. For example:
Collapse
<DockPanel>
<Button DockPanel.Dock="Left">Alf Was Here</Button>
</DockPanel>
The XAML seen above puts a Button inside a DockPanel. The Button is docked to the
left side of the DockPanel because the Dock attached property is set to the 'Left' value of
the Dock enumeration.
For more information about how attached properties work refer to the pages mentioned in
the External links section at the bottom of this article. In the next section, we will see
attached properties in action.
How the WPF Horse Race uses panels
The WPF Horse Race explicitly uses two layout panels: Grid and StackPanel. I say
'explicitly' because it is entirely possible that the controls used in the application
themselves use panels other than those two. In fact, a relatively simple WPF user
interface will usually contain many panels, both small and large.
Let's take a look at a simplified version of the main Window's XAML file:
Collapse
<Window>
<Grid>
<Grid.Background>
<ImageBrush ImageSource="Resources/Background.jpg" Opacity="0.25"
/>
</Grid.Background>
<Grid.RowDefinitions>
<!-- The top row is for the race track. -->
<RowDefinition Height="*" />
<!-- The bottom row is for the command strip. -->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- The 'Race Track' area. -->
<ItemsControl Grid.Row="0" ... />
<!-- The 'Command Strip' area -->
<Border Grid.Row="1">
<Grid>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center">
<TextBlock Margin="10,4">Rotation: </TextBlock>
<Slider ... />
<TextBlock ... />
<TextBlock> degrees</TextBlock>
</StackPanel>
<TextBlock HorizontalAlignment="Right" Margin="10,4">
<Hyperlink>Start new race</Hyperlink>
</TextBlock>
</Grid>
</Border>
</Grid>
</Window>
The XAML seen above contains two Grids and one StackPanel. Below is a diagram
which shows the spatial relationships between those panels, and the elements they
contain:
The main layout
The outermost Grid in the Window is colored red. Notice that it has two rows, separated
in the diagram above, by a black line. Those two rows were declared with the following
markup:
Collapse
<Grid.RowDefinitions>
<!-- The top row is for the race track. -->
<RowDefinition Height="*" />
<!-- The bottom row is for the command strip. -->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
The first row's Height is set to '*' which means that it will try to be as tall as possible.
The second row's Height is set to 'Auto' so that it auto-sizes to the elements it contains.
This setup makes sense because the command strip on the bottom should only take up as
much space as it needs to be fully visible. The rest of the screen real estate should be
given to the "race track" above.
Notice that the row heights are not explicitly set to a numeric value. The Height property
can be set to a numeric value if necessary, but it is usually better to let the Grid class
handle as many metrics calculations as possible. Doing so allows the Grid to intelligently
resize the rows as needed.
Next we can see how to indicate in which row we want visual elements to be placed.
Grid exposes several attached properties, one of which is called Row. Here's how that
attached property is used:
Collapse
<!-- The 'Race Track' area. -->
<ItemsControl Grid.Row="0" ... />
<!-- The 'Command Strip' area -->
<Border Grid.Row="1">...</Border>
The ItemsControl does not need to have the Row attached property set on it because the
default value for that property is zero. However, the Border does need to have Row set on
it so that it does not overlap with the ItemsControl in the first row.
The command strip area
Referring back to the diagram seen above, we can now venture into the nested panels
seen toward the bottom of the UI. Here is the XAML which describes that "command
strip" area:
Collapse
<Border Grid.Row="1">
<Grid>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Margin="10,4">Rotation: </TextBlock>
<Slider ... />
<TextBlock ... />
<TextBlock> degrees</TextBlock>
</StackPanel>
<TextBlock HorizontalAlignment="Right" Margin="10,4">
<Hyperlink>Start new race</Hyperlink>
</TextBlock>
</Grid>
</Border>
That abridged XAML results in the following visual entity:
The entire command strip area is contained within a Border element, which is in the
bottom row of the main Grid panel. The Border's Child is another Grid panel, which is
represented by the yellow rectangle in the diagram above. That Grid contains two
UIElements; a StackPanel and TextBlock. Not all panels can have more than one child
element, which is why Grid was used. On a side note, Grid by default has one row and
one column, which is why the XAML which declares that Grid does not specify them.
The StackPanel, represented by a blue rectangle in the diagram, is an interesting panel
in that it can have any number of child elements, but they all will be tightly arranged next
to each other vertically or horizontally (hence, it "stacks" the child elements). I chose to
use StackPanel to contain the Slider, and surrounding TextBlocks, because it's an easy
way to create a horizontal arrangement of related elements.
The StackPanel automatically sizes to the elements contained within it. That explains
why in the diagram above, the blue rectangle does not stretch all the way to the righthand side of its parent Grid. It is just wide enough to allow the TextBlocks and Slider
to be fully visible.
Positioning elements within a panel
There are two common ways to fine-tune the location of an element within a panel. One
way is to set the element's Margin property and the other is to set its
HorizontalAlignment and/or VerticalAlignment properties.
We can see both of those techniques put to use on the TextBlock which contains the
Hyperlink on the right-hand side of the command strip:
Collapse
<TextBlock HorizontalAlignment="Right" Margin="10,4">
<Hyperlink>Start new race</Hyperlink>
</TextBlock>
Setting the HorizontalAlignment property to 'Right' will force the TextBlock to be
"pushed" over to the right-hand side of the Grid. Setting the Margin property to "10,4" is
shorthand for saying that it should be at least 10 device-independent pixels (DIPs) away
on its left and right edges from any surrounding elements, and at least 4 DIPs away on its
top and bottom edges from any surrounding elements. The net effect of those two
property settings is that the Hyperlink will be displayed on the right-hand side of the
Grid, but with a little space between its right edge and the Grid's right edge. The concept
to take away from this is that panels will perform the "broad strokes" for positioning
child elements, but the child elements can perform the "finishing touches" to position
themselves exactly where they need to be.
External links





The Layout System
Panels Overview
Panel
Panel Hierarchy
Attached Properties Overview
History

April 2, 2007 - Created the article.
Part 3 - Data Binding
Introduction
This is the third article in an introductory series about the Windows Presentation
Foundation. In the previous article we examined layout panels and how they are used to
create WPF user interfaces. In this article we will explore the world of data binding, and
how it is put to use in the WPF Horse Race demo application (which is available for
download at the top of the first article in this series).
For a comprehensive review of WPF data binding be sure to refer to the links listed in the
External links section at the bottom of the page. This article covers the bare essentials of
WPF data binding, and demonstrates various ways in which the WPF Horse Race
application uses data binding.
Background
Data binding in the user interface layer is nothing new. It has been around for quite some
time, in various UI platforms, both for desktop and Web applications. The basic idea is
that you "bind" the visual elements (controls) in a user interface to the data objects they
are meant to display. The binding infrastructure then manages the data interactions from
then on, so that modifications to the UI controls are reflected in the data objects, and vice
versa. The major benefit of using data binding is that it reduces the amount of code the
application developer needs to write.
The architects of some earlier UI platforms have done a good job integrating data binding
with the rest of their framework. WPF's architects have done an amazing job integrating
data binding with all aspects of their framework. Data binding in WPF is ubiquitous and
seamless. It is so powerful and flexible that it literally forces you to change the way you
think about designing and developing user interfaces.
With one simple API you can bind to domain/business objects, XML data, visual
elements, ADO.NET data containers, collections, and basically anything else you can
think of. You can use value converters to execute arbitrary data manipulation operations
when bound values are passed back and forth. You can perform data validation by
creating custom validation rules and applying them to a binding. The list goes on. Data
binding in WPF is really a huge step forward.
Dependency properties
Before diving into the guts and glory of WPF data binding, it is necessary to take a detour
and briefly discuss another fundamental feature of WPF which makes it all possible. In
WPF a property can only be bound if it is a dependency property.
Dependency properties are like normal .NET properties on steroids. There is a whole
dependency property infrastructure in place, which provides an array of features for
application developers to make their lives easier. For our purposes in this article it is
important to know the following things about dependency properties:
1. They can determine their value by retrieving it from a Binding object (i.e. they
can be bound).
2. They can participate in property value inheritance, meaning that if a dependency
property on an element does not have a value it will use the value of that property
on an ancestor element (in the logical tree). This is somewhat analogous to
ambient properties in the Windows Forms world.
3. They can be set in XAML, just like a normal property.
The reasons why those aspects of dependency properties are important will become clear
later on, as we examine how data binding uses them. For more information about
dependency properties, refer to the Dependency properties sub-section in the External
links section at the bottom of this page.
DataContext
User interface elements in WPF have a DataContext dependency property. That property
has the aforementioned "value inheritance" feature enabled, so if you set the
DataContext on an element to a Foo object, the DataContext property on all of its
logical descendant elements will reference that Foo object too. This means that all data
bindings contained within that root element's element tree will automatically bind against
the Foo object, unless explicitly told to bind against something else. This feature is
extremely useful, as we will soon see.
The Binding class
Data binding in WPF revolves around the Binding class. Binding is the sun of the data
binding solar system, so to speak.
That class has a fairly simple and intuitive API, but it is very powerful. The WPF Horse
Race demo application does not nearly use all of the features of the Binding class, but it
does make use of some common ones. Let's take a look at the Binding members we will
see in action later on in this article.




Source - references a Binding's data source. By default this object references the
element's DataContext value, which might be inherited from an ancestor
element's DataContext, as discussed in the previous section. If you set this
property to a non-null value, then the data binding operation will treat that value
as the place where data is pushed to and pulled from.
Path - is used to indicate from which property on the source object to get and set
the bound data value. It is a property of type PropertyPath, which allows it to
support a complex range of path expressions.
ElementName - can be used as an alternative to the Source property described
earlier. It allows you to specify the name of an element to use as a data source.
This can be useful when binding a property on one element to a property on
another element, particularly when the binding is declared in XAML.
Converter - of type IValueConverter. You can set this property to an instance of a
class which implements that interface to intercept any movement of data from the
binding source to the binding target, or vice versa. Value converters are very
convenient and are used quite often.
How the WPF Horse Race uses data binding
Now that we have a general idea of how data binding is structured, it's about time to see
how the WPF Horse Race demo application uses it. We won't examine each place that
data binding is used in the application; some of them are better saved for a later article in
this series. All of the data binding logic in the application is expressed in XAML, but
keep in mind that it is entirely possible to do all of this in the code-behind as well.
Displaying a racehorse's name
Open the RaceHorseDataTemplate.xaml file, which contains the DataTemplate for the
RaceHorse class (if you don't know what a DataTemplate is yet, don't worry, the next
article in this series covers that topic). In that file there is a TextBlock named
'horseName' which is bound to the Name property of a RaceHorse object. Here's an
abridged version of the XAML I'm referring to:
Collapse
<TextBlock x:Name="horseName" Text="{Binding Name}" />
When a RaceHorse named 'Sweet Fate' is displayed in the UI, that TextBlock displays
its name as seen below:
Notice that the binding statement in the XAML snippet above does not explicitly mention
that it is setting the Binding's Path property. Markup extensions, such as Binding, can
have one property which acts as the "default" property when being set in XAML.
Binding's default property is Path. If you are not in favor of using implicit notation like
that, the XAML seen below is equivalent to the previous snippet:
Collapse
<TextBlock x:Name="horseName" Text="{Binding Path=Name}" />
Rotating the race track
In the main Window's XAML file (Window1.xaml) there is a Slider control which
affects the rotation angle of the "race track" ItemsControl.
The ItemsControl has its LayoutTransform property set to a RotateTransform
resource, so the Slider must bind to that RotateTransform in order to affect the
rotation of the ItemsControl. The result of this binding is that when the user moves the
thumb in the Slider, the "race track" rotates, like this:
The following XAML is a skeletal outline of the Window, showing only the markup
relevant to this particular binding:
Collapse
<Window>
<Grid>
<Grid.RowDefinitions>
<!-- The top row is for the race track. -->
<RowDefinition Height="*" />
<!-- The bottom row is for the command strip. -->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.Resources>
<RotateTransform x:Key="trans" Angle="0" />
</Grid.Resources>
<!-- The 'Race Track' area. -->
<ItemsControl LayoutTransform="{StaticResource trans}" />
<!-- The 'Command Strip' area -->
<Border Grid.Row="1">
<Slider Value="{Binding Source={StaticResource trans},
Path=Angle}" />
</Border>
</Grid>
</Window>
Displaying the angle of rotation
Directly beneath the Slider in the main Window's XAML is a TextBlock whose Text
property is bound to the Slider's Value property. The purpose of this TextBlock is to
show the angle of rotation applied to the race track. Since this should display a userfriendly number, and the Slider's Value property is a double, a value converter is used
to remove all of the decimal places (basically it casts the double to an int). Here's how
that works:
Collapse
<StackPanel>
<StackPanel.Resources>
<local:DoubleToIntegerConverter x:Key="conv" />
</StackPanel.Resources>
...
<Slider x:Name="rotationSlider" />
<TextBlock Text="{Binding
ElementName=rotationSlider,
Path=Value,
Converter={StaticResource conv}}"
/>
...
</StackPanel>
That binding uses the ElementName property to refer to the Slider element. It also uses a
custom value converter to convert the Value property of the Slider to an integer. Below
is the code for the value converter:
Collapse
public class DoubleToIntegerConverter : IValueConverter
{
public object Convert(
object value, Type targetType,
object parameter, CultureInfo culture )
{
return (int)(double)value;
}
public object ConvertBack(
object value, Type targetType,
object parameter, CultureInfo culture )
{
throw new NotSupportedException( "Cannot convert back" );
}
}
Calculating the width of a racehorse's progress indicator
As a racehorse "runs the race" it is followed by a green progress indicator. That
indicator's width represents what percent of the race the horse has completed as seen
below:
Determining the width of the progress indicator requires two pieces of information: the
percent of the race completed by the racehorse, and the total distance which the racehorse
must travel to reach the finish line. Based on what we have seen of data binding in WPF
so far, this seems like a problem. How can you bind the width of an element to a value
whose calculation requires two numbers? That's where the MultiBinding and
IMultiValueConverter types come into play.
Here's an approximation of how that works:
Collapse
<Border x:Name="racePit">
<Grid>
<StackPanel>
<StackPanel.Resources>
<local:RaceHorseProgressIndicatorWidthConverter
x:Key="WidthConv" />
</StackPanel.Resources>
<!-- This Rectangle "follows" a horse as it runs the race. -->
<Rectangle x:Name="progressIndicator"
Fill="{StaticResource RaceInProgressBrush}"
>
<!-- The progress indicator width is calculated by an instance
of the RaceHorseProgressIndicatorWidthConverter class. -->
<Rectangle.Width>
<MultiBinding Converter="{StaticResource WidthConv}">
<Binding Path="PercentComplete" />
<Binding ElementName="racePit" Path="ActualWidth" />
</MultiBinding>
</Rectangle.Width>
</Rectangle>
</StackPanel>
</Grid>
</Border>
The multi-value converter used to calculate the progress indicator's width is seen below:
Collapse
public class RaceHorseProgressIndicatorWidthConverter :
IMultiValueConverter
{
public object Convert(
object[] values, Type targetType,
object parameter, CultureInfo culture )
{
int percentComplete = (int)values[0];
double availableWidth = (double)values[1];
return availableWidth * (percentComplete / 100.0);
}
public object[] ConvertBack(
object value, Type[] targetTypes,
object parameter, CultureInfo culture )
{
throw new NotSupportedException( "Cannot convert back" );
}
}
External links
Dependency properties


Dependency Properties Overview
Property Value Inheritance
Data binding




Data Binding Overview � highly recommended
Binding Sources Overview
Binding Class
Piping Value Converters in WPF
History

April 3, 2007 � Created the article.
Part 4 - Data Templates and Triggers
Introduction
This is the fourth article in an introductory series about the Windows Presentation
Foundation. In the previous article we examined data binding and how it is used to
display information in WPF user interfaces. In this article we examine data templating
and triggers, and how they are used in the WPF Horse Race demo application (which is
available for download at the top of the first article in this series).
Just like the other articles in this series, this article does not cover its subject matter in
exhaustive detail. Instead we will examine just enough of the basics so that we can see
how those features are put to use in the demo app. If you want to learn more about how
data templating and triggers can be used, refer to the External links section for additional
information.
Background
So far in this series of articles, we have seen how XAML, layout panels, and data binding
are used to create user interfaces that display simple data. Those fundamental building
blocks, however, serve as the foundation upon which more powerful and compelling
features of WPF depend.
One of those higher-level features allows you to easily describe how an object of any
type can be rendered. In other words, it enables you to create a template, in XAML, of
visual elements which can be "expanded" at runtime to render a data object. Of course,
the feature I'm referring to is known as "data templating".
Data templates are only one type of template in WPF. This article, in fact this entire
series of articles, will not examine the other types of templates because the WPF Horse
Race demo application does not use them. In case you are interested, there are also
control templates, items panel templates, and hierarchical data templates. Refer to the
Other templates sub-section in the External links section toward the bottom of this article
for links to information about them.
Another feature of WPF covered in this article is called "triggers". Triggers are another
fundamental building block in WPF, upon which many parts of the framework depend.
They are, in general, a means of conditionally applying values to properties. Triggers are
especially useful when you are writing XAML because they provide a means of
evaluating properties at runtime and taking certain actions based on their values. In that
sense triggers are a gray area somewhere between the declarative world of XAML
markup and the imperative world of the code-behind.
The DataTemplate class
All of WPF's templating functionality is based on the FrameworkTemplate class.
DataTemplate derives from FrameworkTemplate, as do all other classes used for
templating purposes. This familial relationship between the templating classes is largely
irrelevant for typical WPF development scenarios because in practice, you will create
templates in XAML, and will not use them in a polymorphic manner.
It is far more straightforward and expedient to create a DataTemplate in XAML than it is
to create one programmatically. The code for creating templates is actually very
cumbersome, while the XAML for creating the same template is clean and simple. It is
technically possible to construct data templates programmatically, but even Microsoft
does not recommend it.
Without a DataTemplate
Before we dive into the data template used in the WPF Horse Race application let's see
how a simple example works. First take a look at a simple class which represents a river:
Collapse
namespace WPF_Test
{
public class River
{
string name;
int milesLong;
public string Name
{
get { return name; }
set { name = value; }
}
public int MilesLong
{
get { return milesLong; }
set { milesLong = value; }
}
}
}
If we were to display an instance of this class in a ContentControl, the XAML would be
something like this:
Collapse
<StackPanel>
<StackPanel.Resources>
<local:River x:Key="theRiver" Name="Colorado River"
MilesLong="1450" />
</StackPanel.Resources>
<ContentControl Content="{StaticResource theRiver }" />
</StackPanel>
The UI created by that XAML looks like the following:
That certainly isn't too impressive. What you see above is what gets displayed as a result
of calling ToString() on the River object referenced by the ContentControl. Let's take
a moment to examine what is going on here.
The example above creates an instance of the River class, which represents the Colorado
River. It also creates a ContentControl which is told to display that River object
somehow. The ContentControl examines the River object and tries to figure out how to
render it, but since River does not derive from UIElement it has no way of knowing how
to do so. Once ContentControl is devoid of options, it ends up calling ToString() on
the River object and then displays that text.
With a DataTemplate
Now that we've seen how boring a River object looks in the absence of data templates,
it's time to add one into the mix. Here's the same XAML used before, only this time there
is a template for the River class:
Collapse
<StackPanel>
<StackPanel.Resources>
<DataTemplate DataType="{x:Type local:River}">
<Border BorderBrush="Blue" BorderThickness="3" CornerRadius="12">
<Grid Margin="4">
<TextBlock>
<Run Text="The"/>
<TextBlock Text="{Binding Name}"/>
<Run Text="is"/>
<TextBlock Text="{Binding MilesLong}" />
<Run Text="miles long." />
</TextBlock>
</Grid>
</Border>
</DataTemplate>
<local:River x:Key="theRiver" Name="Colorado River"
MilesLong="1450" />
</StackPanel.Resources>
<ContentControl Content="{StaticResource theRiver}" />
</StackPanel>
This is how that XAML renders:
The River object is displayed much more intelligently when a data template is applied.
The information held within it is now displayed as part of a sentence, and that sentence is
wrapped in a curved blue border. Keep in mind that the rendition of a River object seen
above is completely arbitrary. It can be displayed in whatever way is considered
appropriate for the application in which it exists.
Triggers
Another feature of WPF often used in conjunction with templates is known as "triggers".
In general a trigger is somewhat like an if block in procedural code; it only executes
what it contains when some condition evaluates to true.
For example, here is a modified version of the XAML seen in the previous section. This
time the DataTemplate has a Trigger in it which sets the Border's Background property
to 'LightGray' and the TextBlock's Foreground property to 'Red' when the mouse cursor
is anywhere over the Border.
Collapse
<StackPanel>
<StackPanel.Resources>
<DataTemplate DataType="{x:Type local:River}">
<Border x:Name="bdr"
BorderBrush="Blue" BorderThickness="3" CornerRadius="12"
>
<Grid Margin="4">
<TextBlock x:Name="txt">
<Run Text="The"/>
<TextBlock Text="{Binding Name}"/>
<Run Text="is"/>
<TextBlock Text="{Binding MilesLong}" />
<Run Text="miles long." />
</TextBlock>
</Grid>
</Border>
<DataTemplate.Triggers>
<Trigger SourceName="bdr" Property="IsMouseOver" Value="True">
<Setter TargetName="bdr" Property="Background"
Value="LightGray"/>
<Setter TargetName="txt" Property="Foreground" Value="Red"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
<local:River x:Key="theRiver" Name="Colorado River"
MilesLong="1450" />
</StackPanel.Resources>
<ContentControl Content="{StaticResource theRiver}" />
</StackPanel>
When that XAML is used, the UI looks like this when the cursor is over the River
visuals:
When the cursor is moved away from the River visuals, the background and foreground
colors automatically revert to their previous values. Triggers handle all of that
functionality for us, as seen below:
There are several kinds of triggers, each has a different way of determining when it
should execute. In the remainder of this article, we will only focus on the two types of
triggers used in the WPF Horse Race demo application: DataTrigger and
MultiDataTrigger.
Triggers can be used in other places outside of templates, such as within Styles and on
any FrameworkElement subclass, but this article only shows them being used within a
DataTemplate. For more information about triggers, refer to the Triggers sub-section of
the External links section toward the bottom of this article.
How the WPF Horse Race uses Data Templates and
Triggers
The WPF Horse Race demo application has one custom DataTemplate, which is used to
render instances of the RaceHorse class. The template declaration can be found in the
RaceHorseDataTemplate.xaml file. The full template XAML is not shown in this article
because it is rather lengthy and would serve no purpose being shown in its entirety.
Instead we will examine it piecemeal.
The Visuals for a RaceHorse
The visuals elements in the RaceHorse data template can be distilled down to this basic
structure:
Collapse
<Border>
<Grid>
<StackPanel Orientation="Horizontal">
<!-- This Rectangle is the "progress indicator"
which follows the horse. -->
<Rectangle />
<!--This Image displays the picture of a race horse. -->
<Image />
</StackPanel>
<!-- Displays the horse's name. -->
<TextBlock />
</Grid>
</Border>
Here is a visual explanation for how the elements in the template correspond to what you
see as a RaceHorse runs a race:
(Note: The yellow rectangle in the image above, which represents the StackPanel, was
added just for the sake of clarity. It does not actually appear on screen when the
application is running.)
The root Border element defines the boundaries of the RaceHorse's "race pit" (the area in
which it runs). The Border contains a Grid panel, which, in turn, contains a StackPanel
and a TextBlock. The StackPanel holds the RaceHorse's progress indicator and a
picture of a horse. A StackPanel was used to hold those two elements so that as the
Rectangle (i.e. the progress indicator) becomes wider, the Image will be moved to the
right along with it.
The TextBlock displays the RaceHorse's name. Since the TextBlock is declared beneath
the StackPanel (lexically), it will be rendered on top of the StackPanel, with respect to
the z-order. This ensures that the horse's name is always visible.
Setting properties on the winner of a race
After the template's visuals are declared, there are also some trigger declarations. One of
them is a DataTrigger. A DataTrigger executes its contents when the supplied Binding
returns a specific value. Here is the XAML for that trigger:
Collapse
<!-- Set special values for the winner of the race. -->
<DataTrigger Binding="{Binding IsWinner}" Value="True">
<Setter TargetName="progressIndicator"
Property="Fill" Value="{StaticResource WinnerBrush}" />
<Setter TargetName="horseName"
Property="Foreground" Value="Black" />
<Setter TargetName="horseName"
Property="FontWeight" Value="Bold" />
<Setter TargetName="horseName"
Property="HorizontalAlignment" Value="Center" />
</DataTrigger>
That DataTrigger waits for the templated RaceHorse's IsWinner property to change
from false to true. When that happens (i.e. the RaceHorse wins a race) all Setters
contained within it are executed.
The Setter class provides a way to assign a value to a property. It is especially convenient
when used in XAML, but can be used in the code-behind as well. When a RaceHorse
wins a race the Setters modify the template's visuals, so that the progress indicator is
rendered with a golden brush and its name stands out visually.
In the screenshot below, the RaceHorse named 'Fresh Spice' won the race:
When the next race starts and Fresh Spice's IsWinner property is set back to false, the
properties affected by that trigger will automatically be reverted back to their previous
values.
In case you are curious as to how the DataTrigger knows when the IsWinner property
value changes, the answer lies in the fact that RaceHorse implements
INotifyPropertyChanged. A RaceHorse object's PropertyChanged event is raised when
its IsWinner property value changes. The Binding class is aware of the
INotifyPropertyChanged interface and is notified when the PropertyChanged event
has been raised for the IsWinner property.
Fading Away Losing RaceHorses
When a RaceHorse completes a race but does not finish first, it turns red and then fades
away. By "fades away," I mean that the Opacity property of the RaceHorse's Border
element is animated down to 60% over three quarters of a second. This article does not
cover WPF's support for animation, but it is important to know that animations can be
started in response to a trigger being executed.
In order to change the color of and fade away a RaceHorse which lost a race, a trigger
must know two pieces of information: whether the RaceHorse finished the race yet, and
if it lost the race. This type of "AND" expression can be implemented with a
MultiDataTrigger, as seen below:
Collapse
<!-- This MultiDataTrigger affects losers of the race. -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFinished}" Value="True" />
<Condition Binding="{Binding IsWinner}" Value="False" />
</MultiDataTrigger.Conditions>
<!-- Apply the "finished the race" brush to
the horse's progress indicator. -->
<Setter TargetName="progressIndicator"
Property="Fill" Value="{StaticResource FinishedBrush}" />
<!-- Fade the race pit in and out if the horse lost the race. -->
<MultiDataTrigger.EnterActions>
<!-- Fade away the RaceHorse's Border element when it loses a race.
-->
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<!-- Fade in the RaceHorse's Border element when a new race starts.
-->
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
External links
DataTemplates




Data Templating Overview
Styling and Templating
DataTemplate Class
FrameworkTemplate Class
Triggers




Trigger Class
DataTrigger Class
MultiDataTrigger Class
EventTrigger Class
Other templates



ControlTemplate Class
ItemsPanelTemplate Class
HierarchicalDataTemplate Class
History

April 6, 2007 – Created the article.
License
This article, along with any associated source code and files, is licensed under The Code
Project Open License (CPOL)
Part 5 - Styles
Introduction
This is the fifth article in an introductory series about the Windows Presentation
Foundation. In the previous article we examined data templates and triggers to see how
the rendering of data objects can be described in XAML. In this article we examine
styles, and how they are used in the WPF Horse Race demo application (which is
available for download at the top of the first article in this series).
Just like the other articles in this series, this article does not cover its subject matter in
exhaustive detail. Instead we will examine just enough of the basics so that we can see
how those features are put to use in the demo app. If you want to learn more about how
styles can be used, refer to the External links section for additional information.
Background
WPF borrows many concepts from both the Web and desktop programming worlds. The
separation of user interface layout from behavior (i.e. XAML vs. code-behind) is a tip of
the hat to ASP.NET. The extensive set of event-driven APIs for detailed control over user
interaction is reminiscent of the Windows Forms programming model. WPF can be
thought of as a distillation of the best features found in various UI platforms, plus a wide
range of new features as well.
One major contribution made to WPF by the world of Web development was the concept
of styles. Styling in WPF is somewhat similar to how Cascading Style Sheets (CSS) is
used by Web developers. The basic idea of styling in WPF is that you can define a Style
object which is applied to a specific type of element in the UI, such as a Button or
TextBox. That style can be applied to every instance of the element type, or you can
selectively apply it to certain instances. Styling in WPF really just boils down to a
convenient way to apply property settings to one or more visual elements.
Style vs. theme
Before we start looking into styles, it is important to draw a distinction between styles
and themes. Operating systems have "themes", WPF applications have "styles". Themes
are an OS-level concept: such as the blue, green, and silver themes seen in Windows XP,
or the Aero theme in Vista. WPF applications automatically detect the current OS theme
and use that color scheme wherever possible, and can even programmatically choose
which theme to use.
A style exists only within one WPF application, or just one Window in a WPF
application, or even just one portion of a Window for that matter. Styles cannot be
applied to anything outside of the WPF application in which they exist, nor can they be
applied to any WinForms controls hosted within a WPF Window.
For more information about theme support in WPF, refer to the External links section at
the end of this article.
The Style class
The entire styling infrastructure in WPF is based on the Style class. It has a relatively
small set of public members, which makes it easy to grasp how styling works. Instances
of the Style class are almost always created in XAML, but it is possible to create them in
code if necessary.
Here are some of the properties of Style we will see in use later:

Resources � is a ResourceDictionary where you can put objects used only
within the Style, such as brushes, value converters, etc.

Setters � a collection of Setter and EventSetter objects that apply values to
properties, or assign handlers to events. This is the content property of the Style
class, which makes it very easy to use in XAML.

TargetType � indicates on what type of element the Style will be applied, such
as TreeView or Button.
The styles created in the WPF Horse Race demo application are very simple. There are
other common properties of the Style class which they do not use, such as:

BasedOn � allows for style inheritance. You can derive one Style from another
Style to create customizations without duplicating the core Style's XAML.

Triggers � just like in the DataTemplates seen in the previous article in this
series, this property contains a collection of triggers that can be used to
conditionally apply values to properties.
A Style can be applied to any object which derives from FrameworkElement or
FrameworkContentElement, both of which expose a public property named Style.
Without styles
If WPF did not provide a way to stylize elements, you would have to be out of your mind
to even consider creating a distinctive, branded look and feel for an application's user
interface. To create a visual consistency across all Windows in an application would
quickly turn into a maintenance nightmare, especially once changes needed to be made to
certain aspects of the visual style.
For example, consider the following XAML:
Collapse
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel>
<TextBlock Background="Tan" Margin="2,4">Bike</TextBlock>
<TextBlock Background="Tan" Margin="2,4">Car</TextBlock>
<TextBlock Background="Tan" Margin="2,4">Truck</TextBlock>
</StackPanel>
</Border>
That simple markup results in something which looks like this:
Suppose that the application we are building used to have a rule that TextBlocks must
always have a tan background color, but one day a big wig at our imaginary company
decides that tan is no longer a good color. Instead, now all TextBlocks should have a
light gray background color.
If our application only had the three TextBlocks seen in the snippet above, that wouldn't
be too much trouble. All we would have to do is update those three property settings:
Collapse
<Border BorderBrush="Black" BorderThickness="2" Margin="10">
<StackPanel>
<TextBlock Background="LightGray" Margin="2,4">Bike</TextBlock>
<TextBlock Background="LightGray" Margin="2,4">Car</TextBlock>
<TextBlock Background="LightGray" Margin="2,4">Truck</TextBlock>
</StackPanel>
</Border>
But if our application happens to contain hundreds or even thousands of TextBlocks,
then we are in trouble. Suddenly we would be wishing that there was an easy way to set
every TextBlock's Background property to light gray.
With styles
Fortunately there is an easy way to set properties on any number of elements: "styles".
Let's see how a Style can be used to solve the problem described in the previous section.
Here's one way to do it:
Collapse
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel>
<StackPanel.Resources>
<Style x:Key="TxtBlkStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="Margin" Value="2,4" />
</Style>
</StackPanel.Resources>
<TextBlock Style="{StaticResource TxtBlkStyle}">Bike</TextBlock>
<TextBlock Style="{StaticResource TxtBlkStyle}">Car</TextBlock>
<TextBlock Style="{StaticResource TxtBlkStyle}">Truck</TextBlock>
</StackPanel>
</Border>
The XAML seen above creates a Style in the StackPanel's Resources collection. That
Style targets elements of type TextBlock, and sets their Background and Margin
properties. Notice that the Style has a key, 'TxtBlkStyle'. The Style property on each
TextBlock is then explicitly set to reference that Style. It results in the following UI:
It turns out that there's an even easier way to accomplish this task. If you do not give a
Style a key, then it will automatically be applied to all elements whose type matches the
TargetType of the Style. Here's an example:
Collapse
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="Margin" Value="2,4" />
</Style>
</StackPanel.Resources>
<TextBlock>Bike</TextBlock>
<TextBlock>Car</TextBlock>
<TextBlock>Truck</TextBlock>
</StackPanel>
</Border>
The solutions seen so far do not really solve the overall problem. If we want the entire
application to contain light gray TextBlocks, then we need to move our Style to a
higher place in the resource tree, such as the Application's resources (read more about
that here and here). This way, all TextBlocks in the app will be able to use the Style we
created. Here is an example of this technique:
Collapse
<!-- App.xaml -->
<Application x:Class="WPF_Test.MyApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml"
>
<Application.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="Margin" Value="2,4" />
</Style>
</Application.Resources>
</Application>
<!--Somewhere inside of SomeWindow.xaml -->
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel>
<TextBlock>Bike</TextBlock>
<TextBlock>Car</TextBlock>
<TextBlock>Truck</TextBlock>
</StackPanel>
</Border>
Since the Application's resource collection is the most visible place to put a Style, all
TextBlocks in every Window in the app will use that Style. It is possible to override
that Style in any Window or anywhere in a Window's element tree, but by default that
Style will be used by all TextBlocks in the application.
How the WPF Horse Race uses styles
The WPF Horse Race demo application creates two Styles. One of them is used to affect
the Border element in the data template for the RaceHorse class (as seen in the previous
article in this series). The other provides common values for properties of TextBlocks in
the application's "Command Strip" area, toward the bottom of the Window.
Race pit border style
If the RaceHorse data template's root Border element did not have a Style applied to it,
the UI would look like this:
With the Style applied, it looks like this:
In the RacePitBorderStyle.xaml file, you will find a ResourceDictionary which
contains a Style whose key is 'RacePitBorderStyle'. That file contains the following
XAML:
Collapse
<!-- This resource dictionary contains the Style applied to
each horse's race pit. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Style x:Key="RacePitBorderStyle" TargetType="Border">
<Style.Resources>
<LinearGradientBrush x:Key="BackBrush"
StartPoint="0.5,0" EndPoint="0.5,1"
>
<GradientStop Color="#88000000" Offset="0.1" />
<GradientStop Color="#CC000000" Offset="0.9" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="BorderBrush"
StartPoint="0.5,0" EndPoint="0.5,1"
>
<GradientStop Color="#18000000" Offset="0.1" />
<GradientStop Color="#08000000" Offset="0.9" />
</LinearGradientBrush>
</Style.Resources>
<Setter Property="Background" Value="{StaticResource BackBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource
BorderBrush}"/>
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Margin" Value="2,4" />
</Style>
</ResourceDictionary>
That Style is applied to the root Border element in the RaceHorse data template, as seen
below:
Collapse
<!-- RaceHorseDataTemplate.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfHorseRace"
>
<!-- Import the resource dictionary which contains the Style
applied to Border of each horse's "pit". -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="RacePitBorderStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type local:RaceHorse}">
<Border x:Name="racePit" Style="{StaticResource
RacePitBorderStyle}">
<!-- Other elements omitted for clarity. -->
</Border>
</DataTemplate>
</ResourceDictionary>
Command strip text style
The other Style used in the demo application is implicitly applied to its target elements.
This Style can be seen in Window1.xaml, in the "Command Strip" section of the XAML.
Collapse
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<!-- This Style is applied to all TextBlock
elements in the command strip area. -->
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="#EE000000" />
</Style>
</StackPanel.Resources>
<TextBlock>Rotation: </TextBlock>
<Slider ... />
<TextBlock ... />
<TextBlock> degrees</TextBlock>
</StackPanel>
Notice that this Style's TargetType is set to "TextBlock". When the TargetType is set
to a type that is part of the WPF framework, you do not have to use the more
cumbersome {x:Type TypeName} syntax seen previously in this article. Since the Style
seen above does not have a key, it is automatically applied to all TextBlock elements in
the StackPanel.
External links
Styles and themes

Default Templates in WPF � highly recommended
Styles





Style Class
Styling and Templating
Introduction to Styling and Templating Sample
Nick's Twisted Perspectives on Styles and Templates
Styles and Triggers in WPF
Themes




Create and apply custom themes
Theme support in Windows Presentation Foundation
Vista look on Non-Aero themes
Microsoft.Windows.Themes Namespace
History

April 12, 2007 - Created the article.
License
This article, along with any associated source code and files, is licensed under The Code
Project Open License (CPOL)
Download