Layout
The Layout Containers
The Panel Background
By default, the Background of a layout panel is set to a null reference, which is equivalent to this:
1 <Grid x:Name="layoutRoot" Background="{x:Null}">
When your panel has a null background, any content underneath will show through (similar to if you set a fully transparent background color). However, there’s an important difference—the layout container won’t be able to receive mouse events.
Brushes support automatic change notification. In other words, if you attach a brush to a control and change the brush, the control updates itself accordingly.
The Grid
You create grids and rows by filling the Grid.ColumnDefinitions and Grid.RowDefinitions collections with objects. For example, if you decide you need two rows and three columns, you’d add the following tags:
1 <Grid ShowGridLines="True" Background="White"> 2 <Grid.RowDefinitions> 3 <RowDefinition></RowDefinition> 4 <RowDefinition></RowDefinition> 5 </Grid.RowDefinitions> 6 <Grid.ColumnDefinitions> 7 <ColumnDefinition></ColumnDefinition> 8 <ColumnDefinition></ColumnDefinition> 9 <ColumnDefinition></ColumnDefinition> 10 </Grid.ColumnDefinitions> 11 </Grid>
As this example shows, it’s not necessary to supply any information in a RowDefinition or ColumnDefinition element. If you leave them empty (as shown here), the Grid will share the space evenly between all rows and columns. In this example, each cell will be exactly the same size, depending on the size of the containing page.
The Grid supports three sizing strategies:
- Absolute sizes: You choose the exact size using pixels. This is the least useful strategy, because it’s not flexible enough to deal with changing content size, changing container size, or localization.
- Automatic sizes: Each row or column is given exactly the amount of space it needs and no more. This is one of the most useful sizing modes.
- Proportional sizes: Space is divided between a group of rows or columns. This is the standard setting for all rows and columns.
For maximum flexibility, you can mix and match these different sizing modes. For example, it’s often useful to create several automatically sized rows and then let one or two remaining rows get the leftover space through proportional sizing.
You set the sizing mode using the Width property of the ColumnDefinition object or the Height property of the RowDefinition object to a number. For example, here’s how you set an absolute width of 100 pixels:
1 <ColumnDefinition Width="100"></ColumnDefinition>
To use automatic sizing, you use a value of Auto:
1 <ColumnDefinition Width="Auto"></ColumnDefinition>
Finally, to use proportional sizing, you use an asterisk (*):
1 <ColumnDefinition Width="*"></ColumnDefinition>
If you want to divide the remaining space unequally, you can assign a weight, which you must place before the asterisk. For example, if you have two proportionately sized rows and you want the first to be half as high as the second, you could share the remaining space like this:
1 <RowDefinition Height="*"></RowDefinition> 2 <RowDefinition Height="2*"></RowDefinition>
You can also use two more attached properties to make an element stretch over several cells: RowSpan and ColumnSpan. These properties take the number of rows or columns that the element should occupy.
The GridSplitter
To use the GridSplitter effectively, you need to know a little bit more about how it works. Although the GridSplitter serves a straightforward purpose, it can be awkward at first. To get the result you want, follow these guidelines:
- The GridSplitter must be placed in a Grid cell. You can place the GridSplitter in a cell with existing content, in which case you need to adjust the margin settings so it doesn’t overlap. A better approach is to reserve a dedicated column or row for the GridSplitter, with a Height or Width value of Auto.
- The GridSplitter always resizes entire rows or columns (not single cells). To make the appearance of the GridSplitter consistent with this behavior, you should stretch the GridSplitter across an entire row or column, rather than limit it to a single cell. To accomplish this, you use the RowSpan or ColumnSpan properties you considered earlier.
- Initially, the GridSplitter is invisibly small. To make it usable, you need to give it a minimum size. In the case of a vertical splitter bar (like the one in Figure 3-14), you need to set the VerticalAlignment to Stretch (so it fills the whole height of the available area) and the Width to a fixed size (such as 10 pixels). In the case of a horizontal splitter bar, you need to set HorizontalAlignment to Stretch and Height to a fixed size.
- To actually see the GridSplitter, you need to set the Background property. Otherwise, the GridSplitter remains transparent until you click it (at which point a light blue focus rectangle appears around its edges).
- The GridSplitter respects minimum and maximum sizes, if you’ve set them on your ColumnDefinition or RowDefinition objects. The user won’t be allowed to enlarge or shrink a column or row outside of its allowed size range.
Custom Layout Containers
The Two-Step Layout Process
Every panel uses the same plumbing: a two-step process that’s responsible for sizing and arranging children. The first stage is the measure pass, and it’s at this point that the panel determines how large its children want to be. The second stage is the layout pass, and it’s at this point that each control is assigned its bounds. Two steps are required, because the panel might need to take into account the desires of all its children before it decides how to partition the available space.
You add the logic for these two steps by overriding the oddly named MeasureOverride() and ArrangeOverride() methods, which are defined in the FrameworkElement class as part of the Silverlight layout system. The odd names represent that the MeasureOverride() and ArrangeOverride() methods replace the logic that’s defined in the MeasureCore() and ArrangeCore() methods that are defined in the UIElement class. These methods are not overridable.