Layout in Windows Presentation Foundation (WPF)

Content Controls (that derives from the ContentControl class) have a Content property that you can set to almost any object. At first this could be a problem because you can set to only one object but luckily WPF includes a set of elements that help us to solve this problem.These elements are named Panels.

Basically a Panel is a component that control the rendering of elements and is responsible for sizing, positioning and arranging elements. WPF provides a number of predefined Panels, each supporting a different type of layout.

In the following table you will see all the Panels provided by the WPF with a brief description of each one:

panels

Figure 1 | WPF Panels. This table was extracted from Panels Overview.

As you may see we can group the Panels in two categories: those that are User Interface Panels and those that are not.
In this post we are going to focus on the User Interface Panels that are optimized to support UI Scenarios and are easy to use.

Let’s start with the StackPanel.

StackPanel

The StackPanel allow us to arranges child elements into a single line and to stack the elements in a specific direction. By default the direction is Vertical but you can change it to Horizontal using the Orientation property. Let’s see how to create a StackPanel in C# and in XAML.

C#

XAML

stackPanel = new StackPanel();
stackPanel.HorizontalAlignment = HorizontalAlignment.Left;
stackPanel.VerticalAlignment = VerticalAlignment.Top;
 
Button button1 = new Button();
button1.Content = “Flip Orientation”;
button1.Click += new RoutedEventHandler(OnClick);
Button button2 = new Button();
button2.Content = “Flip Orientation”;
button2.Click += new RoutedEventHandler(OnClick);
 
stackPanel.Children.Add(button1);
stackPanel.Children.Add(button2);
 
<StackPanel x:Name=”stackPanel” 
            HorizontalAlignment=”Left” 
            VerticalAlignment=”Top”>
    <Button Content=”Flip Orientation” 
            Click=”OnClick”/>
    <Button Content=”Flip Orientation” 
            Click=”OnClick”/>
</StackPanel>
void OnClick(object sender, RoutedEventArgs e)
{
    stackPanel.Orientation = (stackPanel.Orientation == Orientation.Horizontal) 
                ? Orientation.Vertical : Orientation.Horizontal;
}

Vertical Orientation

Horizontal Orientation

StackPanelVertical

StackPanelHorizontal

As you see in the code snippets we are adding two buttons to the Children collection. We are also subscribing to the button’s Click event in order to flip the orientation after performing a click.

WrapPanel

The WrapPanel is the panel most similar to the StackPanel. Is used to display child elements in sequential position. Automatically wraps the elements to the next line when there is not enough room. As in the StackPanel, the WrapPanel allows us to specify the elements direction using the Orientation property. Below you will find an example of how to use it.

C#

XAML

wrapPanel = new WrapPanel();
Button button1 = new Button();
button1.Content = “Flip Orientation”;
button1.Click += new RoutedEventHandler(OnClick);
Button button2 = new Button();
button2.Content = “Flip Orientation”;
button2.Click += new RoutedEventHandler(OnClick);
 
wrapPanel.Children.Add(button1);
wrapPanel.Children.Add(button2);
 
<WrapPanel x:Name=”wrapPanel”>
    <Button Content=”Flip Orientation” 
            Click=”OnClick”/>
    <Button Content=”Flip Orientation” 
            Click=”OnClick”/>
</WrapPanel>

Enough room

No Enough room

 WrapPanelEnoughRoom

WrapPanelNoEnoughRoom

The sample is pretty similar to the StackPanel one. The difference is that if you resize the window you will see that when the room is not enough the second button will be positioned to the next line (if the orientation is horizontal). You will experience a similar behavior when the orientation is vertical. Note that of there is not enough room and you click on the button to flip the orientation, nothing is happens.

Grid

The Grid panel can be used to achieve almost everything that can be done with the other Panels. The Grid panel merges the functionality of an absolute positioning and a tabular data control. A Grid allow you to easily position elements. Allow also to define row and column grouping. By default all rows and columns has the same size but you can specify and absolute width or height. Columns and Row also can take advantage of Star sizing to distribute remaining space proportionally. When Start is selected as the height or width of a row or column receives a weighted proportion of the remaining available space. This is in contrast to Auto which distributes space evenly based on the size of the content that is within a column or row.

C#

XAML

 
grid = new Grid();
grid.ShowGridLines = true;
 
