Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF: A Beginner's Guide - Part 1 of n

0.00/5 (No votes)
13 Feb 2008 90  
An introduction into the WPF layout system.

Introduction

Some of you may have come across some of my other articles that I have published here on The Code Project. And if you have, you may have noticed that I have been writing a few on WPF/LINQ/Silverlight/WCF of late. Generally, I try and have fun when I write my articles, and I try to make them interesting for myself; now sometimes, that means that they end up being quite complex, which I like.

However, I remember not so long ago, I had published what I deemed to be an intermediate level WPF article, which due to The Code Project upgrade, got classified as beginner. It was never intended to be a beginner's article, so it got some stick from some folk, for being too complicated and not beginner-esque.

Now, I don't mind getting stick, but this time, it was due to an upgrade problem, not something I had done. It did, however, get me thinking about what one of the forum entries stated: "Where are all the beginner's articles on WPF?"

So I thought about this, and did a search here on The Code Project, and found that generally, WPF writers on The Code Project (me included) are pretty much showing off. Now, don't get me wrong, this is a good thing; I can't tell you how much I've learnt from some of the more showy-offy type articles. But I also thought that there wasn't that much here for beginners with not much time on their hands... so I thought, yeah, perhaps a beginner's series in WPF would be nice. I know Josh Smith did a bang up job with his series A Guided Tour of WPF, which I personally recommend you all read if you haven't already.

And there are always books, but I've got three books on WPF and they all have differing information. So I thought, OK, maybe this beginner's series may not be so bad after all. I decided to give it a crack. I don't know how many articles I'll end up doing for this series, but it will probably be something like:

Like I say, I think Josh did an excellent job covering a lot of this, but why then did I buy three WPF books, was one not enough? Sometimes, it's just nice to have a different point of view. For example, Josh is a WPF master (a condor if you like... or some equally majestic bird... huge black out in the sun, wing spanned bird), and I am a fledging chick just learning to fly (with tiny little wings) in the WPF world. So I figure it may do some people some good, to hear things from a slightly different angle (A.K.A. crazy fledging chick).

I will, of course, be covering some stuff that you could lift straight out of a book, but I hope there will also be some new stuff that I have picked up whilst taking my own WPF journey.

As this series will be quite a commitment, all I ask is that if you find this article useful, please leave a comment at the bottom and a vote, so I know whether it's worth doing the rest of the series. You know there are 100s of other articles in my reserve (I have a big list), where I could go mad, but I feel that this may be a useful series (basically, tell me in the article's forum that you want more of this), and if that is the case, I will be more than happy to commit myself to making this series as enjoyable as I can.

So without further ado, I guess we should start looking into what we need to get to grips within this article. As it is the first article in this (proposed, if people want more of this) series, it really has to start with layout.

Layout

Layout is one of the most important parts of any WPF project; the following sub sections will introduce you to the new layout options at your disposal.

The Importance of Layout

The way I see it, WPF can be used in one of two ways, it can be used in a browser (partial trust) - these are known as XBap, or a full blown application, basically an executable (*.exe). Whatever format is chosen is not important for this article's content, as layout is equally important to both formats.

What I mean is that layout is a fundamental building block used when writing any WPF, no matter whether it's an XBap or an application. Using the layout controls in WPF allows developers/designers to create very complex arrangements of pages/controls. Without layout, we probably couldn't achieve anything, apart from a mess. So if you are looking for a mess, just quit reading right here. If, however, you want to know how to use the new layout options in WPF, read on.

What's New in WPF

If you are a Web developer, probably most (but not all) of what this article is going to cover will be new to you. If you are a WinForms developer, you have undoubtedly come across Panel classes before, and maybe even used some of the more sophisticated Panel sub classes, such as FlowLayoutPanel and TableLayoutPanel. You should also be familiar with WinForms properties such as Anchor and Dock. Sounds familiar? Well, the truth is that some of this knowledge will still be useful, but you will still have to do some learning. For you Web guys, the good news is that the XAML syntax is fairly similar to XHTML, so you shouldn't have any trouble picking up this new way of creating UIs either.

