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();
this.Content = canv;
canv.Margin = new Thickness(0, 0, 0, 0);
canv.Background = new SolidColorBrush(Colors.White);
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);
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()
Me.Content = canv
canv.Margin = New Thickness(0, 0, 0, 0)
canv.Background = New SolidColorBrush(Colors.White)
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)
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();
this.Content = sp;
sp.Margin = new Thickness(0, 0, 0, 0);
sp.Background = new SolidColorBrush(Colors.White);
sp.Orientation = Orientation.Vertical;
Button b1 = new Button();
b1.Content = "Im Top of Stack";
sp.Children.Add(b1);
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()
Me.Content = sp
sp.Margin = New Thickness(0, 0, 0, 0)
sp.Background = New SolidColorBrush(Colors.White)
sp.Orientation = Orientation.Vertical
Dim b1 As New Button()
b1.Content = "Im Top of Stack"
sp.Children.Add(b1)
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();
this.Content = wp;
wp.Margin = new Thickness(0, 0, 0, 0);
wp.Background = new SolidColorBrush(Colors.White);
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()
Me.Content = wp
wp.Margin = New Thickness(0, 0, 0, 0)
wp.Background = New SolidColorBrush(Colors.White)
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.
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;
to a GridColumn Width/Height /GridRow Width/Height which has special classes
dp.Width = Double.NaN;
dp.Height = Double.NaN;
this.Content = dp;
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
dp.Height = [Double].NaN
Me.Content = dp
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.
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;
grid.Height = Double.NaN;
this.Content = grid;
ColumnDefinition cd1 = new ColumnDefinition();
cd1.Width = new GridLength(40);
grid.ColumnDefinitions.Add(cd1);
ColumnDefinition cd2 = new ColumnDefinition();
cd2.Width = new GridLength(1, GridUnitType.Star);
grid.ColumnDefinitions.Add(cd2);
ColumnDefinition cd3 = new ColumnDefinition();
cd3.Width = new GridLength(2, GridUnitType.Star);
grid.ColumnDefinitions.Add(cd3);
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
grid.Height = [Double].NaN
Me.Content = grid
Dim cd1 As New ColumnDefinition()
cd1.Width = New GridLength(40)
grid.ColumnDefinitions.Add(cd1)
Dim cd2 As New ColumnDefinition()
cd2.Width = New GridLength(1, GridUnitType.Star)
grid.ColumnDefinitions.Add(cd2)
Dim cd3 As New ColumnDefinition()
cd3.Width = New GridLength(2, GridUnitType.Star)
grid.ColumnDefinitions.Add(cd3)
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">
-->
<Menu Width="Auto" Height="20"
Background="#FFA9D1F4"
DockPanel.Dock="Top">
-->
<MenuItem Header="File">
<MenuItem Header="Save"/>
<Separator/>
<MenuItem Header="Exit"/>
</MenuItem>
-->
<MenuItem Header="Help">
<MenuItem Header="About"/>
</MenuItem>
</Menu>
-->
<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>
-->
<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>
-->
<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