Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
By Josh Smith | 19 Apr 2007 VS2005C#2.0.NET3.0WindowsDevXAMLWPFBeginner A guided tour of the Windows Presentation Foundation, one feature at a time. 4.57 (103 votes) 1 23 4 5
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.
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:
<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:
<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:
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):
<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 codebehind 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):
public partial class MyWindow : Window { public MyWindow() { InitializeComponent(); } void OnButtonClick( object sender, RoutedEventArgs e ) { MessageBox.Show( "Click me again. That felt good." ); } }
External links
Prerequisites
Microsoft .NET Framework v3.0 Visual Studio "Orcas" March 2007 CTP
XAML
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.
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.
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.
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:
<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.
<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:
</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:
<!-- 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 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.
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 righthand 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
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
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.
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.
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.
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.
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:
<TextBlock x:Name="horseName" Text="{Binding Path=Name}" />
The following XAML is a skeletal outline of the Window, showing only the markup relevant to this particular binding:
<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>
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:
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" ); } }
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:
<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:
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
Data binding
Data Binding Overview ?highly recommended Binding Sources Overview Binding Class Piping Value Converters in WPF
History
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
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.
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.
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:
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:
<StackPanel> <StackPanel.Resources> <local:River x:Key="theRiver" Name="Colorado River" MilesLong="1450" /> </StackPanel.Resources> <ContentControl Content="{StaticResource theRiver }" /> </StackPanel>
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:
<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>
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.
<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.
<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.
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:
<!-- 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.
External links
DataTemplates
Data Templating Overview Styling and Templating DataTemplate Class FrameworkTemplate Class
Triggers
Other templates
History
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
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.
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.
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:
<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>
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:
<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:
<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:
<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:
<!-- 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.
In the RacePitBorderStyle.xaml file, you will find a ResourceDictionary which contains a Style whose key is 'RacePitBorderStyle'. That file contains the following XAML:
<!-- 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:
<!-- 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>
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
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
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)