Within WPF (at least the current version), Microsoft has provided a few Layout controls for developers/designers to use, most of which will be new ground to most of you, I imagine. These new layout controls will be the main focus of this article. You are, of course, free to author your own layout controls, if one of the pre-built controls doesn't suit your needs. We will see more on this later.

For the purpose of this article, we will be looking at the following:

  • Canvas
  • StackPanel
  • WrapPanel
  • DockPanel
  • Grid

Please note that I will only be covering the basics of these controls; there are many more resources available for being clever with these controls, should you wish to research that. I however, consider the more advanced usages of these controls to be outside the scope of this article. Remember it's a beginner's series, so I want to keep it at a beginner's level.

A Brief Detour into the Importance of Margin

One thing that you simply must know, is how important the Margin property is. By using the Margin, we are able to specify how much space the current control (the one we are specifying the Margin property for) wants to have around it. WPF provides a ValueConverter that accepts a string of the format 5,5,5,5, but what does this mean? Well, it's basically saying that we want a Margin of 5 pixels all around the control that is declaring this property. The Margin string is stating Left, Top, Right, Bottom, and is one of three overloaded constructors used by the strangely named Thickness class that is used in the case where we are trying to set the Margin in code-behind.

Canvas

The Canvas control is one of the easier layout controls to use. It is a simple X/Y position container, where each of the contained (children) controls must specify the following four properties in order to be positioned within the parent Canvas control:

  • Canvas.Left
  • Canvas.Right
  • Canvas.Top
  • Canvas.Bottom

With these four properties in place, the control will be positioned using these values within the parent Canvas control. These properties probably look a little bit odd, where we have a Canvas.Left for example; well, they are a bit odd actually. These are not the normal properties that you used in .NET 2.0 land. These are Dependency/Attached Properties, which I am not going to go into right now, as they will be the subject of a further article in this series.

What else do we need to know about a Canvas control and its children? Well, actually that's almost it; the only other thing to consider is that if the Canvas control is a simple X/Y position container, what's to stop two child controls, overlapping, and which child control should be on top? Well, that's all taken care of by another Dependency/Attached Property of the Canvas control. This is called the Canvas.ZIndex property, and this indicates which control should be on top. Basically, the higher the Canvas.ZIndex value is, the more on top the control that declares this Dependency/Attached Property will be. If no Canvas.ZIndex is declared for any of the child controls, Canvas.ZIndex will be set to the order in which the children are added to the Canvas control.

Let's see an example of this, shall we? The following picture shows a Canvas control with two children, one on top of the other. This is taken from the file called CanvasDEMO.xaml in the attached demo project.

So how does this look in code? Well, in XAML, it is as follows:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="WPF_Tour_Beginners_Layout.CanvasDEMO"
    x:Name="Window"
    Title="CanvasDEMO"
    Width="640" Height="480">
        <Canvas Margin="0,0,0,0" Background="White">
            <Rectangle Fill="Blue"
                Stroke="Blue"
                Width="145"
                Height="126"
                Canvas.Left="124" Canvas.Top="122"/>
            <Ellipse Fill="Green"
                Stroke="Green"
                Width="121" Height="100"
                Panel.ZIndex="1"
                Canvas.Left="195" Canvas.Top="191"/>
        </Canvas>
</Window>

And in C#, it would be as follows:

Canvas canv = new Canvas();

//add the Canvas as sole child of Window
this.Content = canv;
canv.Margin = new Thickness(0, 0, 0, 0);
canv.Background = new SolidColorBrush(Colors.White);

//The Rectangle
Rectangle r = new Rectangle();
r.Fill = new SolidColorBrush(Colors.Blue);
r.Stroke = new SolidColorBrush(Colors.Blue);
r.Width = 145;
r.Height = 126;
r.SetValue(Canvas.LeftProperty, (double)124);
r.SetValue(Canvas.TopProperty, (double)122);
canv.Children.Add(r);

