Managing layout in a Windows Presentation Foundation application

This article was originally published in VSJ, which is now part of Developer Fusion.
You might be wondering when I’m going to get on to the fancy animation, media and 3D support that WPF provides.

Well, it’s not going to be in this article, because for many business applications these will be very much secondary considerations. Instead, I’m going to focus on how you can lay out your user interfaces so that they will work on pretty much any device, at any size, without problems. In other words, it’s time to dive in and look at how to design a WPF user interface.

A sidelong glance at layout in Windows Forms

If you have been working with Windows Forms for the past few years, you will be familiar with its limited layout facilities. Anchoring and docking is fine up to a point, but real problems can occur when it comes to localisation, scaling, font changes and high-resolution devices. To a certain extent, ASP.NET applications avoid many of the problems with their tabular-based layouts and use of CSS.

Windows Forms 2.0 goes some way to alleviate complex design problems with the provision of the TableLayoutPanel and FlowLayout control panels. TableLayoutPanel in particular is excellent for generating scalable UI with built-in support for right-to-left reading order as well, and is something that I would recommend that you take a look at. But whilst Windows Forms is great, let’s move on to how we design UI using WPF.

The user interface we’ll be modelling

I’ve chosen to simulate the standard Windows XP search companion from Explorer in order to explain the layout facilities in WPF. Just as a reminder, you can see what we’re looking to reproduce in Figure 1.

Figure 1
Figure 1: Windows XP search companion

If you fire up this search facility and play with it, you’ll notice that it responds sensibly when you resize the window or use the splitter to adjust the size of the selection criteria panel on the left. It also needs to be able to cope sensibly when the user changes their default font size.

So, without further ado, let’s dive into layout management using WPF panels.

Panels

At the heart of the layout engine in WPF are panels. Visual elements are placed within panels, and they then interact with the panel to control their size and position. The core panel types are:
  • Canvas – A canvas can be used to explicitly position an element using absolute coordinates and sizes. This control is generally not good for resizable UIs, and should typically be avoided, so I won’t be talking about it in this article.
  • StackPanel – As its name suggests, a StackPanel simply stacks controls up either vertically or horizontally. Truncation occurs if there is insufficient space in the StackPanel for all of the elements.
  • DockPanel – A DockPanel can be used to dock elements to its edges, in much the same way that the Dock property is used in Windows Forms. DockPanels are excellent hosts for the “chrome” elements in the UI, such as Toolbars, Menus and StatusBars. Elements with a DockPanel can also be sized to fill all remaining space within the control.
  • Grid – The Grid is a tabular control, consisting of columns and rows. Elements can be positioned into specific cells within the Grid, and can then be aligned and sized accordingly. Elements can also span multiple columns and rows in much the same way that table cells can in HTML tables.
DockPanel, StackPanel and Grid will all be used in the application, as shown in Figure 2, which identifies the hierarchy of panels used to create the layout.

Figure 2
Figure 2: Dissecting the UI into panels

Let’s briefly examine the rationale between some of the choices that I made when choosing the panels for the application.

To start with, the top level panel is a DockPanel, because I wanted the menu (and toolbar, if present) to be docked to the top of the window. The main content area of the window is contained in a single Grid. The reason for this is that you can use a GridSplitter object to separate columns and rows in a Grid, offering the flexible resizing options that users’ expect with these types of application. The left-hand panel, containing all the controls that allow the user to control the search, also uses a DockPanel. At the top of this DockPanel is another Grid to control the layout of the caption and its close button. Beneath that comes a StackPanel that contains all the Labels, Buttons and the Expander controls that hide the more advanced options. Finally, the Back and Search buttons are contained in another StackPanel, but this time with a horizontal orientation.

Figure 3 shows what (the slightly modified, for reasons that will become clear later) version of the application looks like.

Figure 3
Figure 3: The initial WPF version

Examining the XAML layout code

The skeleton for the code (with all the interactive controls omitted), is shown in Listing 1, below.

Listing 1: The skeleton XAML for the UI

<Window x:Class=”MyBrowser.Window1”
	xmlns=”http://schemas.microsoft.com/winfx/avalon/2005”
	xmlns:x=”http://schemas.microsoft.com/winfx/xaml/2005”
