Introduction
With WPF, Microsoft introduced a new XML based descriptive language for use when designing interfaces called XAML. To help you use the new language, the company built in the VS forms designer support for XAML. Also, the Microsoft Expression Interactive Designer can be used to create such files.
But how about code-based layout? How did it change? Charles Petzold seems to think that it was left out from WPF layout improvements and designing code-only interfaces has now become harder than ever. Let's see if he is right.
Possible uses of this non-XAML approach in WPF are:
- you do not know XAML, have experience with .NET 2.0 and need fast results
- VS does not offer XAML support for the type of project you are working in (any .NET 2.0 project type at the moment)
- you want to dynamically (at runtime) create a window, it changes too much to use a single XAML for all configurations and don't want to use multiple XAML files
- you want to dynamically (at runtime) add only a few controls to an existing XAML window
Prerequisites
To understand the code, you should have a basic understanding of XAML and WPF layout using XAML. If you have experience dynamically creating .NET 2.0 forms using code, it's even better.
Using the code
The code was written in VS 2005 with the June CTP of .NET 3.0 installed, it might not work with another CTP. It contains an example of code-only WPF window layout in a .NET 2.0 class library project, being used in a .NET 2.0 EXE application.
Changes in WPF layout from .NET 2.0
First of all, WPF made standard the resolution-independent way of designing Windows. In .NET 2.0 you had to manually position each control on the screen using co-ordinates and you could, with a certain amount of additional work, use resolution independent panels. In WPF, panels are the standard way of doing things so you should understand them well. A Form can hold only one child object, in its Content
property. You could add a normal control there, but then you cannot add any other control on your form. That is why the Forms Content
object should be a control Container
, that is a control that can hold other controls. Container
s designed and shipped in WPF for this purpose are: Canvas
, Grid
, StackPanel
, and DockPanel
. They provide different ways to arrange your controls on the form.
General steps in creating a WPF window from code
We will use a normal .NET 2.0 class project to create our WPF window in. Before we start, we should make sure we have all the prerequisites listed. Then, as we are not going to use a .NET 3.0 type of project, we must add references to the used assemblies. A reference is created by right-clicking on the References directory in your Solution Explorer. Then select Add Reference, then .NET. Add these DLLs: PresentationCore, PresentationFramework, and WindowsBase. They contain the objects we need from .NET 3.0. Now we can start coding in a new class file added to your project (Right click on your project in Solution Explorer, Add-> New->Class).
- Inherit from the
Window
class. You want to add extra functionality to a blank WPF window. It is only natural to derive from it, like this:
public class StackPanelWindow:System.Windows.Window
- Declare the controls you want to use:
StackPanel stack;
Button btn;
TextBox text;
- Create a constructor for the derived class in which you set the window's
Width
and Height
:
public StackPanelWindow()
{
this.Width = 300;
this.Height = 150;
- Instantiate the
Panel
and set its Width
and Height
:
stack = new StackPanel();
stack.Width = this.Width;
stack.Height = this.Height;
- Instantiate one of the declared controls and set its
Width
, Height
, other useful properties and events:
text = new TextBox();
text.Width = 100;
text.Height = 20;
text.Text = "some text";
- Add the control to the Container.
stack.Children.Add(text);
- Set the control's positioning. This part is dependent on the panel that you use. Generally, it is something like:
Class.SetXXX(control_to_set,value);
- Use the window in your program like this:
CodeWindows.StackPanelWindow sw = new CodeWindows.StackPanelWindow();
sw.Visibility = System.Windows.Visibility.Visible;
Layout example with StackPanel
The StackPanel
allows you to add items to the Window just as you would add a plate on top of another plate, creating a stack of controls. Keep in mind that the last control added is the one you see at the bottom of the form, controls are added one under the other.
public class StackPanelWindow:System.Windows.Window
{
StackPanel stack;
Button btn;
TextBox text;
public StackPanelWindow()
{
this.Width = 300;
this.Height = 150;
stack = new StackPanel();
stack.Width = this.Width;
stack.Height = this.Height;
this.Content = stack;
text = new TextBox();
text.Width = 100;
text.Height = 20;
text.Text = "some text";
stack.Children.Add(text);
btn = new Button();
btn.IsEnabled = true;
btn.Width = 100;
btn.Height = 20;
btn.Content = "Say it!";
stack.Children.Add(btn);
}
We see that step 7 is missing; the controls are automatically positioned one under the previous one.
Layout example with DockPanel
The DockPanel
is a Panel
in which each control is docked (that is, it sticks to a part of the Panel
). The code is the same as the one with StackPanel
but now we have to set the position of the controls manually by docking each one, like this:
DockPanel.SetDock(text, Dock.Top);
DockPanel.SetDock(btn, Dock.Bottom);
If we add a fifth control and so there is no side to dock it, it can be fitted in the center.
Layout example with Grid
The Grid
is like a table. It has row
s and column
s in which we can position controls. First we create the Grid
structure - the row
s and the column
s:
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions.Add(new RowDefinition());
Then to position each control in the Grid
:
Grid.SetColumn(text, 0);
Grid.SetRow(text, 0);
Layout example with Canvas
The canvas was the standard .NET 2.0 way of doing things. It allows you to position each control on a canvas with precise co-ordinates, like this:
public class CanvasWindow:System.Windows.Window
{
private Canvas canvas;
private TextBox text;
private Button btn;
public CanvasWindow()
{
this.Width = 300;
this.Height = 150;
canvas=new Canvas();
canvas.Width = this.Width;
canvas.Height = this.Height;
this.Content =canvas;
text = new TextBox();
text.Width = 100;
text.Height = 20;
text.Margin = new System.Windows.Thickness(10,10,10,10);
text.Text = "some text";
text.KeyDown += new System.Windows.Input.KeyEventHandler(text_KeyDown);
canvas.Children.Add(text);
btn = new Button();
btn.IsEnabled = true;
btn.Width = 100;
btn.Height = 20;
btn.Content = "Say it!";
btn.Margin = new System.Windows.Thickness(110, 10, 10, 10);
btn.Click += new System.Windows.RoutedEventHandler(btn_Click);
canvas.Children.Add(btn);
}
The positioning was made by this line:
Control.Margin = new System.Windows.Thickness(10,10,10,10);
which is a general way to make sure your object is where it should be. If you want to use another approach, particular to the Canvas
object, use:
Canvas.SetLeft( control_in_canvas , value );
and
Canvas.SetTop( control_in_canvas , value );
Note that layout created using the Canvas
is not resolution-independent, as all the properties are in pixels. It is not recommended to use it if you do not need it.
More Complex Design
For mode complex design, the panels should be used one in another. An example is: use a Stackpanel
in a DockPanel
to have several elements on the side of the DockPanel
.
Dragos is currently a student at the Polytechnic University of Bucharest. He likes keeping up to date with new and amazing technologies and he hopes he will one day master the mechanisms behind modern day programming languages so that he can write the best possible code from a performance and maintainability point of view.
He keeps track of the things he learns on a daily basis on his blog, at http://picobit.wordpress.com/ .