//The Ellipse
Ellipse el = new Ellipse();
el.Fill = new SolidColorBrush(Colors.Green);
el.Stroke = new SolidColorBrush(Colors.Green);
el.Width = 121;
el.Height = 100;
el.SetValue(Canvas.ZIndexProperty, 1);
el.SetValue(Canvas.LeftProperty, (double)195);
el.SetValue(Canvas.TopProperty, (double)191);
canv.Children.Add(el);

Whilst in VB.NET, this would be:

Dim canv As New Canvas()
'add the Canvas as sole child of Window
Me.Content = canv
canv.Margin = New Thickness(0, 0, 0, 0)
canv.Background = New SolidColorBrush(Colors.White)
'The Rectangle
Dim r As New Rectangle()
r.Fill = New SolidColorBrush(Colors.Blue)
r.Stroke = New SolidColorBrush(Colors.Blue)
r.Width = 145
r.Height = 126
r.SetValue(Canvas.LeftProperty, CDbl(124))
r.SetValue(Canvas.TopProperty, CDbl(122))
canv.Children.Add(r)
'The Ellipse
Dim el As New Ellipse()
el.Fill = New SolidColorBrush(Colors.Green)
el.Stroke = New SolidColorBrush(Colors.Green)
el.Width = 121
el.Height = 100
el.SetValue(Canvas.ZIndexProperty, 1)
el.SetValue(Canvas.LeftProperty, CDbl(195))
el.SetValue(Canvas.TopProperty, CDbl(191))
canv.Children.Add(el)

And that's about all there is to the basic Canvas layout.

StackPanel

The StackPanel control is also very easy to use. It simply stacks its contents, vertically or horizontally, using a single property called Orientation.

Let's see an example of this, shall we? The following picture shows a StackPanel control with two children, one on top of the other. This is taken from the file called StackPanelDEMO.xaml in the attached demo project.

So how does this look in code? Well in XAML, it is as follows:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="WPF_Tour_Beginners_Layout.StackPanelDEMO"
    x:Name="Window"
    Title="StackPanelDEMO"
    WindowStartupLocation="CenterScreen"
    Width="640" Height="480">
      <StackPanel Margin="0,0,0,0" 
            Background="White" Orientation="Vertical">
        <Button Content="Im Top of Stack"/>
        <Button Content="Im Bottom Of Stack"/>
     </StackPanel>
</Window>

And in C#, this would be as follows:

StackPanel sp = new StackPanel();

//add the StackPanel as sole child of Window
this.Content = sp;
sp.Margin = new Thickness(0, 0, 0, 0);
sp.Background = new SolidColorBrush(Colors.White);
sp.Orientation = Orientation.Vertical;

//Button1
Button b1 = new Button();
b1.Content = "Im Top of Stack";
sp.Children.Add(b1);
//Button2
Button b2 = new Button();
b2.Content = "Im Bottom of Stack";
sp.Children.Add(b2);

Whilst in VB.NET, this would be:

Dim sp As New StackPanel()
'add the StackPanel as sole child of Window
Me.Content = sp
sp.Margin = New Thickness(0, 0, 0, 0)
sp.Background = New SolidColorBrush(Colors.White)
sp.Orientation = Orientation.Vertical
'Button1
Dim b1 As New Button()
b1.Content = "Im Top of Stack"
sp.Children.Add(b1)
'Button2
Dim b2 As New Button()
b2.Content = "Im Bottom of Stack"
sp.Children.Add(b2)

And that's about all there is to the basic StackPanel layout.

WrapPanel

The WrapPanel control, again, is very easy to use (are you seeing a pattern here? Layout is fairly OK in WPF); it simply wraps its contents.

Let's see an example of this, shall we? The following picture shows a WrapPanel control with 10 children. This is taken from the file called WrapPanelDEMO.xaml in the attached demo project.

So how does this look in code? Well in XAML, it is as follows:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="WPF_Tour_Beginners_Layout.WrapPanelDEMO"
    x:Name="Window"
    Title="WrapPanelDEMO"
    WindowStartupLocation="CenterScreen"
    Width="640" Height="480">
        <WrapPanel Margin="0,0,0,0" Background="White">
            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>
            <Rectangle Margin="10,10,10,10" 
              Fill ="Blue" Width="60" Height="60"/>
    </WrapPanel>
