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.
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:
<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:
<Window>
<Grid>
<Grid.Background>
<ImageBrush ImageSource="Resources/Background.jpg" Opacity="0.25" />
</Grid.Background>
<Grid.RowDefinitions>
-->
<RowDefinition Height="*" />
-->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
-->
<ItemsControl Grid.Row="0" ... />
-->
<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 Grid
s 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:
<Grid.RowDefinitions>
-->
<RowDefinition Height="*" />
-->
<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:
-->
<ItemsControl Grid.Row="0" ... />
-->
<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:
<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 UIElement
s; 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 TextBlock
s, 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 TextBlock
s 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:
<TextBlock HorizontalAlignment="Right" Margin="10,4">
<Hyperlink>Start new race</Hyperlink>
</TextBlock>
Setting the HorizontalAlignment
property to 'Right
' will force the TextBlock
to be "pushed" over to the right-hand side of the Grid
. Setting the Margin
property to "10,4
" is shorthand for saying that it should be at least 10 device-independent pixels (DIPs) away on its left and right edges from any surrounding elements, and at least 4 DIPs away on its top and bottom edges from any surrounding elements.
The net effect of those two property settings is that the Hyperlink
will be displayed on the right-hand side of the Grid
, but with a little space between its right edge and the Grid
's right edge. The concept to take away from this is that panels will perform the "broad strokes" for positioning child elements, but the child elements can perform the "finishing touches" to position themselves exactly where they need to be.
External links
History
- April 2, 2007 - Created the article.