ColumnDefinition columnAuto = new ColumnDefinition();
columnAuto.Width = GridLength.Auto;
ColumnDefinition columnStar = new ColumnDefinition();
columnStar.Width = new GridLength(50, GridUnitType.Star);
ColumnDefinition columnAbs = new ColumnDefinition();
columnAbs.Width = new GridLength(90);
 
grid.ColumnDefinitions.Add(columnAuto);
grid.ColumnDefinitions.Add(columnStar);
grid.ColumnDefinitions.Add(columnAbs);
 
RowDefinition row0 = new RowDefinition();
row0.Height = new GridLength(40);
RowDefinition row1 = new RowDefinition();
row1.Height = new GridLength(100, GridUnitType.Star);
 
grid.RowDefinitions.Add(row0);
grid.RowDefinitions.Add(row1);
 
TextBlock col0Row0 = new TextBlock();
col0Row0.Text = "Column 0 Row 0";
TextBlock col1Row0 = new TextBlock();
col1Row0.Text = "Column 1 Row 0";
TextBlock col2Row0 = new TextBlock();
col2Row0.Text = "Column 2 Row 0";
Rectangle rect = new Rectangle();
rect.Fill = Brushes.Green;
TextBlock col1Row1 = new TextBlock();
col1Row1.Text = "Column 1 Row 1";
TextBlock col2Row1 = new TextBlock();
col2Row1.Text = "Column 2 Row 1";
 
grid.Children.Add(col0Row0);
grid.Children.Add(col1Row0);
grid.Children.Add(col2Row0);
grid.Children.Add(rect);
grid.Children.Add(col1Row1);
grid.Children.Add(col2Row1);
 
Grid.SetColumn(col0Row0, 0);
Grid.SetRow(col0Row0, 0);
Grid.SetColumn(col1Row0, 1);
Grid.SetRow(col1Row0, 0);
Grid.SetColumn(col2Row0, 2);
Grid.SetRow(col2Row0, 0);
Grid.SetColumn(rect, 0);
Grid.SetRow(rect, 1);
Grid.SetColumn(col1Row1, 1);
Grid.SetRow(col1Row1, 1);
Grid.SetColumn(col2Row1, 2);
Grid.SetRow(col2Row0, 1);
<Grid ShowGridLines=”True”>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width=”auto”/>
        <ColumnDefinition Width=”*”/>
        <ColumnDefinition Width=”90″/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height=”40″/>
        <RowDefinition Height=”*” />
    </Grid.RowDefinitions>
    <TextBlock Grid.Column=”0″ 
               Grid.Row=”0″>Column 0 Row 0</TextBlock>
    <Rectangle Fill=”Green” 
               Grid.Column=”0″ Grid.Row=”1″/>
    <TextBlock Grid.Column=”2″ 
               Grid.Row=”0″>Column 2 Row 0</TextBlock>
    <TextBlock Grid.Column=”1″ 
               Grid.Row=”0″>Column 1 Row 0</TextBlock>
    <TextBlock Grid.Column=”1″ 
               Grid.Row=”1″>Column 1 Row 1</TextBlock>
    <TextBlock Grid.Column=”2″ 
               Grid.Row=”1″>Column 2 Row 1</TextBlock>
</Grid>

3 Columns 2 Rows

Grid  

Canvas

The Canvas panel is the most flexible layout provided by WPF. The position of the elements is according to absolute x and y coordinates. The Canvas allow us to position elements at any position on the screen even at coordinates that are outside its bounds (see the ClipToBounds property to know how to avoid this behavior). One thing you have to have in mind is that the order in which the elements appear determines the order in which they are drawn. As a result of this flexibility it is possible for an element to overdraw other elements (including those outside the bounds of the canvas).

C#

XAML

canvas = new Canvas();
canvas.Background = Brushes.AliceBlue;
canvas.Width = 150;
canvas.Height = 150;
 
Ellipse elipse1 = new Ellipse();
elipse1.Fill = Brushes.Green;
elipse1.Width = 70;
elipse1.Height = 70;
 
Ellipse elipse2 = new Ellipse();
elipse2.Fill = Brushes.SlateBlue;
elipse2.Width = 100;
elipse2.Height = 100;
 
canvas.Children.Add(elipse1);
canvas.Children.Add(elipse2);
 
Canvas.SetTop(elipse1, 15);
Canvas.SetLeft(elipse1, 20);
 