</Window>

And in C#, this would be as follows:

WrapPanel wp = new WrapPanel();

//add the WrapPanel as sole child of Window
this.Content = wp;
wp.Margin = new Thickness(0, 0, 0, 0);
wp.Background = new SolidColorBrush(Colors.White);

//Add Rectangles
Rectangle r;
for (int i = 0; i <= 10; i++)
{
    r = new Rectangle();
    r.Fill = new SolidColorBrush(Colors.Blue);
    r.Margin = new Thickness(10, 10, 10, 10);
    r.Width = 60;
    r.Height = 60;
    wp.Children.Add(r);
}

Whilst in VB.NET, this would be:

Dim wp As New WrapPanel()
'add the WrapPanel as sole child of Window
Me.Content = wp
wp.Margin = New Thickness(0, 0, 0, 0)
wp.Background = New SolidColorBrush(Colors.White)
'Add Rectangles
Dim r As Rectangle
For i As Integer = 0 To 10
    r = New Rectangle()
    r.Fill = New SolidColorBrush(Colors.Blue)
    r.Margin = New Thickness(10, 10, 10, 10)
    r.Width = 60
    r.Height = 60
    wp.Children.Add(r)
Next

And that's about all there is to the basic WrapPanel layout.

DockPanel

The DockPanel control is one of the most useful (in my opinion) layout controls. It is the one that we would probably use as the base layout control that any new Window uses. Basically, with a DockPanel control (or two), we can achieve the sort of layout that has been the main layout for most applications we have ever seen. We can basically get a menu docked to the top, then a left/right main content area, and a status strip at the bottom. This is all thanks to a couple of properties on the DockPanel control. Basically, we can control the docking of any of our child controls that is within a parent DockPanel control by the use of the following Dependency/Attached Property.

  • DockPanel.Dock

This property may be set to Left/Right/Top/Bottom. There is one further property exposed as a normal CLR property on the DockPanel control, which is called LastChildFill, which when set to true will make the last child control that was added to the DockPanel control fill the remaining available space. This will override any DockPanel.Dock property that the child control may have already set.

Let's see an example of this, shall we? The following picture shows a DockPanel control with two children, one docked to the top, and the other docked to fill the remaining available area. This is taken from the file called DockPanelDEMO.xaml in the attached demo project.

So how does this look in code? Well in XAML, it is as follows:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="WPF_Tour_Beginners_Layout.DockPanelDEMO"
    x:Name="Window"
    Title="DockPanelDEMO"
    WindowStartupLocation="CenterScreen"
    Width="640" Height="480">

    <DockPanel Width="Auto" 
           Height="Auto" LastChildFill="True">
        <Rectangle Fill="CornflowerBlue" Stroke="CornflowerBlue"
            Height="20" DockPanel.Dock="Top"/>
        <Rectangle Fill="Orange" Stroke="Orange"  />
    </DockPanel>
</Window>

And in C#, this would be as follows:

DockPanel dp = new DockPanel();
dp.LastChildFill = true;
//this is the same as Width="Auto" in XAML, as long as its not applied
to a GridColumn Width/Height /GridRow Width/Height which has special classes
dp.Width = Double.NaN;
dp.Height = Double.NaN;

//add the WrapPanel as sole child of Window
this.Content = dp;
//Add Rectangles
Rectangle rTop = new Rectangle();
rTop.Fill = new SolidColorBrush(Colors.CornflowerBlue);
rTop.Stroke = new SolidColorBrush(Colors.CornflowerBlue);
rTop.Height = 20;
dp.Children.Add(rTop);
rTop.SetValue(DockPanel.DockProperty,Dock.Top);
Rectangle rFill = new Rectangle();
rFill.Fill = new SolidColorBrush(Colors.Orange);
rFill.Stroke = new SolidColorBrush(Colors.Orange);
dp.Children.Add(rFill);

Whilst in VB.NET, this would be:

Dim dp As New DockPanel()
dp.LastChildFill = True
dp.Width = [Double].NaN
'this is the same as Width="Auto" in XAML
dp.Height = [Double].NaN
'this is the same as Height="Auto" in XAML
'add the DockPanel as sole child of Window
Me.Content = dp
'Add Rectangles
Dim rTop As New Rectangle()
rTop.Fill = New SolidColorBrush(Colors.CornflowerBlue)
rTop.Stroke = New SolidColorBrush(Colors.CornflowerBlue)
rTop.Height = 20
dp.Children.Add(rTop)
rTop.SetValue(DockPanel.DockProperty, Dock.Top)
Dim rFill As New Rectangle()
rFill.Fill = New SolidColorBrush(Colors.Orange)
rFill.Stroke = New SolidColorBrush(Colors.Orange)

dp.Children.Add(rFill)

And that's about all there is to the basic DockPanel layout.

Grid

The Grid control is by far the most sophisticated WPF layout control there is (at present). It is sort of like an HTML table control, where you can specify rows and columns, and have cells that span multiple rows, or cells that span multiple columns. There is also a strange syntax which may be used for the width/height of columns and rows, which is known as the star "*" notation, which is exposed through the use of the GridLength class. Think of this as being like a percentage of what's left divider. For example, I could have some markup such as:

<Grid.ColumnDefinitions>
    <ColumnDefinition Width="40"/>
    <ColumnDefinition Width="*"/>
    <ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>

Where I have declared three Grid ColumnDefinition controls, where the first ColumnDefinition gets a fixed width of 40 pixels, and the remaining space is divided between the last two ColumnDefinition controls, where the last one gets twice as much as the second last one. This is the same principle for RowDefinition.

In order for child controls of a Grid control to tell the WPF layout system which cell they belong to, we simply use the following Dependency/Attached Properties, which use a 0 based index.

  • Grid.Column
  • Grid.Row

And to specify how many rows or columns a cell should occupy, we simply use the following Dependency/Attached properties, which start at 1.

  • Grid.ColumnSpan
  • Grid.RowSpan

By clever usage of a Grid control, you should almost be able to mimic any of the other layout controls. I'll leave that as an exercise for the reader.

Let's see an example of the Grid control, shall we? The following picture shows a Grid control with three columns and a row, where there are two children. The first child occupies column 1, and the second child occupies columns 2-3 as its Grid.ColumnSpan is set to 2. This is taken from the file called GridDEMO.xaml in the attached demo project.

So how does this look in code? Well, in XAML, it is as follows:

<Window
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      x:Class="WPF_Tour_Beginners_Layout.GridDEMO"
      x:Name="Window"
      Title="GridDEMO"
      WindowStartupLocation="CenterScreen"
      Width="640" Height="480">
    <Grid Width="Auto" Height="Auto" >

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="40"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>
        <Rectangle Fill="Aqua" 
          Grid.Column="0" Grid.Row="0"/>
        <Rectangle Fill="Plum" 
          Grid.Column="1" Grid.ColumnSpan="2"/>
    </Grid>
</Window>

And in C#, this would be as follows:

Grid grid = new Grid();
grid.Width = Double.NaN;
//this is the same as Width="Auto" in XAML

//this is the same as Height="Auto" in XAML
grid.Height = Double.NaN;
//add the Grid as sole child of Window
this.Content = grid;

//col1

ColumnDefinition cd1 = new ColumnDefinition();
cd1.Width = new GridLength(40);
grid.ColumnDefinitions.Add(cd1);
//col2
ColumnDefinition cd2 = new ColumnDefinition();
cd2.Width = new GridLength(1, GridUnitType.Star);
grid.ColumnDefinitions.Add(cd2);

//col3
ColumnDefinition cd3 = new ColumnDefinition();
cd3.Width = new GridLength(2, GridUnitType.Star);
grid.ColumnDefinitions.Add(cd3);
//Now add the cells to the grid
Rectangle r1c1 = new Rectangle();
r1c1.Fill = new SolidColorBrush(Colors.Aqua);
r1c1.SetValue(Grid.ColumnProperty, 0);
r1c1.SetValue(Grid.RowProperty, 0);
grid.Children.Add(r1c1);