>

	<DockPanel LastChildFill=”True”>

		<!- The Toolbar ->
		<ToolBarTray DockPanel.Dock=”Top” Margin=”2”>
			...
		</ToolBarTray>

		<Grid x:Name=”mainViews”>
			<Grid.ColumnDefinitions>
				<ColumnDefinition Width=”*” />
				<ColumnDefinition Width=”2*” />
			</Grid.ColumnDefinitions>

			<Grid Grid.Column=”0”> <!- Left hand panel ->
				<DockPanel LastChildFill=”True”>
					<Grid DockPanel.Dock=”Top”
					Background=”#F0F0F0”>
						... <!- Caption and close button ->
					</Grid>
					<!- Grid to hold the search controls, makes
						it easy to do the light blue box with
						white border, which is the drawn at the
						top of the grid ->
					<Grid Margin=”10,10,15,10”>
						<Border ... >
							<Rectangle ... />
						</Border>

						<StackPanel ... >
								<!- The controls ->
						</StackPanel>
					</Grid>
				</DockPanel>
			</Grid>

			<!- The splitter control ->
			<GridSplitter />

			<!- The list of results (faked!) ->
			<ListView Grid.Column=”1” x:Name=”listResults”>

			</ListView>
		</Grid>
	</DockPanel>
</Window>
I’ve highlighted some items of particular interest in this code:
  1. To begin with, the top level Window element indicates that this is a windowed WPF application, and therefore must be installed on the client machine (rather than running as an Express application in the browser).
  2. The DockPanel then assumes responsibility for the entire client area of the Window. Note that a Window can only have a single child element, in common with all elements that are derived from the ContentControl type. By setting the LastChildFill attribute to True, the DockPanel will automatically resize the last element fill the remaining space once all of the other child elements are docked.
  3. The ToolBarArray element is docked to the top of the DockPanel by setting the DockPanel.Dock property to Top (unsurprisingly). In much the same way that extender controls in Windows Forms, parent XAML elements can add attributes to their children. In WPF these are known as attached properties.
  4. The main Grid that encompasses all the remainder of the DockPanel consists of two columns. In this case, the relative widths of the columns are specified using the Width attributes of * and 2* respectively, to provide proportional spacing. The Grid will thus give the left-hand column half as much real estate as that of the right. We could have used 5* and 10*, 1.75* and 3.5*, or any other pair of values that come out to a 1:2 ratio to achieve the same effect. Of course, you can provide specific Width values, or you can use the value of “Auto” to indicate that the column should resize itself to its content. Grids allocate space to the fixed or Auto columns and rows first, before dividing up the remainder of the space to the proportional items.
  5. When using Grids, the child elements should specify the cell that they want to occupy using Grid.Column and Grid.Row. These values both default to 0 if you don’t provide them.
  6. One thing that we might want to do is mix graphic elements with our layout elements. I used a rectangle to provide the light blue background for the controls, with the white border. These graphics elements don’t affect the layout management of controls. However, this does introduce the concept of z-ordering. Elements that appear in the tree first are rendered first by the compositing engine in WPF. This means that they will appear behind the following elements, which is why the rectangle provides a nice background to the controls, rather than obscuring them.
  7. Finally, remember that we wanted a splitter to lie between the controls on the left-hand side and the search results on the right. This is trivially introduced through the use of a GridSplitter element. It is important to note that the GridSplitter can be positioned anywhere within the Grid element. The layout manager doesn’t use the order of the element in the mark-up to decide which rows or columns it affects. Instead the values of its attached Grid.Column or Grid.Row properties, in conjunction with Grid.ColumnSpan and Grid.RowSpan, determine its placement.

Unleashing the power of the layout system

Up to now you’re possibly thinking that all this isn’t very magical; but this brings me on to the somewhat strange toolbar for the application, as shown in Figure 3. You will notice that there is a slider (which is exciting enough in a toolbar in Windows Forms, but is mundane in WPF) to control the zoom factor. Figure 4 shows what happens when we actually zoom into the application.

Figure 4
Figure 4: Zooming into the UI

There are a number of important points here. To start with, there is no pixellation (no “jaggies”) from the zooming in. This is because WPF uses vector, not raster, graphics, and the compositing and rendering of the display to actual pixels occurs after the scaling operation has been performed.

You will also notice that the layout of the controls is still sane. This means that the StackPanel containing the controls is still neatly contained within the Grid on the left-hand side. Of course, the controls are now overflowing the display area of the StackPanel, which means that only the first two TextBoxes are accessible. I’ll show you how you can address this issue with just a couple of lines of XAML code in a moment.

The issues that the zoom feature highlights