Canvas.SetTop(elipse2, 35);
Canvas.SetLeft(elipse2, 40);
<Canvas x:Name=”canvas” 
        Background=”AliceBlue” 
        Height=”200″ Width=”200″>
    <Rectangle Width=”50″ 
               Height=”50″ 
               Fill=”Green” 
               Canvas.Top=”20″ 
               Canvas.Left=”50″/>
    <Rectangle Width=”90″ 
               Height=”50″ 
               Fill=”Red” 
               Canvas.Top=”80″ 
               Canvas.Right=”140″/>
</Canvas>

Elements overdrawn

Elements outside the bounds of the canvas

CanvasOverdraw  

 CanvasOutsideTheBounds

The C# sample shows a child element overdraw other. If you read the code, probably you see that I’m using static methods to define the position of the elements.This static methods, behind the scenes, use attached properties that is something in going to tackle in a future post.  
The XAML sample shows a child element positioned outside the bounds of the Canvas.

DockPanel

The DockPanel allow us to position content along the edges of a container. This can be achieved by using the attached Dock property. Another important property of the DockPanel is the LastChildFill property that determines the position of the final element added as a child of a DockPanel, if is set this property to true (which is the default setting) the last child element of the DockPanel always fill the remaining space.

C#

XAML

dockPanel = new DockPanel();
dockPanel.Background = Brushes.AliceBlue;
dockPanel.Width = 200;
dockPanel.Height = 250;
dockPanel.LastChildFill = true;
 
Button button1 = new Button();
button1.Content = “Dock Top”;
button1.Background = Brushes.Yellow;
Button button2 = new Button();
button2.Content = “Dock Bottom”;
button2.Background = Brushes.Green;
Button button3 = new Button();
button3.Content = “Dock Left”;
button3.Background = Brushes.Red;
Button button4 = new Button();
button4.Content = “Dock Right”;
button4.Background = Brushes.CadetBlue;
Button button5 = new Button();
button5.Content = “Fill”;
button5.Background = Brushes.Pink;
 
dockPanel.Children.Add(button1);
dockPanel.Children.Add(button2);
dockPanel.Children.Add(button3);
dockPanel.Children.Add(button4);
dockPanel.Children.Add(button5);
 
DockPanel.SetDock(button1, Dock.Top);
DockPanel.SetDock(button2, Dock.Bottom);
DockPanel.SetDock(button3, Dock.Left);
DockPanel.SetDock(button4, Dock.Right);
<DockPanel x:Name=”dockPanel” 
           Background=”AliceBlue” 
           LastChildFill=”True” 
           Height=”200″ 
           Width=”250″>
    <Button Content=”Dock Left” 
            DockPanel.Dock=”Left” 
            Background=”Red”/>
    <Button Content=”Dock Top” 
            DockPanel.Dock=”Top” 
            Background=”Yellow”/>
    <Button Content=”Dock Right” 
            DockPanel.Dock=”Right” 
            Background=”CadetBlue”/>
    <Button Content=”Dock Bottom” 
            DockPanel.Dock=”Bottom” 
            Background=”Green”/>
    <Button Content=”Fill the remaining space” 
            Background=”Pink”/>
</DockPanel>

Top, Bottom, Left, Right

Left, Top, Right, Bottom

DockPanel

DockPanel2

If you see the samples, both are identical, so why the outcome is different? The outcomes of the samples are different because the position of child elements of DockPanel on the screen is determined by the Dock property of the respective child element and the relative order of those child elements under the DockPanel. Therefore, a set of child elements with the same Dock property values (like our samples) can be positioned differently on the screen depending on the order of these children under the DockPanel. Child ordering affects positioning because the DockPanel iterates through its child elements in order, setting the position of each element depending on remaining space.

VirtualizingStackPanel

The VirtualizingStackPanel is variation of the StackPanel that automatically “virtualizes” data-bound child content. It’s important to say that the Virtualization in a StackPanel only occurs when items are data-bound. With “virtualize” we are referring to a technique by which a subset of UIElements are generated from a larger number of data items based on which items are visible on screen. This offers a better performance over a normal StackPanel as we are not using intensively memory and processor.

<StackPanel DataContext=“{Binding Source={StaticResource Products}}”>
    <TextBlock Text=“{Binding XPath=@name}” Foreground=“Black”/>
        <ListBox VirtualizingStackPanel.IsVirtualizing=“True” 
                 ItemsSource=“{Binding XPath=Category}”/>      

Resources

In this section you will find useful samples and articles, that may help you to better understand the WPF layout system.

In Spanish (from Paulo’s blog):