Rectangle r1c23 = new Rectangle();
r1c23.Fill = new SolidColorBrush(Colors.Plum);
r1c23.SetValue(Grid.ColumnProperty, 1);
r1c23.SetValue(Grid.ColumnSpanProperty, 2);
grid.Children.Add(r1c23);

Whilst in VB.NET, this would be:

Dim grid As New Grid()
grid.Width = [Double].NaN
'this is the same as Width="Auto" in XAML
grid.Height = [Double].NaN
'this is the same as Height="Auto" in XAML
'add the Grid as sole child of Window
Me.Content = grid

'col1
Dim cd1 As New ColumnDefinition()
cd1.Width = New GridLength(40)
grid.ColumnDefinitions.Add(cd1)
'col2
Dim cd2 As New ColumnDefinition()
cd2.Width = New GridLength(1, GridUnitType.Star)
grid.ColumnDefinitions.Add(cd2)
'col3
Dim cd3 As New ColumnDefinition()
cd3.Width = New GridLength(2, GridUnitType.Star)
grid.ColumnDefinitions.Add(cd3)
'Now add the cells to the grid
Dim r1c1 As New Rectangle()
r1c1.Fill = New SolidColorBrush(Colors.Aqua)
r1c1.SetValue(Grid.ColumnProperty, 0)
r1c1.SetValue(Grid.RowProperty, 0)
grid.Children.Add(r1c1)

Dim r1c23 As New Rectangle()
r1c23.Fill = New SolidColorBrush(Colors.Plum)
r1c23.SetValue(Grid.ColumnProperty, 1)
r1c23.SetValue(Grid.ColumnSpanProperty, 2)

grid.Children.Add(r1c23)

As I say, the Grid control is quite sophisticated, so I urge you to explore this one further. You can do all sorts of things with the Grid such as have GridSplitter controls for resizing columns/rows, and you can set up shared sizes across multiple grids; this is known as SizeGroup. So please explore the Grid control further.

Putting it all Together

So now we can put all this good knowledge together and we could create something as beautiful as this:

No... only joking, I think the best thing to do here is, let's say I have some hypothetical layout that I want to achieve. Let's say one of the common layouts, favoured for years, where we have a menu bar at the top, followed by a main content area, and a status bar area at the bottom. Let's see a mock up (designed as a simple WinForm) of what we are aiming for:

I think I have given you all the tools you need to carry out designing this sort of layout in WPF. Do you want a hint, I think you will need to use the StackPanel, DockPanel, and Grid controls in order to get the job done.

In case you are wondering (lost **** it), here is how I did it (XAML only this time, sorry folks):

<Window x:Class="WPF_Tour_Beginners_Layout.PuttingItAllTogether"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      WindowStartupLocation="CenterScreen"
      Title="PuttingItAllTogether" 
      Width="640" Height="480" >

    <DockPanel Width="Auto" 
            Height="Auto" LastChildFill="True">
        <!--Top Menu Area-->
        <Menu Width="Auto" Height="20" 
            Background="#FFA9D1F4"
            DockPanel.Dock="Top">

            <!-- File Menu -->
            <MenuItem Header="File">
                <MenuItem Header="Save"/>
                <Separator/>
                <MenuItem Header="Exit"/>

            </MenuItem>
            <!-- About Menu -->
            <MenuItem Header="Help">

                <MenuItem Header="About"/>
            </MenuItem>
        </Menu>

        <!--Bottom Status Bar area, declared before middle section,
        as I want it to fill entire bottom of Window,
        which it wouldn't if there was a Left docked panel before it -->
        <StackPanel Width="Auto" 
            Height="31" Background="#FFCAC5C5"
            Orientation="Horizontal" DockPanel.Dock="Bottom">

            <Label Width="155" 
              Height="23" Content="Status Bar Message...."
              FontFamily="Arial" FontSize="10"/>

        </StackPanel>

        <!--Left Main Content area-->
        <StackPanel Width="136" 
               Height="Auto" Background="White">

            <Button Margin="5,5,5,5" Width="Auto" 
              Height="26" Content="button1"/>
            <Button Width="126" Height="26" 
              Content="button2" Margin="5,5,5,5"/>
            <Button Width="126" Height="26" 
              Content="button3" Margin="5,5,5,5"/>

        </StackPanel>

        <!--Right Main Content area, NOTE HOW this Grid is the last child
        so takes all the remaining room -->
        <Grid Width="Auto" Height="Auto" 
                  Background="#FFCC9393">

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

            </Grid.ColumnDefinitions>

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

            </Grid.RowDefinitions>

            <Rectangle Fill="Aqua" Margin="10,10,10,10" 
                  Grid.Row="0" Grid.Column="0"/>
            <Rectangle Fill="Aqua" Margin="10,10,10,10" 
                  Grid.Row="0" Grid.Column="1"/>
            <Rectangle Fill="Aqua" Margin="10,10,10,10" 
                  Grid.Row="1" Grid.Column="0"/>
            <Rectangle Fill="Aqua" Margin="10,10,10,10" 
                  Grid.Row="1" Grid.Column="1"/>

        </Grid>

    </DockPanel>
