Silverlight Layout

摘要:Silverlight Layout

Silverlight 1.0 did not have layout functionality. Silverlight 2 does. But why is that a big deal? And what does "layout" mean, anyway? In generic terms, when you "lay something out" you are positioning things in a space. You could lay out a garden, a housing development, a grocery store, anything where you have some space to fill up, and things to fill it up with. This obviously includes placing visual elements on a computer screen.

In the first examples below, we'll ignore the fact that Silverlight 2 has a layout system, and do all of our layout the hard way.

Putting a button on the screen

Let's take the simple case of wanting to make a button by putting a TextBlock inside of a Rectangle in the top-left corner of the area occupied by the Silverlight plugin. (Let's ignore the fact that Silverlight 2 has a Button, so you wouldn't really need to make your own.)

Let's start with the Rectangle:

<UserControl x:Class="LayoutPt1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml>
<
Rectangle Height="20" Width="100" Stroke="Black" Fill="Beige"/>
</
UserControl>

So far, so good. (Note: I will not include the UserControl in subsequent examples, but it is still there.) Now, let's put the TextBlock in:

<Canvas>
<
Rectangle Height="20" Width="100" Stroke="Black" Fill="Beige"/>
<
TextBlock Text="Open"/>
</
Canvas>

But this doesn't look so good. The text isn't in the right spot:

Rectangle and TextBlock #1

But how do you know where to put it? One way to do it would be use trial and error, and change the Canvas.Top and Canvas.Left properties of the TextBlock until it looks OK. But it seems like maybe this wonderful computer thingy could do that for you. Of course it can, but it will take some code.

<Canvas>
<
Rectangle x:Name="button" Height="20" Width="100" Stroke="Black" Fill="Beige"/>
<
TextBlock x:Name="buttonText" Text="Open"/>
</
Canvas>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;

namespace LayoutPt1
{
    public partial class Page : UserControl
{ public Page() { InitializeComponent(); } private void UserControl_Loaded(object sender, RoutedEventArgs e) { Canvas.SetLeft(buttonText, (button.Width - buttonText.ActualWidth) / 2); Canvas.SetTop(buttonText, (button.Height - buttonText.ActualHeight) / 2); } } }

(Note: In subsequent examples, I am only going to include the interesting bits of the code rather than the entire UserControl subclass file.) The text inside of the Rectangle looks better, but there is still something wrong. The whole thing is slammed up in the corner of the browser:

Rectangle and TextBlock #2

It would look nicer with some more room, so let's change the XAML.

<Canvas>
<
Rectangle x:Name="button" Canvas.Left="8" Canvas.Top="8" Height="20"
Width="100" Stroke="Black" Fill="Beige"/>
<
TextBlock x:Name="buttonText" Text="Open"/>
</
Canvas>

Oops. That doesn't look so good:

image

Looks like we should change the code to account for the fact the the Rectangle is no longer at 0,0.

Canvas.SetTop(buttonText, Canvas.GetTop(button) + (button.Height - buttonText.ActualHeight) / 2);

Much better:

image

So now, we have some XAML and some code that will center a TextBlock inside of a Rectangle at 8,8.

Adding more buttons

Now that we have one button on the screen, let's add another. Where should we put it? Well, we forgot some white space around the button last time, so let's not forget it this time. The first button is at 8,8 and it is 100 wide and 20 high, and if we want a space of four pixels between the buttons for the margin, we should put the next button at 8 + 20 + 4 = 32. We should be able to copy, paste and modify our code to center the text.

