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><ButtonDockPanel.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><ImageBrushImageSource="Resources/Background.jpg"Opacity="0.25"/></Grid.Background><Grid.RowDefinitions><!--<span class="code-comment"> The top row is for the race track. --></span><RowDefinitionHeight="*"/><!--<span class="code-comment"> The bottom row is for the command strip. --></span><RowDefinitionHeight="Auto"/></Grid.RowDefinitions><!--<span class="code-comment"> The 'Race Track' area. --></span><ItemsControlGrid.Row="0".../><!--<span class="code-comment"> The 'Command Strip' area --></span><BorderGrid.Row="1"><Grid><StackPanelOrientation="Horizontal"VerticalAlignment="Center"><TextBlockMargin="10,4">Rotation: </TextBlock><Slider.../><TextBlock.../><TextBlock> degrees</TextBlock></StackPanel><TextBlockHorizontalAlignment="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:

<Grid.RowDefinitions><!--<span class="code-comment"> The top row is for the race track. --></span><RowDefinitionHeight="*"/><!--<span class="code-comment"> The bottom row is for the command strip. --></span><RowDefinitionHeight="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:

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:

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:

<TextBlockHorizontalAlignment="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.

I've never used ItemsControl directly like that. Wouldn't a ListBox be better?

No, ItemsControl is better. A ListBox is an ItemsControl that also has the ability to contain selected items and by default it supports scrolling. Since this demo does not need selection or scrolling, there is no benefit in using ListBox.