</Window>

Which resulted in the following Window:

There are a couple of tricks here, namely the following:

  • The Grid for the right hand content area must be the last child declared in order for it to take up the remaining space that the parent DockPanel wants to fill, due to LastChildFill="True".
  • The StackPanel used for the status bar must be before any other child that is declared as being DockPanel.Dock="Left" or DockPanel.Dock="Right". As, if there was another element before the status bar StackPanel, the status bar StackPanel would not be able to span the entire width available, as this space would have been stolen by any child that was DockPanel.Dock="Left" or DockPanel.Dock="Right". Try it, you'll see what I mean. Simply move the status bar XAML further down in the XAML file, say to the end.

Performance Considerations

As some panels can be bound to items (this will be discussed further in the DataBinding article), there may be occasions where the number of child elements displayed in a panel is quite large. For example, if a StackPanel contains a ListBox that is bound to a large database query, there would be lots of items. In this case, it's the ListBox that will have lots of children. However, internally, the ListBox control uses a Vertical StackPanel to render its items by default. Mmmm, that's not so great.

However, all is not that bad. WPF has one further trick up its sleeve to aid in this situation. We can use the Dependency/Attached Property VirtualizingStackPanel.IsVirtualizing on a ListBox, which means that the ListBox control's internal StackPanel rendering its items will now be virtualized. But what the heck is a VirtualizingStackPanel?

When a panel is virtualized, it means that only the visible elements are created. The rest aren't displayed. For example, when creating a ListBox displaying images bound to a database holding 100,000 rows, it would take a long time for the ListBox to load. If you use a virtualize panel, then only the visible images will get created in the UI. When you scroll down, the currently visible items will get destroyed, and the new visible items will get loaded onto the UI. There is only one panel that supports virtualization, and it is the VirtualizingStackPanel. If you need to create any new virtualized panels, you will have to write your own.

Custom Layouts

It's also possible to create your own custom panel which performs all types of custom layouts.

Now I could try and be clever here, and come up with some new spin on how to do this, but Paul Tallet has already done a great job here, with a section in his great FishEye panel CodeProject article. So this next little section's words are thanks to Paul Tallet:

To get your own custom panel off the ground, you need to derive from System.Windows.Controls.Panel and implement two overrides: MeasureOverride and LayoutOverride. These implement the two-pass layout system where during the Measure phase, you are called by your parent to see how much space you'd like. You normally ask your children how much space they would like, and then pass the result back to the parent. In the second pass, somebody decides on how big everything is going to be, and passes the final size down to your ArrangeOverride method where you tell the children their size and lay them out. Note that every time you do something that affects layout (e.g., resize the window), all this happens again with new sizes.

There is a whole bunch of custom panel links over on Rob Relyea's blog here.

We're Done

Phew, that was a lot to go through I suppose, but I hope by now you have the basics of layout in WPF.

But... Like I Said, If You Want More...

As I stated earlier, this article series is a big commitment for me, so if you want more, please vote, and leave a message so I know that it's worth me doing a whole series like this, at this level.

History

  • 17/01/08: Initial issue.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here