* Layout (this article)
* XAML vs Code / Markup Extensions And Resources (next article)
* Commands and Events
* Dependency Properties
* DataBinding
* Styles/Templates
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
Layout is a fundamental building block used when writing any WPF. 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.
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 3 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.
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 children controls, the 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.
In XAML it will be like..
<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#, this 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);
And that's about all there is to basic Canvas layout.
I'm not good with VB. If you want to have it in VB, you can try some convertors.
StackPanel
The StackPanel
control is also very easy to use. It simply stacks its contents, vertically or horizontally, using a single property, called Orientation
.
The following picture shows a WrapPanel
control with 10 children. This is taken from the file called WrapPanelDEMO.xaml.
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);
}
And that's about all there is to basic WrapPanel
layout.
DockPanel
The DockPanel
control is one of the most useful (IMHO) 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 2), 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 or 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);
And that's about all there is to 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 starts 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.
Grid
control, shall we? The following picture shows a Grid
control with 3 Columns and 1 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.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
grid.Height = Double.NaN; //this is the same as Height="Auto" in XAML
//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);
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.
No comments:
Post a Comment
Please leave your comments here to improve myself!