Of course, I’m predicting that most UIs won’t have a zoom feature like this. However, let’s think about the issues that this zooming feature is designed to highlight. To start with, resolutions on modern PCs vary from 640 x 480 (very rare) all the way up to 1920 x 1440 and beyond. This can cause a problem when laying out dialogs based on pixels, not dialog units. Users can also adjust the standard font size settings, often rendering many UIs unintelligible.

Localisation is also a problem. Words rarely translate into the same screen real estate, which is something that is especially true when translating from, say, English to German. You need a layout technology that doesn’t require you to continually reposition controls as you translate their text.

Finally, the number of dots per inch (DPI) is increasing as screen technology gets more sophisticated. We regularly find screens at not just the standard 96DPI, but also with 150DPI, 200DPI and even more. WPF applications that make use of panels for layout will seamlessly operate on all these devices. This is possible because even when you set an explicit size, the pixel that you work with in WPF is different than in GDI.

So what’s a pixel?

To anyone who has worked with GDI, a pixel represents a single dot on the device. On a 96DPI display, there were 96 pixels to one inch; on a 150DPI display, there are 150.

In WPF, a pixel is always 1Ž96 of an inch big. This number has been chosen because it maps back onto traditional Windows pixels. This means that if you defined a line which is 96 units long in WPF, it will always be one inch long on the device, no matter what the DPI value is of the display.

Implementing the zoom

Whilst not directly relevant to the discussion of laying out UI, you might be wondering how hard it was to add the zoom feature. Given that this is WPF, the official answer would be “super easy”. The following code shows the event handler that deals with the zoom slider:
private void OnZoomChanged(
	object sender, RoutedEventArgs e)
{
	ScaleTransform st =
		new ScaleTransform(
		zoomFactor.Value,
		zoomFactor.Value );
	mainViews.LayoutTransform = st;
}
mainViews is the name of the Grid that is responsible for the area of the window with the exception of the toolbar, and is identified by the x:Name attribute in XAML (see Listing 1 for details).

The ScaleTransform object is applied to the Grid through its LayoutTransform property, resulting in all X and Y values being multiplied by the zoom factor during compositing. I’ll talk a little bit more about LayoutTransform below.

Margins and Padding

For now, though, I want to go back to examining how our interactive controls are laid out. More specifically, I want to look at two important aspects of layout: Margins and Padding.

The Margin represents the spacing around the outside of an element. For example, both the light blue rectangle and the StackPanel containing the controls are contained within a Grid. This element is defined as:

<Grid Margin=”10, 10, 15, 10”>
This simply ensures that the Grid will receive 10 pixels of space around it on the left, top and bottom edges, and 15 pixels of space on the right hand edge. Within this Grid I then place the light blue rectangle and the StackPanel containing the controls.

You can see the Grid and its margins highlighted in red in Figure 5.

Figure 5
Figure 5: Margins around the Grid
When specifying margins, you can omit some of the four values. For example, specifying:

Margin=”8”
…will create a margin of eight pixels on all four edges. Padding, on the other hand, is the reverse of margins, in that the spacing is provided inside the control. Consider the difference between these two buttons:
<Button Margin=”10,20,10,20”>
	World</Button>
<Button Padding=”10,20,10,20”>
	Hello</Button>
The first Button is indicating that it wants spacing outside of the control (the margin around the control), whereas the second is indicating that it wants space inside the control (between the edge of the button and the text within it). The layout manager will take into account the elements’ requests for margins and padding when it comes time to measure the elements to decide how it will position the controls.

The ScrollViewer

One of the problems when zooming the UI (or resizing the Window, or a user changing the font size) is that the controls might not all be able to fit into the StackPanel. Consequently, they won’t be visible to the user, as the StackPanel will simply truncate what it displays. There is a very easy fix to this: we can add scrolling support to the StackPanel.

Probably the easiest way of doing this is to add a ScrollViewer that encapsulates the StackPanel. The ScrollViewer can contain a single child element and will display scroll bars around the child element as needed. Here’s the XAML for it:

<ScrollViewer
	VerticalScrollBarVisibility=”Auto”
	Margin=”0,8,2,8” >
	<StackPanel ...>
		...
	</StackPanel>
</ScrollViewer>
Using the VerticalScrollBarVisibility attribute lets us control the scroll bar so that it is only displayed as necessary. So how does the ScrollViewer know when it needs to display a scroll bar?

