I see a lot of questions on the Silverlight.Net forums about control layout. Typically they follow the general pattern “I placed a {insert control} into a {insert container}, but {insert problem} is happening. Can you help?” I thought it would be useful to cover the main types of control containers available in WPF and Silverlight, and catalog the differences in their default behavior with respect to layout.
WPF supports a rich set of control containers derived from the base Panel class. These include Canvas, DockPanel, StackPanel, WrapPanel, and Grid. Silverlight is a little more restricted, owing to its need to keep the runtime install as light as possible, and supports only Canvas, Grid, and StackPanel. In this post I will focus on those because they are common across both platforms. Even just these three are enough to gain a wealth of layout flexibility. Hopefully this post will help you take advantage of the different strengths and weaknesses of each. The best way to follow along with these examples is to use the XamlPad.exe tool included with the Windows SDK 6.0.
The most effective way to illustrate the differences between these container controls is to start with more or less identical definitions for all three so that we can plug them into XamlPad and see how they react. Here is the markup:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel Orientation="Horizontal"> <Grid Background="Gray" Margin="5" Height="200" Width="200"> <Rectangle Fill="Blue" /> </Grid> <Canvas Background="Gray" Margin="5" Height="200" Width="200"> <Rectangle Fill="Blue" /> </Canvas> <StackPanel Background="Gray" Margin="5" Height="200" Width="200"> <Rectangle Fill="Blue" /> </StackPanel> </StackPanel> </Grid>
Ignore for a moment the root Grid and first StackPanel. Their only purpose is to arrange the three containers we’ll look at in a nice, horizontal package. As you can see from the code I have a Grid, a Canvas, and a Stackpanel. All three are dimensioned to 200 x 200 units, and all three have an identical Rectangle as the sole child element. If you plug this markup into XamlPad you should see the following (reduced for presentation purposes):
So, what’s going on here? The containers obviously don’t treat this identical child Rectangle identically. The Rectangle is dimensionless, it has no Width and Height attributes, and so when placed into a container it will take its default size for the layout strategy implemented by the container. In the Grid that means it expands to fill the entire available area, which in this case is the whole 200 x 200 space, because the Grid has no explicit row and column definitions. In the Canvas and StackPanel it doesn’t show up at all. So if we want to see it I guess we’ll have to give it some dimensions. Here is the updated code:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel Orientation="Horizontal"> <Grid Background="Gray" Margin="5" Height="200" Width="200"> <Rectangle Width="100" Height="100" Fill="Blue" /> </Grid> <Canvas Background="Gray" Margin="5" Height="200" Width="200"> <Rectangle Width="100" Height="100" Fill="Blue" /> </Canvas> <StackPanel Background="Gray" Margin="5" Height="200" Width="200"> <Rectangle Width="100" Height="100" Fill="Blue" /> </StackPanel> </StackPanel> </Grid>
I apologize for the line numbers which make this code hard to copy. I use the CodeViewer WP plugin and I haven’t figured out how to turn them off. Click the download link and you’ll get a plain text version you can ctrl-c from easily. Anyway, plug this into XamlPad and you get a somewhat different result, which should look like what you see below:
Now we can see that there really are three Rectangles. We can also see that once again the three containers react quite differently to this identical element. In each container the Rectangle now has the specified size of 100 x 100 units. The default allignment is where things diverge. In the Grid a child element goes to the center of the cell. In the Canvas it ends up at top-left (0,0), while in the StackPanel it ends up at top-center.
This behavior makes sense given the design goals of these containers. The Grid’s purpose is to arrange content into cells. Within a cell it has no notion of (x,y) coordinates, and so lacking any other allignment directives it puts the child in the center. The Canvas, on the other hand, is designed specifically to allow (x,y) positioning of child elements. Since we didn’t tell it where to put the Rectangle it defaulted to (0,0). Perfectly reasonable choice. The StackPanel is a container that stacks its children one after another. You can control the direction it stacks in with the Orientation property, which defaults to Vertical. So the StackPanel in the example wants to stack its elements vertically. The Rectangle thus starts at the top, and since it is narrower than the panel the allignment defaults to center.
What if we add a second shape? Let’s try putting an Ellipse into each container along with the existing Rectangle. Here’s the code:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel Orientation="Horizontal"> <Grid Background="Gray" Margin="5" Height="200" Width="200"> <Rectangle Width="100" Height="100" Fill="Blue" /> <Ellipse Width="100" Height="100" Fill="Red" /> </Grid> <Canvas Background="Gray" Margin="5" Height="200" Width="200"> <Rectangle Width="100" Height="100" Fill="Blue" /> <Ellipse Width="100" Height="100" Fill="Red" /> </Canvas> <StackPanel Background="Gray" Margin="5" Height="200" Width="200"> <Rectangle Width="100" Height="100" Fill="Blue" /> <Ellipse Width="100" Height="100" Fill="Red" /> </StackPanel> </StackPanel> </Grid>
I’ve made the Ellipse the same size, but have given it a different fill color so that we can easily see what happens. And what happens, when you plug this markup into XamlPad, is this:
In both the Grid and the Canvas the second element is stacked on top of the first. Grid and Canvas both allow child elements to overlap and share coordinate space. This can be quite a useful trait if you want to create layers in your application. In a drawing package I am working on I have a shape layer and transparent tool layer created by stacking Canvases in a Grid cell. The StackPanel is doing what StackPanels do, i.e. stacking. In this case the Ellipse has been stacked under the Rectangle due to the default vertical orientation.
Now that we’ve looked at how these containers compare when defined as identically as possible, lets look specifically at three things that make each panel uniquely useful. For example, let’s make the Grid an actual grid:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel Orientation="Horizontal"> <Grid Background="Gray" Margin="5" Height="200" Width="200"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Rectangle Grid.Row="0" Grid.Column="0" Width="100" Height="100" Fill="Blue" /> <Ellipse Grid.Row="1" Grid.Column="1" Width="100" Height="100" Fill="Red" /> </Grid> <!-- Canvas and StackPanel removed --> </StackPanel> </Grid>
The ColumnDefinitions and RowDefinitions collections tell the Grid how to carve up the screen area it owns. In this case I’ve given it two rows and two columns, resulting in four addressable cells. The cells are addressed by using the Grid.Row and Grid.Column attached properties on a child element. If you’re not familiar with attached properties they are context-specific dependency properties, that a child element acquires when it has a parent of a certain type, in this case a Grid. I have placed the Rectangle in cell (0,0) and the Ellipse in cell (1,1). This is what it looks like:
The Canvas has its own tricks as well. Obviously there must be a way to position elements. That comes in the form of the Canvas.Top and Canvas.Left attached properties. Let’s move the Ellipse away from the Rectangle:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel Orientation="Horizontal"> <!-- Grid removed --> <Canvas Background="Gray" Margin="5" Height="200" Width="200"> <Rectangle Width="100" Height ="100" Fill="Blue" Canvas.ZIndex="1" /> <Ellipse Canvas.Top="50" Canvas.Left="50" Width="100" Height="100" Fill="Red" /> </Canvas> <!-- StackPanel removed --> </StackPanel> </Grid>
As you can see I have used Canvas.Left and Canvas.Top to position the Ellipse at (50,50) in the coordinate space of the Canvas, which happens to be dead in the middle. I have also used another attached property, Canvas.ZIndex, to move the Rectangle on top of the Ellipse. If you don’t use ZIndex the zorder is the same as the parse order from top to bottom. In WPF Grid also supports a ZIndex attached property with the same effect, but Silverlight does not. Here’s what the markup looks like in XamlPad:
As you can see, the Ellipse is positioned in the center, and the Rectangle is on top due to the ZIndex property.
There is a lot more that could be said about these three container types. We haven’t talked about content allignment, which can change the default behavior pretty significantly. But at least I hope this post has been helpful in terms of increasing your understanding of how these containers function. In the end each has its own set of applications. Grid is probably the most generally useful, which is why Visual Studio and Blend default to using one for the root of a layout. But when you want to position elements explictly to (x,y) coords you’ll need to use a Canvas, and when you want to arrange elements horizontally or vertically regardless of differences in element size, StackPanel will fit the bill.