<Canvas>
<
Rectangle x:Name="button1" Canvas.Left="8" Canvas.Top="8" Height="20"
Width="100" Stroke="Black" Fill="Beige"/>
<
TextBlock x:Name="buttonText1" Text="Open"/>
<
Rectangle x:Name="button2" Canvas.Left="8" Canvas.Top="32" Height="20"
Width="100" Stroke="Black" Fill="Beige"/>
<
TextBlock x:Name="buttonText2" Text="Save"/>
</
Canvas>
Canvas.SetTop(buttonText1, Canvas.GetTop(button1) + (button1.Height - buttonText2.ActualHeight) / 2);
Canvas.SetLeft(buttonText2, Canvas.GetLeft(button2) + (button2.Width - buttonText2.ActualWidth) / 2);
Canvas.SetTop(buttonText2, Canvas.GetTop(button2) + (button2.Height - buttonText2.ActualHeight) / 2);

Hah. Got it right the first try:

image

Now it gets complicated

But now, let's add some more buttons, which should be just like adding the "Save" button. But by the way, we want a "New" button to be on top, so we have to move everything down. And we also want our little stack of buttons in the bottom-right corner instead of the top left. We could do that by changing all of the Canvas.Left and Canvas.Top values. A little laborious, but not too bad. But what if we wanted to change the plugin size? Well, we could type in the numbers again. But what it the plugin was sized not absolutely, but as a percentage of the browser size, and we wanted our buttons to always be in the bottom right as the user resized the browser? Then we'd have to  do this:

<Canvas>
<
Rectangle x:Name="button0" Height="20" Width="100" Stroke="Black" Fill="Beige"/>
<
TextBlock x:Name="buttonText0" Text="New"/>
<
Rectangle x:Name="button1" Height="20" Width="100" Stroke="Black" Fill="Beige"/>
<
TextBlock x:Name="buttonText1" Text="Open"/>
<
Rectangle x:Name="button2" Height="20" Width="100" Stroke="Black" Fill="Beige"/>
<
TextBlock x:Name="buttonText2" Text="Save"/>
<
Rectangle x:Name="button3" Height="20" Width="100" Stroke="Black" Fill="Beige"/>
<
TextBlock x:Name="buttonText3" Text="Save as..."/>
</
Canvas>
{
    const double bigMargin = 8;
    const double smallMargin = 4;

    double right = Application.Current.Host.Content.ActualWidth - bigMargin;
    if (right < 0) return;

    double bottom = Application.Current.Host.Content.ActualHeight - bigMargin;

    PositionButton(button3, buttonText3, smallMargin, right, ref bottom);
    PositionButton(button2, buttonText2, smallMargin, right, ref bottom);
    PositionButton(button1, buttonText1, smallMargin, right, ref bottom);
    PositionButton(button0, buttonText0, smallMargin, right, ref bottom);
}

private void PositionButton(Rectangle r, TextBlock t, double margin, double right, ref double bottom)
{
    double left = right - r.Width;
    double top = bottom - r.Height;

    Canvas.SetLeft(r, left);
    Canvas.SetTop(r, top);
    Canvas.SetLeft(t, left + (r.Width - t.ActualWidth) / 2);
    Canvas.SetTop(t, top + (r.Height - t.ActualHeight) / 2);

    bottom -= r.Height + margin;
}

And it looks wonderful, and moves when the browser changes size:

image

But it was painful, and it was a lot of code to write just to move put four buttons where we wanted them. And even though things are nicely factored for putting a stack of buttons in the bottom right, imagine a screen full of buttons and such. And while you are designing, you'd add and remove elements, try different elements, move elements, etc. so you'd constantly be changing code to move things around. Yuck.

Layout to the rescue

So if you had never really thought about it before, it is easy to see why something that would do all of the dirty work for you would be nice.

That "something" in Silverlight 2 is the layout system. Silverlight 2 adds layout container elements. These layout container elements define how their children will be sized and positioned. A simple and very useful layout container is the Border element, which is similar to a Rectangle in many ways, but it has a single child that it centers inside of it by default.