There are two phases during the rendering process. During the first phase the tree of elements is walked and they are measured to see how much real estate each element requires. Each element can therefore express a desired amount of space that it needs to occupy. The StackPanel, for example, will indicate that it wants enough space for all of the elements within it. However, the layout manager might identify that there is insufficient space for all of the controls, in which case the ScrollViewer will step in and be ready to display the scroll bar.

The second phase of processing is where the elements are actually arranged. At this point, all the elements know the amount of real estate that they have to play with, and will position their content accordingly. In the case of the StackPanel and ScrollViewer, they work together to ensure that the correct part of the panel is being displayed according to the thumb position of the scroll bar. Figure 6 shows the ScrollViewer in action.

Figure 6
Figure 6: ScrollViewer saves the day

Transforms

One of the interesting features of WPF is its transformation capability. You’ve already seen a ScaleTransform in action, which was used to provide a zoom effect to the UI. There are other types of transformation, including RotateTransform and TranslateTransform. All transformations simply affect the values of X and Y that are presented to the compositing engine, now matter whether they are absolute positions, widths or heights, margins or padding.

There is, however, one subtlety that needs to be addressed when working with transformations: whether they affect just the rendering of an element, or whether they affect the layout of the element and its surrounds. Now in order to demonstrate this, the UI has another couple of sliders in the toolbar: one which rotates the TextBox into which the user can type a phrase to search for, whilst the other rotates the TextBox where they can specify the location to search.

RenderTransform

Figure 7 shows the result of the first rotation.

Figure 7
Figure 7: Applying a RenderTransform

As you can see, the TextBox has been rotated in situ, but the position (and z-order) of the other elements is completely unaffected. This is because I have applied a RenderTransform to the element, as shown by the following code:

private void OnRotatePhraseChanged(
	object sender, RoutedEventArgs e )
{
	RotateTransform rt =
		new RotateTransform( ... );

	// Doesn’t impact the layout of
	// other controls
	txtPhrase.RenderTransform = rt;
}
As can be seen quite clearly, the RenderTransform only affects the rendering of the element.

LayoutTransform

A LayoutTransform, on the other hand, affects how elements are laid out. You’ve already seen one LayoutTransform: the code that applied the ScaleTransform to the mainViews Grid. Figure 8 shows another example of applying a LayoutTransform, but this time to a TextBox.

Figure 8
Figure 8: Applying a LayoutTransform

I’ve highlighted the fact that this time a new bounding box has to be calculated for the rotated control, which affects the following controls in the StackPanel. In comparison with Figure 7, you will notice how the subsequent controls have been pushed further down the StackPanel to make room for the TextBox.

The only difference between the the most recent listing above and the code below is the fact that I used the LayoutTransform property on the element:

private void OnRotateLookInChanged(
	object sender, RoutedEventArgs e )
{
	RotateTransform rt =
		new RotateTransform( ... );

	// Layout transform impacts the
	// layout.
	// The bounding box of the control
	// is generated, and other controls
	// are moved out of the way
	txtLookIn.LayoutTransform = rt;
}
Obviously, you can see how powerful and flexible these transformations are, but I would strongly advise against the use of arbitrary rotations if you want to keep your users happy!

Conclusion

This has been a whistle-stop introduction to the power and the flexibility of WPF’s layout capabilities. Using panels, you can design device-independent UIs that scale and adapt to user preferences and cultural settings with the minimum of effort. This is an important facet of UI design, and a key part of writing Windows Vista-ready applications.

Most importantly, all of the layout code can be expressed in XAML. This means that designers will be able to use tools such as Microsoft’s forthcoming interactive designer, codenamed “Sparkle”, to create this XAML, freeing developers to work on the substantive logic behind the UI.


Dave Wheeler is a Microsoft Certified Trainer and holds the MCSD.NET certification. You can read more about WPF and other .NET technologies on his blog. He is a regular speaker at Bearpark’s annual DevWeek conference in London – the 9th annual event takes place on 20–24 February 2006.

Resources

It’s worth pointing out that an updated set of WinFX components and a new SDK are available on the MSDN Library. There’s a link to the new pre-release build on the main Windows Vista page.

You can also download the source code for the sample application described in this article from the VSJ code bin.

You might also like...

Comments

About the author

Dave Wheeler United Kingdom

Dave Wheeler is a freelance instructor and consultant who specialises in .NET application development. He’s a moderator on Microsoft’s ASP.NET and Silverlight forums and is a regular speaker at ...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“In order to understand recursion, one must first understand recursion.”