Friday, July 10, 2009

WPF - Beginners tutorial PART - 1

I have started to induct two .NET people. I would prefer to start a series of tutorials at beginner level. I take advantage of Sacha Barber's tutorials in codeproject which got best article award for the month Jasnuary 2008. I thought of starting this PART - 1 with the following topics covered. The list of topics as of now planned are follows...

* 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.

Let's see an example of the 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!