Scott Lanning's Sizer Tutorial
From WxPerl wiki
Sizers Tutorial, by ScottLanning (2002-09-07)
Contents |
Introduction
Sizers are what you use to layout controls (widgets) on a dialog. They are similar to geometry managers in Tk such as pack and grid. They can be confusing at first and often don't do what you might have guessed. You should read the Sizers overview (part of wxWindows docs) before reading this tutorial in order to get an overview. There is also a section of demo/demo.pl in wxPerl which demonstrates some Sizer usage. This tutorial will go into greater detail, using an experimental approach where I try to "prove" everything from the sourcecode.
There are four types of Sizers that I will cover: wxBoxSizer, wxStaticBoxSizer, wxFlexGridSizer, and wxGridSizer. (I don't deal with wxNotebookSizer, as those are different in that aren't concerned as much with controls layout as with sizing the dialog frame.) These Sizers are subclasses of the abstract wxSizer base class; that means you should look at the wxSizer documentation to find information on some general Sizer methods, and the subclass documentation to find differences for each subclass.
Sizers are generally pretty simple to use. You construct a Sizer object, call its Add method to add things to it, call the parent Window's SetSizer method to give the Sizer to the parent:
# Construct parent Window on which to put the Sizer $parent = Wx::Frame->new(....); # Construct Sizer $sizer = Wx::GridSizer(1, 2); # Add something to the Sizer $button = Wx::Button($parent, ....); $sizer->Add($button); .... # Position the controls automatically $parent->SetAutoLayout(1); # Put the Sizer on the Window $parent->SetSizer($sizer);
The method Wx::Sizer::Add is used to add things to a Sizer. It has three forms: one adds a Wx::Window, one adds a Wx::Sizer, and one adds some blank space. In the wxPerl source code, file XS/Sizer.xs, these are implemented as Wx_Sizer::AddWindow, Wx_Sizer::AddSizer, and Wx_Sizer::AddSpace. A Button is a type of Window, so it is added with this XS code:
void
Wx_Sizer::AddWindow( window, option = 0, flag = 0, border = 0, data = 0 )
Wx_Window* window
int option
int flag
int border
Wx_UserDataO* data
CODE:
THIS->Add( window, option, flag, border, data );
The first line says that `Add' doesn't return anything. From the parameter list on the second line, we see that `window' is the only required parameter; the remaining arguments default to 0. The `option', `flag', and `border' parameters deal with how the Window gets displayed within the Sizer. We'll go into more details about these later. The XS CODE section tells what C++ code to call, where THIS is the current C++ object. All three versions of `Add' are identical except for the first parameters.
Of course there are other methods beside `Add', but we will get to any of the useful ones later.
Preparation
Before we go on, you need some code to play with. Here I give a template which can be used with all the following code snippets. Name it 'sizers.pl', and make it executable (chmod 755 sizers.pl). I factored out the Sizer-specific code into the subroutine `init_sizer', so just put your Sizer experiments in there.
#!/usr/bin/perl -w
# sizers.pl
use strict;
App->new()->MainLoop();
package App;
use base qw(Wx::App);
use Wx qw(:everything);
sub OnInit {
my $self = shift;
my ($frame, $sizer);
$frame = Wx::Frame->new(
undef, -1, 'A Frame', wxDefaultPosition, wxSIZE(300,200)
);
$sizer = $self->init_sizer($frame);
$frame->SetAutoLayout(1);
$frame->SetSizer($sizer);
$self->SetTopWindow($frame);
$frame->Show(1);
}
sub init_sizer {
my ($self, $frame) = @_;
my ($sizer, $button);
### INSERT SIZER CODE HERE ###
return $sizer;
}
1;
__END__
GridSizers
GridSizers place controls in a uniformly spaced grid. The defining feature of a GridSizer is that each square of the grid is as large as the largest control in the GridSizer. Because of the uniformity of GridSizer makes it relatively simple, I use it to demonstrate most of the common features of all Sizers. Later I show the different behaviors of the other Sizers.
You can find the declaration for Wx::GridSizer in the source, XS/Sizer.xs:
Wx_GridSizer*
Wx_GridSizer::new( rows, cols, vgap = 0, hgap = 0 )
int rows
int cols
int vgap
int hgap
The vgap and hgap (vertical and horizontal gap -- i.e., space between controls) arguments default to 0, while the `rows' and `cols' arguments are required. Note that the `rows' argument is always required (the C++ documentation has a second form of constructor without `rows').
For our first real code example, try out the following by putting it under '### INSERT SIZER CODE HERE ###' in sizers.pl:
$sizer = Wx::GridSizer->new(2, 3);
foreach my $row (1 .. 2) {
foreach my $col (1 .. 3) {
$button = Wx::Button->new($frame, -1, "($row, $col)");
$sizer->Add($button);
}
}
The result is seen in %UploadedFiles:gridsizer-01.png%. The buttons are arranged in two rows, aligned to the top and left of their cells. Try resizing the Frame to observe how the Sizer repositions the Buttons as the Frame width increases or decreases. Note that, if you comment out the line
$frame->SetAutoLayout(1);
in `OnInit', the buttons are placed on top of each other, as shown in %UploadedFiles:gridsizer-02.png%. The default position for a Button is apparently in the upper-left corner of its parent window. According to the documentation, the call to Wx::Window::SetAutoLayout causes the Wx::Window::Layout method to invoke its "sizer-based layout algorithm". (Though, despite what the docs for SetAutoLayout say about Wx::Window::SetSizer calling SetAutoLayout implicitly, I still found it necessary to make an explicit call to SetAutoLayout.)
Let's take a look at how the `flag' parameter affects the layout. It does this in three ways: 1) aligning of the controls within their own cells, 2) resizing controls within their cells, and 3) padding the edges of the controls with a border.
Aligning
Each control has a position, which is the location of its upper-left corner within its parent Window. Aligning a control consists of moving its position around so that it will be in along one of the corners or sides of its own cell. The default case alignment is in the upper-left corner of the cell. The flags associated with the default alignment are wxALIGN_LEFT|wxALIGN_TOP. Let's look at the opposite case, then:
$sizer = Wx::GridSizer->new(2, 3);
foreach my $row (1 .. 2) {
foreach my $col (1 .. 3) {
$button = Wx::Button->new($frame, -1, "($row, $col)");
$sizer->Add($button, 0, wxALIGN_BOTTOM|wxALIGN_RIGHT);
}
}
The result is shown in %UploadedFiles:gridsizer-03.png%. You can tell that the GridSizer has divided the Frame into equal-sized cells which resize as the Frame's border is resized. Then, using the flags wxALIGN_LEFT, wxALIGN_TOP, wxALIGN_RIGHT, wxALIGN_BOTTOM, and wxALIGN_CENTRE, you can align the buttons within their cells.
Resizing
Another useful thing you can do with the `flag' argument to `Add' is to resize the control itself. This resizing is in addition to the resizing of the cells. The wxGROW flag causes each Button to fill its cell: Here's an example of wxGROW on every other cell:
$sizer = Wx::GridSizer->new(2, 3);
foreach my $row (1 .. 2) {
foreach my $col (1 .. 3) {
$button = Wx::Button->new($frame, -1, "($row, $col)");
if (($row + $col) % 2) {
$sizer->Add($button, 0, wxGROW);
} else {
$sizer->Add($button);
}
}
}
Look at %UploadedFiles:gridsizer-04.png% to see the result. You'll notice that the wxGROW flag makes a Button fill its own cell, but that doesn't affect any of the other evenly spaced, square cells.
Another way to resize the controls in a Sizer is with the wxSHAPED flag. This flag does "proportional" resizing.
$sizer = Wx::GridSizer->new(2, 3);
foreach my $row (1 .. 2) {
foreach my $col (1 .. 3) {
$button = Wx::Button->new($frame, -1, "($row, $col)");
if (($row + $col) % 2) {
$sizer->Add($button, 0, wxSHAPED);
} else {
$sizer->Add($button);
}
}
}
As you can see from %UploadedFiles:gridsizer-05.png% and playing with resizing the frame yourself, each Button expands to fill the horizontal space in its cell, whereas its vertical expansion is proportional to how wide the frame is. You can also set the `flag' argument to wxSHAPED|wxALIGN_CENTER_VERTICAL to cause the resized buttons to be aligned in the center vertically (wxALIGN_CENTER_HORIZONTAL is useless because the buttons take up all of the horizontal space), as seen in %UploadedFiles:gridsizer-06.png%.
Padding
The last thing we can do with the `Add' command is to pad any side of a control with blank space, using the flags wxTOP, wxBOTTOM, wxLEFT, and wxRIGHT. The fourth argument to `Add' determines the size of any border. An example of of padding the top and bottom with a space of 20 is shown here:
$sizer = Wx::GridSizer->new(2, 3);
foreach my $row (1 .. 2) {
foreach my $col (1 .. 3) {
$button = Wx::Button->new($frame, -1, "($row, $col)");
$sizer->Add($button, 0, wxGROW|wxTOP|wxBOTTOM, 20);
}
}
The result is in %UploadedFiles:gridsizer-07.png%.
FlexGridSizers
The cells of a GridSizer are uniform in size. This means that every cell has the width of the widest one and the height of the tallest one in the whole GridSizer. The behavior of a FlexGridSizer is perhaps misleading. While the GridSizer cells will generally resize as you resize the Frame, the cells of a FlexGridSizer tend not to. Each row of cells is as tall as the tallest cell in that particular row, and each column of cells is as wide as the widest cell in its column. Try this example, or look at %UploadedFiles:flexgridsizer-01.png%.
$sizer = Wx::FlexGridSizer->new(2, 3);
my $add_button = sub {
my ($w, $h) = @_;
my $button = Wx::Button->new(
$frame, -1, "($w, $h)", wxDefaultPosition, wxSIZE($w, $h)
);
$sizer->Add($button);
};
$add_button->(90, 30);
$add_button->(70, 40);
$add_button->(50, 60);
$add_button->(50, 80);
$add_button->(50, 40);
$add_button->(80, 70);
If you change the `Add' line to
$sizer->Add($button, 0, wxGROW);
it looks like %UploadedFiles:flexgridsizer-02.png%. The cell sizes are the same, of course, but each button has filled up its cell. In fact, you'll find that the behavior is much the same as GridSizer, so try messing with the alignment, resizing, and padding flags to see whether it matches your expectations or not.
[_BoxSizer and StaticBoxSizer not finished_]