This XAML will put our single "button" in the right spot in the upper left like we did above, but no code is necessary (obviously Silverlight is working away behind the scenes, but that's it):

<Canvas>
<
Border BorderBrush="Black" BorderThickness="1" Background="Beige" Height="20" Width="100" Margin="8">
<
TextBlock Text="New" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>
</Canvas>

Another simple layout container is the StackPanel. By default, this puts its children in a vertical stack, just like we did. Here's what that XAML looks like, again without having to write any code:

<Canvas>
<
StackPanel Margin="6">
<
Border BorderBrush="Black" BorderThickness="1" Background="Beige" Height="20" Width="100" Margin="2">
<
TextBlock Text="New" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>
<
Border BorderBrush="Black" BorderThickness="1" Background="Beige" Height="20" Width="100" Margin="2">
<
TextBlock Text="Open" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>
<
Border BorderBrush="Black" BorderThickness="1" Background="Beige" Height="20" Width="100" Margin="2">
<
TextBlock Text="Save" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>
<
Border BorderBrush="Black" BorderThickness="1" Background="Beige" Height="20" Width="100" Margin="2">
<
TextBlock Text="Save as..." HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>
</
StackPanel>
</
Canvas>

This looks exactly the same as our upper-left button stack. There are some more attributes in the XAML, but that's a small price to pay. The XAML for the bottom-right stack of buttons looks like this:

<Border>
<
StackPanel Margin="6" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<
Border BorderBrush="Black" BorderThickness="1" Background="Beige" Height="20" Width="100" Margin="2">
<
TextBlock Text="New" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>
<
Border BorderBrush="Black" BorderThickness="1" Background="Beige" Height="20" Width="100" Margin="2">
<
TextBlock Text="Open" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>
<
Border BorderBrush="Black" BorderThickness="1" Background="Beige" Height="20" Width="100" Margin="2">
<
TextBlock Text="Save" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>
<
Border BorderBrush="Black" BorderThickness="1" Background="Beige" Height="20" Width="100" Margin="2">
<
TextBlock Text="Save as..." HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>
</
StackPanel>
</
Border>

By replacing the Canvas (which doesn't really participate in layout) with a Border, and setting the horizontal and vertical alignments on the StackPanel, we now have a stack of buttons in the bottom-right of the plugin, that will automatically reposition itself if the plugin changes size. Did I mention that this example contains no code?

What does the Silverlight 2 layout system do?

The Silverlight 2 layout system has two main tasks:

  1. Figure out how big elements are
  2. Figure out where to put them

To do this, it relies on the fact that all elements that can have children know how to position their children. It introduces layout elements that have specific positioning behavior:

The Border has one child, that is centered by default.

The StackPanel can have any number of children, and arranges them in either a column (the default) or a row.

The Grid can have any number of children, and it arranges them sort of like a spreadsheet does. You can define rows and columns, and specify which rows and columns your elements can go into.

It is also possible to define your own layout containers, but that is an advanced topic.

Silverlight 2 also has a number of properties that layout uses, such as Margin, HorizontalAlignment, VerticalAlignment, MinWidth, MaxWidth, MinHeight, MaxHeight, etc.

The power of layout is demonstrated when you put layout containers inside of layout containers. For the next example, let's say that you want your Silverlight 2 app to look like a Windows app, with a row of buttons across the top, a line for status information on the bottom, and a row of buttons down the left side.

This could be done a number of ways, but when you start dividing the screen into rows and columns, a Grid is the natural way to think of things. I'll dive into the Grid more in subsequent posts. This is how your app would look (and I'm going to use real Buttons this time):

image

This is the XAML:

<UserControl x:Class="LayoutPt1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Loaded="UserControl_Loaded">
<
Grid Background="Beige">
<
Grid.RowDefinitions>
<
RowDefinition Height="auto"/>
<
RowDefinition Height="*"/>
<
RowDefinition Height="auto"/>
</
Grid.RowDefinitions>
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="auto"/>
<
ColumnDefinition Width="*"/>
</
Grid.ColumnDefinitions>

<
StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Background="Coral">
<
Button Content="New" Width="50" Margin="2" HorizontalAlignment="Left"/>
<
Button Content="Open" Width="50" Margin="2"/>
</
StackPanel>

<
Grid Grid.Row="2" Grid.Column="1" Background="CornflowerBlue">
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="*"/>
<
ColumnDefinition Width="auto"/>
</
Grid.ColumnDefinitions>
<
TextBlock Grid.Column="0" Text="Status text" VerticalAlignment="Center"/>
<
Button Grid.Column="1" Content="Help" Width="50" Margin="2"/>
</
Grid>

<
StackPanel Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Background="LightGreen">
<
Button Content="Command 1" Margin="2" Width="80" HorizontalAlignment="Left"/>
<
Button Content="Command 2" Margin="2" Width="80" HorizontalAlignment="Left"/>
<
Button Content="Command 3" Margin="2" Width="80" HorizontalAlignment="Left"/>
</
StackPanel>

</
Grid>
</
UserControl>

Canvas

Canvas, the most basic layout container, was present in Silverlight 1.0. It should not be used in most layout scenarios, as for the most part, it makes you do everything yourself. It lets its children be as big as they want to be, and positions them according to the Canvas.Left and Canvas.Top attached properties. It is "boundless" and does not clip by default. In Layout Fundamentals Part 1, the first examples showed how much "fun" it can be to position elements on a Canvas.

Border

Border is a very simple layout container. It does not derive from Panel, and can have only one child, which is centered by default. In addition to the Background property, Border has the BorderBrush, BorderThickness, CornerRadius, and Padding properties.

BorderBrush specifies the brush that will be used to draw the border of the Border. This is analogous to the Stroke property on a Rectangle. BorderThickness determines how many pixels thick each side of the Border will be, and CornerRadius determines the curve on each corner. The Padding property is used to put space between the border and the content. Note that the CornerRadius is used for rendering only. Only the BorderThickness property is used for layout calculations. This means that although it is possible to make a Border that looks like a circle, it is also possible to have the content overlapping the rounded corners of the border.

This XAML:

<Border Height="100" Width="150" BorderBrush="Blue" BorderThickness="12,4" CornerRadius="12,4,12,4" Background="LightBlue">
<
TextBlock Text="Fancy Border" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>

produces a fancy Border like this:

image

I said that the Border centers its children by default, but it isn't really the Border that does that, but rather the default values for HorizontalAlignment and VerticalAlignment on its child that do the centering. You'll notice that I specified the alignment of the TextBlock. TextBlock is special; it will take up all the space that is given to it, and render itself in the top-left, unless explicitly told to do otherwise. Also note that I specified the Width and Height of the Border. That is always an option, but you can let the Border figure out for itself how big it should be. In this case, it would first determine how big its child (the TextBlock) is, then add the Padding and BorderThickness properties.

<Border BorderBrush="Blue" BorderThickness="12,4" CornerRadius="12,4,12,4" Background="LightBlue">
<
TextBlock Text="Fancy Border" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>

image

The Border is just a bit too tight around the text, so let's add some Padding:

<Border BorderBrush="Blue" BorderThickness="12,4" CornerRadius="12,4,12,4" Background="LightBlue" Padding="4">
<
TextBlock Text="Fancy Border" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</
Border>

image

The Padding property is used in addition to the Margin property, which will be discussed below. If we put a Margin on the TextBlock, then we get this:

<Border BorderBrush="Blue" BorderThickness="12,4" CornerRadius="12,4,12,4" Background="LightBlue" Padding="4">
<
TextBlock Text="Fancy Border" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8"/>
</
Border>

image

Now there are 12 pixels between the TextBlock and the inside of the Border. It doesn't really matter how many of those pixels are the Border's Padding, or the TextBlock's Margin.

And now, I have a confession to make. I've been cheating. All of the XAML that I've been using actually includes a UserControl and a Canvas:

<UserControl x:Class="LayoutPt2.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml>
<
Canvas>
<
Border BorderBrush="Blue" BorderThickness="12,4" CornerRadius="12,4,12,4" Background="LightBlue" Padding="4">
<
TextBlock Text="Fancy Border" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8"/>
</
Border>
</
Canvas>
</
UserControl>

This is because if the Border was the child of the UserControl instead of the Canvas, it would take up the entire available space, which in this case would be the plugin space, because the default value for the HorizontalAlignment and VerticalAlignment are Stretch, which will cause the element to take up all of the available space. Often, this is exactly what you want, but for this example, I wanted to show what the Border would look like if it was not doing that. Here's what things would looks like without the Canvas:

<UserControl x:Class="LayoutPt1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml>
<
Border BorderBrush="Blue" BorderThickness="12,4" CornerRadius="12,4,12,4" Background="LightBlue" Padding="4">
<
TextBlock Text="Fancy Border" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8"/>
</
Border>
</
UserControl>

image

This fill behavior is typical, and is the result of an element being given more space than it actually needs. Unless the width and height have been set or constrained (e.g. by the MaxWidth and/or MaxHeight properties) or the HorizontalAlignment and/or VerticalAlignments set to a value other then Stretch, most elements will expand to fill the space assigned to them. The Margin and Padding properties have an effect on the minimum size only.

If an element in a layout container requires more space than is given to it, it is automatically clipped. Here's some XAML that specified the width of the Border, to make it narrower than the text requires:

<Canvas>
<
Border BorderBrush="Blue" Width="100" BorderThickness="12,4" CornerRadius="12,4,12,4" Background="LightBlue" Padding="4">
<
TextBlock Text="Fancy Border" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8"/>
</
Border>
</
Canvas>

And here's how that looks:

image

You can see that the text does not go all the way up to the edge. That is because of the Margin and Padding. Those properties are applied before the content is clipped.

StackPanel

The StackPanel will place its children in either a column (default) or row. This is controlled by the Orientation property. A vertical StackPanel can have its width and height specified; if the width is not specified, it will be as wide as its widest child (or it will stretch to fit its parent), if the height is not specified it will take up as much space vertically as is required to fit all of its children. An unconstrained horizontal StackPanel will be as wide as necessary to hold all of its children, and as tall as the tallest child. Here is some XAML for a vertical StackPanel with its Width specified:

<StackPanel Background="Aquamarine" HorizontalAlignment="Center" VerticalAlignment="Center" Width="150">
<
Button Width="100" Content="Width=100" Margin="2"/>
<
Button Width="auto" Content="Width=auto" Margin="2"/>
<
Button Width="200" Content="Width=200" Margin="2"/>
</
StackPanel>

This looks like:

image

The StackPanel has its Width set to 150. The first Button, which has its Width set to 100, is centered. The second button, which has its width set to "auto" (which is the same as not setting it) expands to fill the width of the StackPanel. The third Button, which has a Width of 200, is clipped, and is not centered. Note that all Buttons, whether they fit, were stretched to fill the StackPanel, or clipped, have their Margin property applied. If the alignment properties of the StackPanel are set to "Stretch", and the width is unconstrained, what happens? Here the XAML:

<UserControl x:Class="LayoutPt1.Page"
   
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Loaded="UserControl_Loaded">
    <
StackPanel Background="Aquamarine"HorizontalAlignment="Stretch"VerticalAlignment="Stretch">
        <
ButtonWidth="100"Content="Width=100"Margin="2"/>
        <
ButtonWidth="auto"Content="Width=auto"Margin="2"/>
        <
ButtonWidth="200"Content="Width=200"Margin="2"/>
    </
StackPanel>
</
UserControl>

Here's the result:

image

The StackPanel fills up the entire plugin space. The Buttons with their widths specified are centered; the Button with its Width set to "auto" is stretched fill the entire width. The bottom of the StackPanel is empty. There is no way to get the items in a vertical StackPanel to build from the bottom of the StackPanel; likewise a horizontal StackPanel's items will always be on the left. The VerticalAlignment property of a vertical StackPanel and the HorizontalAlighnment of a horizontal StackPanel are ignored. However, the HorizontalAlignment of an element in a vertical StackPanel, and the VerticalAlignment of an element in a horizontal StackPanel, will be honored. Here an example:

<StackPanel Background="Aquamarine" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<
Button Width="100" Content="Width=100" Margin="2" HorizontalAlignment="Left"/>
<
Button Width="auto" Content="Width=auto" Margin="2"/>
<
Button Width="200" Content="Width=200" Margin="2" HorizontalAlignment="Right"/>
</
StackPanel>

Here's what that looks like:

image

To emphasize the point about nested layout containers, here are some horizontal StackPanels inside of a vertical StackPanel:

<StackPanel Background="Gray" HorizontalAlignment="Center" VerticalAlignment="Center">
<
StackPanel Orientation="Horizontal" Background="Red" Margin="2">
<
Button Margin="2" Width="30" Content="A"/>
<
Button Margin="2" Width="30" Content="B"/>
<
Button Margin="2" Width="30" Content="C"/>
</
StackPanel>
<
StackPanel Orientation="Horizontal" Background="White" Margin="2">
<
Button Margin="2" Width="30" Content="D"/>
<
Button Margin="2" Width="30" Content="E"/>
<
Button Margin="2" Width="30" Content="F"/>
</
StackPanel>
<
StackPanel Orientation="Horizontal" Background="Blue" Margin="2">
<
Button Margin="2" Width="30" Content="G"/>
<
Button Margin="2" Width="30" Content="H"/>
<
Button Margin="2" Width="30" Content="I"/>
</
StackPanel>
</
StackPanel>

This looks like:

image

Grid

The Grid is the most powerful layout container provided in Silverlight 2. It can have multiple children, and acts rather like a spreadsheet. The cells are not explicitly defined; you specify the rows and columns, and those define the cells. A row is the same height and a column is the same width across the entire Grid, but elements can be made to span multiple cells. Cells can contain more than one item.

The placement of an element in the Grid is specified using attached DependencyProperties that are set on the children of the Grid: Grid.Row, Grid.Column, Grid.RowSpan and Grid.ColumnSpan.

The size of rows and columns may be specified exactly, set to "auto", or use "star-sizing". If a row or column is set to "auto", it will be just as tall or wide as its tallest or widest child. Star-sizing is very powerful but a bit more complicated, so it will be discussed below.

Let's just launch right into it, and bring back the example from the first post in this series. I've modified it a little bit to make it more interesting.

<UserControl x:Class="LayoutPt1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Loaded="UserControl_Loaded">
<
Grid Background="Beige">
<
Grid.RowDefinitions>
<
RowDefinition Height="auto"/>
<
RowDefinition Height="*"/>
<
RowDefinition Height="auto"/>
</
Grid.RowDefinitions>
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="100"/>
<
ColumnDefinition Width="*"/>
</
Grid.ColumnDefinitions>

<
StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Background="Coral">
<
Button Content="New" Width="50" Margin="2" HorizontalAlignment="Left"/>
<
Button Content="Open" Width="50" Margin="2"/>
</
StackPanel>

<
Grid Grid.Row="2" Grid.Column="1" Background="CornflowerBlue">
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="*"/>
<
ColumnDefinition Width="auto"/>
</
Grid.ColumnDefinitions>
<
TextBlock Grid.Column="0" Text="Status text" VerticalAlignment="Center"/>
<
Button Grid.Column="1" Content="Help" Width="50" Margin="2"/>
</
Grid>

<
StackPanel Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Background="LightGreen">
<
Button Content="Command 1" Margin="2" HorizontalAlignment="Left"/>
<
Button Content="Command 2" Margin="2" HorizontalAlignment="Left"/>
<
Button Content="Command 3" Margin="2" HorizontalAlignment="Left"/>
</
StackPanel>
</
Grid>
</
UserControl>

This looks like:

image

Let's examine the XAML. There are two Grids, and two StackPanels. The first Grid, the "outer" one, has a RowDefinition section like this:

<Grid.RowDefinitions>
<
RowDefinition Height="auto"/>
<
RowDefinition Height="*"/>
<
RowDefinition Height="auto"/>
</
Grid.RowDefinitions>

This means that this Grid has three rows. These are numbered starting from 0, but in the definitions section, the number is implicit. The first definition is row 0, the next is row 1, etc. The only attribute a row has is its height. This is controlled by its Height, MinHeight and MaxHeight properties. When the Height is set to "auto", the row will only be as tall is it has to be to contain its children or to make an orderly Grid. The second Row has a size of "*". This means that the row will take up all left-over space. So if the Height of the Grid ends up being 100, and the first and last rows each take up 25, then the second row will be 50 high. It gets a little more complicated when more than one row or column is star-sized. The only item of note in the ColumnDefinition section is that the first column has a fixed size of 100.

The elements are placed in the Grid cells using the Grid.Row and Grid.Column properties.

Star-sizing in a Grid is a very powerful way of allocating relative amounts of space. The algorithm is pretty complicated (it has to deal with auto- and star-sized rows and columns, spanning, etc.) but the principle is simple: whatever space is left over from fixed- and auto-sized columns is allocated to all of the columns with star-sizing, according to the proportion of stars. Let's consider these ColumnDefinitions:

<Grid Background="Beige" Width="300">
<Grid.ColumnDefinitions>
<
ColumnDefinition Width="100"/>
<
ColumnDefinition Width="*"/>
</
Grid.ColumnDefinitions>

The Grid has a width of 300. Column 0 has a width of 100, and there is one star-sized column, so the star-sized column gets all of the space left over (200). If the definitions looked like this:

<Grid.ColumnDefinitions>
<
ColumnDefinition Width="100"/>
<
ColumnDefinition Width="*"/>
<
ColumnDefinition Width="*"/>
</
Grid.ColumnDefinitions>

Then columns 1 and 2 would gets widths of 100 each. But with these definitions:

<Grid.ColumnDefinitions>
<
ColumnDefinition Width="100"/>
<
ColumnDefinition Width="*"/>
<
ColumnDefinition Width="3*"/>
</
Grid.ColumnDefinitions>

the space is allocated differently. The total of the multipliers on the stars is 4 (the 1 on "*" is implicit.) So each star is now width 4 / 200 = 50. Column 1 will be 1 * 50 wide and column 2 will be 3 * 50 wide. Real numbers can also be used:

<Grid.ColumnDefinitions>
<
ColumnDefinition Width="100"/>
<
ColumnDefinition Width=".5*"/>
<
ColumnDefinition Width="1.5*"/>
</
Grid.ColumnDefinitions>

This ends up allocating the same amount of space in the previous example, because I chose nice numbers. The sum of the star multipliers is 2, so each star is worth 100 (in case you had forgotten the Grid is 300 wide, and the first column is 100 wide, so 200 will be divided up among the star-sized columns.) Column 1 is therefore .5 * 100 = 50 wide, and column 2 is 1.5 * 100 = 150 wide.

Column and row spanning are controlled by the Grid.ColumnSpan and Grid.RowSpan properties. When one of these is set on the child of a Grid, it will cover more than one column or row. In the example above, the StackPanel has a Grid.RowSpan of 3:

<StackPanel Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Background="LightGreen">
<
Button Content="Command 1" Margin="2" HorizontalAlignment="Left"/>
<
Button Content="Command 2" Margin="2" HorizontalAlignment="Left"/>
<
Button Content="Command 3" Margin="2" HorizontalAlignment="Left"/>
</
StackPanel>

This causes the StackPanel to cover all three rows of the Grid, starting from the row specified by its Grid.Row property.