Você está na página 1de 28

A Guided Tour of WPF – Part 1

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 XML-based 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 Copy Code


<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 Copy Code


<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 Copy Code


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 Copy Code


<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 Copy Code


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

A Guided Tour of WPF – Part 2

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 Copy Code


<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 Copy Code


<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 Copy Code


<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 Copy Code


<!-- 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 Copy Code


<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 right-hand 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 Copy Code


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

A Guided Tour of WPF – Part 3

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 Copy Code


<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 Copy Code


<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 Copy Code


<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 user-friendly 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 Copy Code


<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 Copy Code


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 Copy Code


<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 Copy Code
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" );
}
}

A Guided Tour of WPF – Part 4

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 Copy Code


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 Copy Code


<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 Copy Code


<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 Copy Code


<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 Copy Code


<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 Copy Code


<!-- 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 Copy Code


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

A Guided Tour of WPF – Part 5

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 Copy Code


<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 Copy Code


<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 Copy Code


<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 Copy Code


<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 Copy Code


<!-- 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 Copy Code


<!-- 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 Copy Code


<!-- 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 Copy Code


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

Você também pode gostar