Search the Catalog
Learning Perl/Tk: Graphical User Interfaces with Perl

Learning Perl/Tk: Graphical User Interfaces with Perl

By Nancy Walsh
1st Edition January 1999
1-56592-314-6, Order Number: 3146
376 pages, $32.95

Sample Chapter 2:

Geometry Management

To display widgets on the screen, they must be passed to a geometry manager. The geometry manager controls the position and size of the widgets in the display window. There are several geometry managers available with Perl/Tk: pack, place, and grid.

All three geometry managers are invoked as methods on the widget, but they all have their own methodologies and arguments to change where and how the widgets are put on the screen:

$widget1->pack(); $widget2->place(); $widget3->grid();

When you organize the widgets in your window, it is often necessary to separate groups of widgets to get a certain look and feel. For instance, when you use pack(), it is difficult to have widgets stacked both horizontally and vertically without grouping them in some fashion. We group widgets by using a frame widget inside a window or by using another window (a toplevel widget).

We create our first window by calling MainWindow. The MainWindow is a special form of a toplevel widget. For more detailed information on how to create/configure frames and toplevel widgets, see Chapter 12, Frames, and Chapter 13, Toplevel Widgets.

Because of the differences between the three geometry managers, it is difficult (not entirely impossible, but definitely not recommended) to use more than one geometry manager within the same area. In our $mw, I can display many types of widgets, but if I start using pack(), I should continue to use pack() on all of the widgets contained directly in $mw. I wouldn't want to switch in the middle to using grid(). Because a window can contain a frame, which in turn contains other widgets, we use pack() to pack the frame inside the main window and then we could use grid() to manage the widgets inside the frame. See Figure 2-1.

Figure 2-1. Frame within a window that uses a different geometry manager

 

Although the different geometry managers have their own strengths and weaknesses, the most commonly used is pack(), so I'll discuss it first and in the most detail. The grid() geometry manager was under development as I was writing this book. grid has been improved greatly with the release of Tk 8.0 and subsequent porting to Perl. The place() geometry manager is the most tedious to use because you have to determine exact coordinates for every single widget.

Pack

Remember when you were a small child and you had those wooden puzzles to put together? They often had cute little pictures of animals on them. Each piece in the puzzle had exactly one place where it could go, and there weren't any overlaps allowed between pieces.

With the pack geometry manager, our windows are similar to the wooden puzzle because widgets cannot overlap or cover each other (partially or completely). See Figure 2-2. If a button is packed in a certain space on the window, the next button (or any widget) will have to move around the already packed button. Luckily, our windows will only be dealing with rectangular shapes instead of funny-shaped puzzle pieces.

Figure 2-2. Overlap error

 

The order in which you pack your widgets is very important because it directly affects what you see on the screen. Each frame or toplevel maintains a list of items that are displayed within it. This list has an order to it; if widget A is packed before widget B, then widget A will get preference. This will become clear as we go through some examples. You will often get a very different look to your window just by packing the widgets in a different order.

If you don't care what the window looks like and how the widgets are put in it, you can use pack() with no arguments and skip the rest of this chapter. Here it is again:

$widget->pack();

To make your window look nicer and more manageable (and user friendly), there are arguments that can be sent to the pack method that will change the way the widgets and the window looks. As with anything in Perl/Tk, the arguments are arranged in pairs. So the more sophisticated usage would be:

$widget->pack( [ option => value, ... ] );

Here is the code to create a window that doesn't use any options to pack(). Figure 2-3 shows the resulting window (I know we haven't covered all the widgets used in this example, but hang in there, it's pretty simple).

#!/usr/bin/perl -w
use Tk;
 
my $mw = MainWindow->new;
$mw->title("Bad Window");
$mw->Label(-text => "This is an example of a window that looks bad\nwhen you don't send any options to pack")->pack;
 
$mw->Checkbutton(-text => "I like it!")->pack;
$mw->Checkbutton(-text => "I hate it!")->pack;
$mw->Checkbutton(-text => "I don't care")->pack;
$mw->Button(-text => "Exit",
            -command => sub { exit })->pack;
MainLoop;
Figure 2-3. Window with widgets managed by pack

 

We can alter the preceding code and add some options to the pack() calls that will make our window look much nicer:

#!/usr/bin/perl -w
use Tk;
 
my $mw = MainWindow->new;
$mw->title("Good Window");
$mw->Label(-text => "This window looks much more organized, and less haphazard\nbecause we used some options to make it look nice")->pack;
 
$mw->Button(-text => "Exit",
            -command => sub { exit })->pack(-side => 'bottom', 
                                            -expand => 1,
                                            -fill => 'x');
$mw->Checkbutton(-text => "I like it!")->pack(-side => 'left',
                                              -expand => 1);
$mw->Checkbutton(-text => "I hate it!")->pack(-side => 'left', 
                                              -expand => 1);
$mw->Checkbutton(-text => "I don't care")->pack(-side => 'left', 
                                                -expand => 1);
MainLoop; 

Figure 2-4 shows the much more organized window.

Figure 2-4. Window with widgets managed by pack using some options

 

Using pack() allows you to control:

The options, values, and defaults are listed and discussed in the following section.

Pack Options

This list shows all the options available when you call pack(). The default values are shown in bold (which indicates if you don't use that option, you'll get the effects of that value for that option).

-side => 'left' | 'right' | 'top' | 'bottom'

Puts the widget against the specified side of the window or frame

-fill => 'none' | 'x' | 'y'| 'both'

Causes the widget to fill the allocation rectangle in the specified direction

-expand => 1 | 0

Causes the allocation rectangle to fill the remaining space available in the window or frame

-anchor => 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw' | 'center'

Anchors the widget inside the allocation rectangle

-after => $otherwidget

Puts $widget after $otherwidget in packing order

-before => $otherwidget

Puts $widget before $otherwidget in packing order

-in => $otherwindow

Packs $widget inside of $otherwindow rather than the parent of $widget, which is the default

-ipadx => amount

Increases the size of the widget horizontally by amount ✕ 2

-ipady => amount

Increases the size of the widget vertically by amount ✕ 2

-padx => amount

Places padding on the left and right of the widget

-pady => amount

Places padding on the top and bottom of the widget

Positioning Widgets

Each window (or frame) has four sides to it: top, bottom, left, and right. The packer uses these sides as points of reference for widgets. By default, pack() places the widgets against the top of the toplevel or frame.

You can control what side a widget is placed against by using the -side option:

-side =>  'left' | 'right' | 'top' | 'bottom' 

For example, if we would like our button against the left edge of the window, we can specify -side => 'left'.

Using our Hello World example as a base, let's look at what happens when we pack our button against the different sides. The only line we will change is the ->pack part of the Button creation line. We'll also change the "Hello World" string in the $mw->title command to easily show the new options to pack.

$mw->Button(-text => 'Done', 
  -command => sub { exit })
->pack(-side => 'top');
->pack;

OR

$mw->Button(-text => 'Done', 
  -command => sub { exit })
->pack;

$mw->Button(-text => 'Done', 
  -command => sub { exit })
->pack(-side => 'bottom');

$mw->Button(-text => 'Done', 
  -command => sub { exit })
->pack(-side => 'left');

$mw->Button(-text => 'Done', 
  -command => sub { exit })
->pack(-side => 'right');

 

The windows shown here have been made a bit larger to emphasize the difference that using alternative values for -side makes. Normally, the window will be only as large as required to show the button. When you are deciding which way to place widgets in a window, it is always a good idea to see what happens when you make the window both larger and smaller. Make sure the behavior you get is what you want.

So far, pack() seems pretty simple, but what if you want to put more than one button in your application? What happens when we simply add more buttons?

$mw->Button(-text => 'Done1', -command => sub { exit })->pack;
$mw->Button(-text => 'Done2', -command => sub { exit })->pack;
$mw->Button(-text => 'Done3', -command => sub { exit })->pack;
$mw->Button(-text => 'Done4', -command => sub { exit })->pack;

Since the default -side is top, we would expect them to all be mushed up against the top of the window, right? Sort of. The packer allocates space for each widget and then manipulates the widget inside that space and the space inside the window.

Figure 2-5 shows what the window with the four Done buttons looks like; the next section explains why.

Figure 2-5. Four buttons packed with default settings

 

Allocation Rectangles

When given an item to pack, the packer first looks to see which side (top, bottom, right, or left) to use. It then sets aside an invisible rectangular area across the length of that side for use only by that widget.

In Figure 2-6, the solid-line rectangle represents our empty window (or frame), and the dotted-line rectangle is the rectangular area that the packer sets aside for the first button. It actually does go all the way across the width or height of the window, but to make it easier to see, it's shown a little indented.

Figure 2-6. Rectangular areas set aside by packer when using -side => `top' and -side => `left'

 

The dimensions for the dotted-line box, which we'll call the allocation rectangle, are calculated based on the size of the requesting widget. For both the top and bottom sides, the allocation rectangle is as wide as the window and only as tall as the widget to be placed in it. For the right and left sides, the allocation rectangle is as tall as the window but only as wide as required to fit the widget.

Our examples so far have used buttons in which the text of the button determines the width of the button. If we create a button with the text "Done" on it and one with the text "Done, Finished, That's it," the second button is going to be much wider than the first. When these two buttons are placed up against either the right or left side of the window, the second button would have a wider allocation rectangle than the first. If we placed those same two buttons against the top and the bottom, the allocation rectangles would be the same height and width because the width is determined by the window, not the widget.

After the size of the allocation rectangle is determined, the widget is placed within the allocation rectangle according to other options passed and/or the default values of those options. I will go over those options and how they can affect the allocation rectangle later.

Once the first widget has been placed in the window, the amount of area available for subsequent allocation rectangles is smaller because the first allocation rectangle has used some of the space (see Figure 2-7).

Figure 2-7. Second allocation rectangle when default side `top' is used

 

When more than one button is placed against different sides in the same window, the results will vary depending on the order used.

We'll start by placing one button along the top, one along the bottom, and then buttons right and left:

$mw->Button(-text => "TOP", -command => sub { exit })
   ->pack(-side => 'top');
 
$mw->Button(-text => "BOTTOM", -command => sub { exit })
   ->pack(-side => 'bottom');
 
$mw->Button(-text => "RIGHT", -command => sub { exit })
   ->pack(-side => 'right');
 
$mw->Button(-text => "LEFT", -command => sub { exit })
  ->pack(-side => 'left');

The allocation rectangles for this window would look like the diagram in Figure 2-8.

Figure 2-8. Allocation rectangles for four buttons

 

Figure 2-9 shows what the actual window looks like, both normal size and resized so it's a bit larger.

Figure 2-9. Four buttons placed around the sides of the window

 

Filling the Allocation Rectangle

Normally, the widget is left at the default size, which is usually smaller than the allocation rectangle created for it. If the -fill option is used, the widget will resize itself to fill the allocation rectangle according to the value given. The possible values are:

-fill => 'none' | 'x' | 'y' | 'both'

Using the value 'x' will resize the widget in the x direction. Likewise, 'y' will cause the widget to resize in the y direction. Using -fill => 'both' is a good way to see exactly what size and placement was given to the allocation rectangle because 'both' resizes the widget in both x and y directions. Using our four-button example again, we'll specify -fill => 'both'.

$mw->Button(-text => "TOP", -command => sub { exit })
  ->pack(-side => 'top', -fill => 'both');
  
$mw->Button(-text => "BOTTOM", -command => sub { exit })
  ->pack(-side => 'bottom', -fill => 'both');
 
$mw->Button(-text => "RIGHT", -command => sub { exit })
  ->pack(-side => 'right', -fill => 'both');
 
$mw->Button(-text => "LEFT", -command => sub { exit })
  ->pack(-side => 'left', -fill => 'both');

Figure 2-10 shows the resulting window.

Figure 2-10. Four buttons packed to each side using -fill => `both'

 

If we switch the button we create first, we get a different result. The window in Figure 2-11 was created by packing the widgets in this order: left, right, top, bottom.

Figure 2-11. Four buttons packed to each side in a different order using -fill => `both'

 

Figure 2-12 demonstrates yet another order, which really shows that the allocation rectangles change size depending on what gets packed first.

Figure 2-12. Four buttons packed in order of top, right, bottom, and left

 

A common use of -fill is on widgets with scrollbars: listbox, canvas, and text. Usually, the scrollbars are along the edge of the window, and you want the listbox to fill the remaining area. See Chapter 6, Scrollbars, and Chapter 7, The Listbox Widget, for more information.

Expanding the Allocation Rectangle

The -expand option manipulates the allocation rectangle and not the widget inside it. The value associated with -expand is a boolean value.

-expand => 1 | 0

Given a true value, the allocation rectangle will expand into any available space left over in the window depending on which side the widget was packed.

Widgets packed with side right or left will expand in the horizontal direction. Widgets packed with side top or bottom will expand in the vertical direction. If more than one widget is packed with -expand turned on, the extra space in the window is divided evenly among all the allocation rectangles that want it.

In Figure 2-9 or See , you saw that there was some space left in the center of the window that wasn't occupied by any widget. If we change the code and add -expand => 1 to the list of pack options for each button, the result is the window in Figure 2-13.

Figure 2-13. Four buttons using the -expand => 1 and -fill => `both' options

 

Note that Figure 2-13 left the -fill => 'both' option in the code. If we omit the -fill option, the buttons stay their original size, but the allocation rectangles (which are invisible) take over the extra space in the window (see Figure 2-14).

Figure 2-14. Four buttons using -expand => 1 and -fill => `none'

 

In Figure 2-14, the buttons are centered in their allocation rectangles because of the default value of the -anchor option, which is 'center'.

Anchoring a Widget in Its Allocation Rectangle

The anchor option manipulates the widget inside the allocation rectangle by anchoring it to the place indicated by the value passed in. It uses the points of a compass as a reference.

-anchor =>  'e' | 'w' | 'n' | 's' | 'ne' | 'nw' | 'se' | 'sw' | 'center'

Figure 2-15 shows those locations in an example allocation rectangle.

Figure 2-15. Allocation rectangle with -anchor points labeled

 

The default for -anchor is 'center', which keeps the widget in the center of its allocation rectangle. Unless the -expand option is set to a true value, this won't seem to change much of anything in the window. As seen in Figure 2-16, which shows the result of using the -expand => 1 option, it is obvious that the widget sticks to that center position when the window is resized.

Figure 2-16. Default behavior of -anchor with -expand set to 1

 

If all other defaults are used to pack the widget, Figure 2-17 shows what -anchor => 'e' and -anchor => 'w' does.

Figure 2-17. Examples of -anchor => `e' and -anchor => `w'

 

Remember that the allocation rectangle is created based on which side the widget is packed against, so certain combinations will appear to have not had any effect. For example:

$mw->Button(-text => "Done", -command => sub { exit })
  ->pack(-side => 'top', -anchor -> 'n');

This code fragment will leave the widget exactly where it was if the -anchor option had not been specified because the allocation rectangle does not change size at all. If the -expand option is also specified, then when the window is resized, the widget would stick to the north side of the window. If -anchor => 's' had been specified, when the window is resized, the widget would stick to the south side of the window.

The -anchor option is more often used to line up several widgets in a row. Figure 2-18 and Figure 2-19 show two common examples.

Figure 2-18. Window with three buttons all packed with -side => `top', -anchor => `w'

 

Figure 2-19. Windows with three buttons all packed with -side => `left', -anchor => `n'

 

Sometimes, when -side and -anchor are used together, the results don't seem to be what you would expect at first glance. Always keep in mind that invisible allocation rectangle and how it affects what you see on the screen.

Widget Order in the Window

Each window that has widgets packed into it keeps track of those widgets in an ordered list. The order of this list is normally determined by the order in which the widgets were packed. The last item packed is the last item in the list. Using the -after option, you can change the default order by specifying which widget should be placed after your new widget. On the opposite end, if you use the -before option, you can put the new widget before a previously packed widget:

-after => $otherwidget
-before => $otherwidget

As an example, let's create four buttons ($widget1, $widget2, $widget3, $widget4) and only pack three to begin with. The pack command for $widget4 might then be:

$widget4->pack(-after => $widget1);

Figure 2-20 shows two windows: one before $widget4 is packed and one after $widget4 is packed.

Figure 2-20. On left: the window with three buttons packed in order. On right: the button with Done4 label was packed using -after => $widget1

 

If we want to put $widget4 in front of $widget1, we use this command, and see the results in Figure 2-21.

$widget4->pack(-before => $widget1);
Figure 2-21. Button with Done4 label was packed using -before => $done1

 

Padding the Size of the Widget

The final way to force pack to size the widget is to use the padding options. The first set of padding options affects the widget itself by adding to its default size. Different amounts can be added in the x and y direction, or they can be the same. To specify how much padding should occur in the x direction, use the -ipadx option:

-ipadx => amount

Specify padding for the y direction like this:

-ipady => amount

The amount is a number that is a valid screen distance. I'll discuss the definition of a valid screen distance in the next section.

Both the -ipadx and -ipady options change the size of the widget before the allocation rectangle is calculated. -ipadx adds the amount specified to both the right and left sides of the widget. The overall width of the widget would increase by (2 x amount). -ipady adds to the top and bottom of the widget, causing the overall height of the widget to increase by (2 x amount). Figure 2-22 shows how the -ipadx and -ipady options affect a button.

Figure 2-22. The Done1 button was created with options: -ipadx => 10, -ipady => 10

 

The other kind of padding is inserted between the edge of the widget and the edge of the allocation rectangle and is done with the -padx and -pady options:

-padx => amount
-pady => amount

Using -padx and -pady does not affect the size of the widget, but it does affect the size of the allocation rectangle. It acts as a buffer around the widget, protecting it from having to touch other widgets. Figure 2-23 shows the effects of using -padx and -pady.

Figure 2-23. The Done1 button was created with options -padx => 10, -pady => 10

 

A good way to remember the difference between -ipadx/y and -padx/y is that the "i" stands for "inside the widget" or "internal padding."

Valid screen distances

Many times you'll see options that require values specified in screen units (or what is called a valid screen distance). The options -ipadx and -ipady are examples of this type of option. Always check to see what value the option actually requires.

A screen unit is a number followed by a designation for the unit to use. If there is no designation, the units are in pixels. Table 2-1 shows all the possibilities.

Table 2-1: Valid screen units

Designator

Meaning

Examples

(none)

Pixels (default)

20, 30, "20", "40"

c

Centimeters

'3c', '4c', "3c"

i

Inches

'2i', "3i"

m

Millimeters

'4m', "4m"

p

Printer points (1/72 inch)

"72p", '40p'

To use these designators, it is necessary to use quotes (either single or double) around the value. Here are some examples:

$button->pack(-ipadx => 20);      # 20 pixels
$button->pack(-ipadx => '20');    # Also 20 pixels
$button->pack(-ipadx => "1i");    # 1 inch
$button->pack(-ipadx => '1m');    # 1 millimeter
$button->pack(-ipadx => 1);       # 1 pixel
$button->pack(-ipadx => "20p");   # 20 printer points

Remember that a "p" designator does not stand for pixels, but printer points. I recommend always using pixels as your unit of measure. Different screens display different resolutions; one screen might display an actual inch and another might display something else.

Displaying in a Parent Other Than Your Own

By default, when a widget is packed, it is packed inside the region that created it. Sometimes it is necessary to display a widget inside a different region. Use the -in option to do so:

-in => $otherwindow

It puts the new widget at the end of the packing order for the $otherwindow and displays it accordingly. All other options specified in the pack() call still apply.

Methods Associated with Pack

There are a few methods that are used in conjunction with the pack geometry manager. They allow the programmer to get information about either the widget that has been packed or the parent widget in which other widgets are packed.

Unpacking a widget

To unpack a widget from a window or frame, use the packForget method:

$widget->packForget();

packForget makes it look like the widget disappears. The widget is not destroyed, but it is no longer managed by pack. The widget is removed from the packing order, so if it were repacked later, it would appear at the end of the packing order.

Retrieving pack information

To return a list containing all the pack-configuration information about a widget, use packInfo:

@list = $widget->packInfo();

The format of the list is in option/value pairs. The first pair in the list is -in and the current window that contains $widget (usually also the parent). This is an example of the information returned from packInfo:

-in MainWindow=HASH(0x818dcf4) -anchor n -expand 0 -fill none -ipadx 0 -ipady 0 -padx 10 -pady 10 -side left

From this, we can tell that we packed our $widget into the main window rather than a frame. Since the list has a "paired" quality to it, we could easily store the result from packInfo in a hash and reference the different option values by using a key to the hash:

%packinfo = $widget->packInfo;
print "Side used: ", $packinfo{-side}, "\n";

Disabling and enabling automatic resizing

When you put a widget inside a window, the window (or frame) will resize itself to accommodate the widget. If you are dynamically placing widgets inside your window while the program is running, the window will seem to bounce from size to size. You can turn off this behavior by using packPropagate on the frame or toplevel widget:

$widget->packPropagate(0);

If set to 0 or 'off', packPropagate changes the behavior of the widget so that it doesn't resize to accommodate items packed inside of it. When a false value is sent to packPropagate before widgets are placed inside it, this automatic resizing doesn't happen, so you can't see any of the widgets placed inside the parent until it is manually resized. If you call packPropogate after the widgets have been placed inside it, the widget will ignore any size changes from its child widgets.

Listing widgets

You can determine the widgets your frame or toplevel holds with the packSlaves method:

@list = $parentwidget->packSlaves();

packSlaves returns an ordered list of all the widgets that were packed into the $parentwidget. An empty string (or empty list) is returned if no widgets were packed into $parentwidget.

The list returned from packSlaves looks like this:

Tk::Button=HASH(0x81b2970) Tk::Button=HASH(0x8116ccc) Tk::Button=HASH(0x81bcdd4)

Each item is a reference to a packed widget and can be used to configure it. For example, you can increase the size of each widget by 20 in both the x and y directions by looping through it and "packing" it with new information. Using our good window example in Figure 2-4, we can add a button that will contain a subroutine that uses packSlaves:

$mw->Button(-text => "Enlarge",
            -command => \&repack_kids)->pack(-side => 'bottom',
                                             -anchor => 'center');
sub repack_kids {
  my @kids = $mw->packSlaves;
  foreach (@kids) {
    $_->pack(-ipadx => 20, -ipady => 20);
  }
}

Figure 2-24 shows the resulting window.

Figure 2-24. Window before pressing Enlarge button

 

Let's look at what happens when we press the Enlarge button. As shown in Figure 2-25, all the widgets are now repacked with additional parameters of -ipadx => 20, -ipady => 20. These new options are in addition to any other parameters the widgets were packed with before. If an option is repeated, the last one specified overrides the previous ones.

Figure 2-25. Window after pressing Enlarge button

 

The window is suddenly huge! Subsequent presses of the Enlarge button will do nothing more to the window because all the widgets already have an -ipadx and -ipady of 20. If we wanted to always add 20 to the values of -ipadx and -ipady, we would have to request the current values and add 20 to them. Here's the code for that:

sub repack_kids {
  my @kids = $mw->packSlaves;
  foreach (@kids) {
    %packinfo = $_->packInfo();
    $_->pack(-ipadx => 20 + $packinfo{"-ipadx"}, 
             -ipady => 20 + $packinfo{"-ipady"});
  }
}

We use packInfo to get the current configuration and add 20 to that value.

Grid

The grid geometry manager divides the window into a grid composed of columns and rows starting at 0,0 in the upper left-hand corner. Figure 2-26 shows a sample grid.

Figure 2-26. Diagram showing window divided into grid

 

Rather than using the sides of a window as reference points, grid() divides the screen into columns and rows. It looks a lot like a spreadsheet doesn't it? Each widget is assigned a grid cell using the options available to grid().

The grid() method takes a list of widgets instead of operating on only one widget at a time.[1] Here is the generic usage:

$widget1->grid( [ $widget2, ... , ] [ option => value, ... ] );

A specific example:

$widget1->grid($widget2, $widget3);

Instead of using three separate calls, you can use one grid() call to display all three widgets. You can also invoke grid() on each widget independently just as you can pack(). Each call to grid() will create another row in the window. So in our example, $widget1, $widget2, and $widget3 will be placed in the first row. Another call to grid would create a second row. This is what happens when you do not specify any additional options to the grid() call.

For greater control, you can specify explicit -row and -column options for each widget in the window. I will cover these options later.

These assumptions are made when additional options are not specified:

A few examples will help demonstrate. Each call to grid() will create another row, so we know we have two rows in the following example:

# Create two rows, each with four widgets
$widget1->grid($widget2, $widget3, $widget4);
$widget5->grid($widget6, $widget7, $widget8);

In this example, we have created four rows and there is only one widget in each row:

# Create four rows, each with one widget
$widget1->grid();
$widget2->grid();
$widget3->grid();
$widget4->grid();

We can also create widgets as we go:

$mw->Button(-text => 'Button1', -command => \&call1)->grid(
            $mw->Button(-text => 'Button2', -command => \&call2),
            $mw->Button(-text => 'Button3', -command => \&call3),
            $mw->Button(-text => 'Button4', -command => \&call4));

Pay careful attention because the second, third, and fourth calls to Button are inside the call to grid(). All four of the buttons will be placed in the first row. If we executed the same exact command again, the new widgets would be placed in the next row.

Special Characters

There are several special characters that can be used to alter the way the widgets are gridded in the window. Each special character serves as a type of placeholder that indicates what to do with that position in the grid:

"-" (a minus sign)
Tells grid that the widget specified right before this one in the list should span this column as well. To span more than one column, place a "-" in each widget position to span. A "-" may not follow a "^" or an "x".
"x"
Effectively leaves a blank space where a widget would otherwise be placed.
"^"
A widget in row x will span row x and x + 1 when this character is placed in the grid command for row x + 1 in that row/column position. The number of "^" characters must match the number of columns the widget spans in row x. Similar to "-", but goes down, not across.[2]

The following sections include some examples that illustrate what the special characters do.

Spanning columns

The following bit of code creates three rows of buttons. The first two rows are normal, and in the third, the second button spans three columns. Each "-" character adds one to the number of columns the button uses, and the default is 1. So the original column and two hyphens ("-","-") indicate that there are three columns to span. The -sticky option is necessary for the widgets to stick to the sides of the cells it spans. If the -sticky option had been left out, the button would be centered across the three cells it spans.

$mw->Button(-text => "Button1", -command => sub { exit })->grid
  ($mw->Button(-text => "Button2", -command => sub { exit }),
   $mw->Button(-text => "Button3", -command => sub { exit }),
   $mw->Button(-text => "Button4", -command => sub { exit }));
 
$mw->Button(-text => "Button5", -command => sub { exit })->grid
  ($mw->Button(-text => "Button6", -command => sub { exit }),
   $mw->Button(-text => "Button7", -command => sub { exit }),
   $mw->Button(-text => "Button8", -command => sub { exit }));
 
 
$mw->Button(-text => "Button9", -command => sub { exit })->grid
  ($mw->Button(-text => "Button10", -command => sub { exit }),
   "-", "-",  -sticky => "nsew");

The resulting window is shown in Figure 2-27.

Figure 2-27. Example of column spanning using the "-" character

 

Empty cells

The "x" character translates to "skip this space" and leaves a hole in the grid. I removed the line that created Button6 and replaced it with an "x" in the following code. The cell for it is still there, it just doesn't contain a widget.

$mw->Button(-text => "Button1", -command => sub { exit })->grid
  ($mw->Button(-text => "Button2", -command => sub { exit }),
   $mw->Button(-text => "Button3", -command => sub { exit }),
   $mw->Button(-text => "Button4", -command => sub { exit }));
  
$mw->Button(-text => "Button5", -command => sub { exit })->grid
  ("x",
   $mw->Button(-text => "Button7", -command => sub { exit }),
   $mw->Button(-text => "Button8", -command => sub { exit }));

The resulting window is shown in Figure 2-28.

Figure 2-28. Leaving an empty cell between widgets

 

Grid Options

The rest of the options are similar to those used with pack():

"-"

Special character used in grid widget list. Increases columnspan of widget prior to it in widget list.

"x"

Special character used in grid widget list. Leaves a blank space in the grid.

"^"

Special character used in grid widget list. Increases rowspan of the widget in the grid directly above it.
-column => n
Sets the column to place widget in (n >= 0).
-row => m
Sets the row to place widget in (m >= 0).
-columnspan => n
Sets the number of columns for the widget to span beginning with -column.
-rowspan => m
Sets the number of rows for the widget to span beginning with -row.
-sticky => string
String contains characters n, s, e, or w. Widget will stick to those sides.

-in => $otherwindow

Indicates that widget is gridded inside $otherwindow instead of parent of $widget.
-ipadx => amount
$widget becomes larger in x direction by 2 ✕ amount.
-ipady => amount
$widget becomes larger in y direction by 2 ✕ amount.
-padx => amount
Buffer space equal to amount is placed to left and right of widget.
-pady => amount
Buffer space equal to amount is placed on top and bottom of widget.

Explicitly Specifying Rows and Columns

Rather than letting grid() make assumptions, it is sometimes necessary to explicitly state the row and column in which the widget should be placed. This is done by using the -row and -column options. Each option takes a nonnegative integer as an argument:

-column => n, -row => m

When you use -row and -column, it is not necessary to build or grid() the widgets in any sort of logical order (except for your own sanity when you are debugging). You could place your first widget in column 10 and row 5 if you like. All of the other cells with lower row and column values will remain empty.

Explicitly Spanning Rows and Columns

It is also possible to explicitly indicate that a widget (or widgets) should span some columns or rows. The option to use to span columns is -columnspan. For spanning rows, the option is -rowspan. Both options take an integer that is 1 or greater. The value indicates how many rows or columns should be spanned, including the row or column in which the widget is placed.

For this example, I have used the easy way to place widgets in columns and rows by not explicitly specifying the -row and -column options. Note that the second grid command applies to two button widgets, so the single -columnspan option applies to both buttons created there.

$mw->Button(-text => "Button1", -command => sub { exit })->grid
  ($mw->Button(-text => "Button2", -command => sub { exit }),
   $mw->Button(-text => "Button3", -command => sub { exit }),
   $mw->Button(-text => "Button4", -command => sub { exit }),
   -sticky => "nsew");
 
# Button5 will span Columns 0-1 and Button6 will span 2-3
$mw->Button(-text => "Button5", -command => sub { exit })->grid
  ($mw->Button(-text => "Button6", -command => sub { exit }),
   -sticky => "nsew", -columnspan => 2);

The resulting window is shown in Figure 2-29.

Figure 2-29. -columnspan example

 

This window could also have been created using the "-" special character to indicate column spanning, like this:

$mw->Button(-text => "Button1", -command => sub { exit })->grid
  ($mw->Button(-text => "Button2", -command => sub { exit }),
   $mw->Button(-text => "Button3", -command => sub { exit }),
   $mw->Button(-text => "Button4", -command => sub { exit }),
   -sticky => "nsew");
 
# Button5 will span Columns 0-1 and Button6 will span 2-3
$mw->Button(-text => "Button5", -command => sub { exit })->grid
  ("-", $mw->Button(-text => "Button6", -command => sub { exit }), "-"
   -sticky => "nsew");

This example illustrates how to explicitly use the -row and -column options in addition to the -rowspan option:

$mw->Button(-text => "Button1", -command => sub { exit })->
  grid(-row => 0, -column => 0, -rowspan => 2, -sticky => 'nsew');
$mw->Button(-text => "Button2", -command => sub { exit })->
  grid(-row => 0, -column => 1);
$mw->Button(-text => "Button3", -command => sub { exit })->
  grid(-row => 0, -column => 2);
$mw->Button(-text => "Button4", -command => sub { exit })->
  grid(-row => 0, -column => 3);
 
$mw->Button(-text => "Button5", -command => sub { exit })->
  grid(-row => 1, -column => 1);
$mw->Button(-text => "Button6", -command => sub { exit })->
  grid(-row => 1, -column => 2);
$mw->Button(-text => "Button7", -command => sub { exit })->
  grid(-row => 1, -column => 3);

See Figure 2-30 for the resulting window.

Figure 2-30. Explicit -rowspan example

 

Forcing a Widget to Fill the Cell

When you use the pack() command, it is necessary to indicate both -fill and -expand options to get the widget to resize inside its allocation rectangle. The grid() command doesn't have an allocation rectangle to fill, but it does have the cell within the grid. Using the -sticky option with grid() is similar to using -fill and -expand with pack().

The value associated with -sticky is a string containing the compass points to which the widget should "stick." If the widget should always "stick" to the top of the cell, you would use -sticky => "n". To force the widget to fill the cell completely, use -sticky => "nsew". To make the widget as tall as the cell but only as wide as it needs to be, use -sticky => "ns". The string value can contain commas and whitespace, but they will be ignored. These two statements are equivalent:

-sticky => "nsew"
-sticky => "n, s, e, w"  # Same thing

If you use -sticky with your widgets and then resize the window, you'll notice that the widgets don't resize as you think they should. They don't because resizing of the cells and the widgets in them is taken care of with the gridColumnconfigure and gridRowconfigure methods, which are discussed later in this chapter.

Padding the Widget

grid() also accepts these four options: -ipadx, -ipady, -padx, -pady. They work exactly the same as they do in pack(), but instead of affecting the size of the allocation rectangle, they affect the size of the cell in which the widget is placed.

In this example, the -ipady and -ipadx options are applied to the top row of buttons and not the bottom row. Notice in Figure 2-31 how Buttons 5 through 8 are also wider than they really need to be. This is because we used the -sticky => "nsew" option.

$mw->Button(-text => "Button1", -command => sub { exit })->grid
  ($mw->Button(-text => "Button2", -command => sub { exit }),
   $mw->Button(-text => "Button3", -command => sub { exit }),
   $mw->Button(-text => "Button4", -command => sub { exit }),
   -sticky => "nsew", -ipadx => 10, -ipady => 10);
 
$mw->Button(-text => "Button5", -command => sub { exit })->grid
  ($mw->Button(-text => "Button6", -command => sub { exit }),
   $mw->Button(-text => "Button7", -command => sub { exit }),
   $mw->Button(-text => "Button8", -command => sub { exit }),
   -sticky => "nsew");
Figure 2-31. grid -ipadx and -ipady example

 

In this example, the -pady and -padx options are applied to the top row of buttons and not the bottom row. Figure 2-32 shows the results.

$mw->Button(-text => "Button1", -command => sub { exit })->grid
  ($mw->Button(-text => "Button2", -command => sub { exit }),
   $mw->Button(-text => "Button3", -command => sub { exit }),
   $mw->Button(-text => "Button4", -command => sub { exit }),
   -sticky => "nsew", -padx => 10, -pady => 10);
 
$mw->Button(-text => "Button5", -command => sub { exit })->grid
  ($mw->Button(-text => "Button6", -command => sub { exit }),
   $mw->Button(-text => "Button7", -command => sub { exit }),
   $mw->Button(-text => "Button8", -command => sub { exit }),
   -sticky => "nsew");
Figure 2-32. grid -padx and -pady example

 

Specifying a Different Parent

The -in option works the same way it does in pack(). The $widget will be placed in $otherwindow and not in the default parent of $widget. Here is the usage:

-in => $otherwindow

Configuring Columns and Rows

As with any of the geometry managers, grid has a few methods that are associated with it. Each method is invoked via a widget that has been placed on the screen by using grid(). Sometimes it is necessary to change the options of the group of cells that makes up your grid.

You can control resizing and the minimum size of a cell with the gridColumnconfigure and gridRowconfigure methods. Each takes a column or a row number as its first argument and then takes some optional arguments that will change the configuration of that column or row.

Both gridColumnconfigure and gridRowconfigure work very similar to the configure method used with widgets. Unlike the configure method used with widgets, however, the options you can specify with gridColumnconfigure and gridRowconfigure cannot be used with the grid() command. The options you can use with gridColumnconfigure and gridRowconfigure are -weight, -minsize, and -pad.

If you send only a row or column number, an array is returned with the current options and their values for that method:

@column_configs = $mw->gridColumnconfigure(0);
@row_configs = $mw->gridRowconfigure(0);

In this example, we are getting the options and their values for the first column and the first row. The results of using the default values would look like this:

-minsize 0 -pad 0 -weight 0
-minsize 0 -pad 0 -weight 0

You can get the value of only one of the options by sending that option as the second argument:

print $mw->gridColumnconfigure(0, -weight), "\n";
print $mw->gridRowconfigure(0, -weight), "\n";

The results would be:

0
0

To change the value of the options, use the option and then the value you want associated with it immediately after the option; for example:

$mw->gridColumnconfigure(0, -weight => 1);
$mw->gridRowconfigure(0, -weight => 1);

You can also specify multiple options in one call:

$mw->gridColumnconfigure(0, -weight => 1, -pad => 10);
$mw->gridRowconfigure(0, -weight => 1, -pad => 10);

Now that we know how to call gridColumnconfigure and gridRowconfigure, we need to know what the three different options do.

Weight

The -weight option sets how much space is to be allocated to that column or row when the window is divided into cells. Remember to use -sticky => "nsew" in your grid() command if you want the widget to resize when the cell does. The default -weight is 0, which causes the column width or row height to be dictated by the largest widget in the column. Each -weight value has a relationship to the other -weights in the rows or columns.

If a column or row has a -weight of 2, it is twice as big as a column or row that has a -weight of 1. Columns or rows of -weight 0 don't get resized at all. If you want all your widgets to resize in proportion to the size of the window, add this to your code before you call MainLoop:

($columns, $rows) = $mw->gridSize();
for ($i = 0; $i < $columns; $i++) {
  $mw->gridColumnconfigure($i, -weight => 1);
}
for ($i = 0; $i < $rows; $i++) {
  $mw->gridRowconfigure($i, -weight => 1);
}

This code will assign the -weight of 1 to every single row and column in the grid, no matter what size the grid is. Of course, this method only works if you want to assign the same size to each row and each column, but you get the idea.

Here is an example of how the -weight option works (Figure 2-33 shows the result):

$mw->Button(-text => "Button1", -command => sub { exit })->grid
  ($mw->Button(-text => "Button2", -command => sub { exit }),
   $mw->Button(-text => "Button3", -command => sub { exit }),
   $mw->Button(-text => "Button4", -command => sub { exit }),
   -sticky => "nsew");
 
$mw->Button(-text => "Button5", -command => sub { exit })->grid
  ("x",
   $mw->Button(-text => "Button7", -command => sub { exit }),
   $mw->Button(-text => "Button8", -command => sub { exit }),
   -sticky => "nsew");
 
$mw->gridColumnconfigure(1, -weight => 1);
$mw->gridRowconfigure(1, -weight => 1);

By giving row 1 and column 1 a weight of 1 (whereas all other rows and columns have 0 weight), they take over any extra available space when the size of the window is increased. Notice that columns 0, 2, and 3 are only as wide as is necessary to draw the buttons and their text, but column 1 has filled in the extra space. The same effect happens for row 0 with a weight of 0 and row 1 with a new weight of 1. (The window has been resized larger to demonstrate the effects of -weight.)

Figure 2-33. gridRowconfigure and gridColumnconfigure example

 

Minimum cell size

The option -minsize sets the smallest width for the column or the smallest height for each row. The -minsize option takes a valid screen distance as a value. In this example, the minimum size of the cells in row 0 and column 0 is set to 10 pixels:

$mw->gridColumnconfigure(0, -minsize => 10);
$mw->gridRowconfigure(0, -minsize => 10);

If the column or row was normally less than 10 pixels wide, then it would be forced to be at least that large.

Padding

You can add padding around the widget and to the widget by using the -padx/y and -ipadx/y options. You can also add a similar type of padding by using the -pad option with the gridColumnconfigure and gridRowconfigure methods. The padding is added around the widget, not to the widget itself. When you call gridColumnconfigure, the -pad option will add padding to the left and right of the widget. Calling gridRowconfigure with -pad will add padding to the top and bottom of the widget. Here are two examples:

$mw->gridColumnconfigure(0, -pad => 10);
$mw->gridRowconfigure(0, -pad => 10);

Bounding box

To find out how large a cell is, you can use the gridBbox method:

($xoffset, $yoffset, $width, $height) = $master->gridBbox(0, 2);

This example gets the bounding box for column 0 and row 2. All the values returned are in pixels. The bounding box will change as you resize the window. The four values returned represent the x offset, the y offset, the cell width, and the cell height (offsets are relative to the window or frame where the widget is gridded).

Removing a Widget

Like packForget, gridForget causes the widget(s) to be removed from view on the screen. This may or may not cause the window to resize itself; it depends on the size of $widget and where it was on the window. Here are some examples:

$mw->gridForget();             # Nothing happens
$widget->gridForget();         # $widget goes away
$widget->gridForget($widget1); # $widget and $widget1 go away
$widget->gridForget($w1, $w3); # $widget, $w1, $w3 go away

The widgets are undrawn from the screen, but the cells they occupied remain.

Getting Information

The gridInfo method returns information about the $widget in a list format. Just as with packInfo, the first two elements indicate where the widget was placed:

@list = $widget->gridInfo();

Here are some sample results from gridInfo:

-in Tk::Frame=HASH(0x81abc44) -column 0 -row 0 -columnspan 1 -rowspan 2 -ipadx 0 -ipady 0 -padx 0 -pady 0  -sticky nesw

Widget Location

The gridLocation method returns the column and row of the widget nearest the given (x, y) coordinates:

($column, $row) = $master->gridLocation($x, $y);

Both $x and $y are in screen units relative to the master window (in our examples, $mw). For locations above or to the left of the grid, -1 is returned.

When given the arguments (0, 0), our application returned this:

0 0

which indicates the cell at column 0 and row 0.

Propagation

There is a gridPropagate method that is similar to packPropagate:

$master->gridPropagate( 0 );

When given a false value, gridPropagate turns off geometry propagation, meaning size information is not sent upward to the parent of $master. By default, propagation is turned on. If gridPropagate is not given an argument, the current value is returned.

How Many Columns and Rows?

To find out how large the grid has become after placing numerous widgets in it, you can use gridSize to get back the number of columns and the number of rows:

($columns, $rows) = $master->gridSize();

The list returned contains the number of columns and then the number of rows. In many of the earlier examples, we had a grid size that was four columns by two rows.

($c, $r) = $f->gridSize();    #$c = 4, $r = 2

It is not necessary for a widget to be placed in a column/row for it to be considered a valid column/row. If you place a widget in column 4 and row 5 using -row=>5, -column=>4 and the only other widget is in row 0 and column 0, then gridSize will return 5 and 6.

Grid Slaves

There are two ways to find out which widgets have been put in a window or frame. Use gridSlaves without any arguments to get the full list or specify a row and column. Here are examples of both:

@slaves = $mw->gridSlaves();
print "@slaves\n";

The preceding code would have printed this:

Tk::Button=HASH(0x81b6fb8) Tk::Button=HASH(0x81ba454) Tk::Button=HASH(0x81ba4cc) Tk::Button=HASH(0x81ba538) Tk::Button=HASH(0x81b6fa0) Tk::Button=HASH(0x81ba5e0) Tk::Button=HASH(0x81ba6dc) Tk::Button=HASH(0x81ba748)

We could have specified the widget in column 0, row 0:

$widget = $mw->gridSlaves( -row => 0, -column => 0 );
print "$widget\n";
# Would print this: Tk::Button=HASH(0x81b6fb8)

If you specify only the -row option, you'll get a list containing only the widgets in that row. The same goes for only specifying a -column; your list will contain only the widgets in that column.

Place

The place() geometry manager is different than grid() or pack(). Rather than referencing against a cell location or a window's side, most of the time you'll be using a relative form of x and y coordinates. You can also use place() to overlap portions of widgets, which isn't allowed in either grid() or pack().

Invoking place() is similar to calling the other geometry managers:

$widget->place( [ option => value, . . . ] );

The options specified when you call place() affect how the widgets are put on the screen.

Place Options

-anchor => 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw' | 'center'

Sets the position in the widget that will be placed at the specified coordinates.

-bordermode => 'inside' | 'outside' | 'ignore'

Determines whether or not the border portion of the widget is included in the coordinate system.
-height => amount
Sets the absolute height of the widget.

-in => $window

Indicates that the child widget will be packed inside $window instead of in the parent that created it. Any relative coordinates or sizes will still refer to the parent.
-relheight => ratio
Indicates that the height of the widget relates to the parent widget's height by ratio.
-relwidth => ratio
Indicates that the width of the widget relates to the parent widget's width by ratio.
-relx => xratio
Indicates that the widget will be placed relative to its parent by xratio.
-rely => yratio
Indicates that the widget will be placed relative to its parent by yratio.
-width => amount
Indicates that the width of the widget will be amount.
-x => x
Indicates that the widget will be placed at x. x is any valid screen distance.
-y => y
Indicates that the widget will be placed at y. y is any valid screen distance.

Absolute Coordinates

The parent window (or frame) has a standard coordinate system where 0,0 is in the upper-left corner. The x values increase to the right, and the y values increase as you go down. See Figure 2-34.

Figure 2-34. Coordinate system of parent window when absolute coordinates are used

 

To use absolute coordinates to specify where to place the widget, we would use options -x and -y:

-x => x, -y => y

Valid values for x and y are valid screen distances (for example, 5, which is in pixels). The widget will have its anchor position (controlled by -anchor) placed at the x and y coordinates. The default anchor is "nw"; the upper-left corner of the widget.

Another major difference between place() and the other geometry managers is that at least two arguments are required when place() is invoked. There are no default values for the -x and -y options. You will get an error if you try to invoke place() with no arguments (for example, $widget->place()).

The simplest example of using -x and -y is to place a widget at 0,0:

$mw->Button(-text => "Exit",
            -command => sub { exit })->place(-x => 0, -y => 0);

As you would expect, the widget ends up in the upper-left corner of the window as shown in Figure 2-35. No matter what size the window, our widget will remain positioned at (0,0). Even when the window is resized as small as possible, the widget will not move.

Figure 2-35. Button placed using -x => 0, -y => 0

 

Here is an example of using -x and -y to create some overlapping widgets:

$mw->Button(-text => "Exit", 
            -command => sub { exit })->place(-x => 10, -y => 10);
$mw->Button(-text => "Exit", 
            -command => sub { exit })->place(-x => 20, -y => 20);

Figure 2-36 shows the resulting window.

Figure 2-36. Overlapping buttons with place()

 

Relative Coordinates

In place(), there is an additional coordinate system defined for the parent widget that allows relative placement within it. This coordinate system is shown in Figure 2-37.

Figure 2-37. The relative coordinate system

 

The upper-left corner has the coordinates (0.0,0.0). The lower-right corner's coordinates are (1.0, 1.0). The middle of the window would be (0.5, 0.5). The coordinates are specified in floating point form to allow place() to handle any size window. This allows the widget to remain at that position (in the center, for instance) no matter how the window is resized.

It is valid to specify coordinates both smaller than 0.0 and larger than 1.0. However, your widget most likely won't be completely visible in the window when you use out-of-range coordinates.

This code snippet produces the button shown in Figure 2-38:

$b = $mw->Button(-text => "Exit", -command => sub { exit });
$b->place(-relx => 0.5, -rely => 0.5);
Figure 2-38. Using place with -relx => 0.5, -rely => 0.5

 

Although the button in Figure 2-38 is placed in the middle of the screen, it looks off-center because the upper-left corner of the widget was placed in the middle of the window instead of the center. You can change this with the -anchor option, which I'll discuss shortly. If we resize this window, the button still stays in the middle of the window (see Figure 2-39).

Figure 2-39. -relx => 0.5, -rely => 0.5 window resized larger

 

This next example creates two buttons, both placed in the window with relative coordinates:

$mw->Button(-text => "Exit", 
            -command => sub { exit })->place(-relx => 0.2, 
                                             -rely => 0.2);
$mw->Button(-text => "Exit", 
            -command => sub { exit })->place(-relx => 0.5, 
                                             -rely => 0.5);

No matter what size the window is or where other widgets are in the screen, the two buttons will stay in those relative locations (see Figure 2-40).

Figure 2-40. Two buttons placed relative to the parent window

 

The left window in Figure 2-40 is the default size of the window when it was created. The right window is what it looks like after the window was resized to make it much smaller. Notice that the second button placed in the window remains on top. It does so because we are still maintaining the ordered list of widgets in the window; the second Exit button (placed at 0.5,0.5) is drawn last, so it's drawn above the other button.

You can also combine the absolute and relative coordinate systems simply by using both in the argument list. The relative coordinate system is considered first, and then the x or y value is added to that position. The options -relx => 0.5, -x => -10 means to place the widget 10 pixels to the left of the middle of the window.

Anchoring the Widget

Think of the child widget as a piece of paper that you want to put on your bulletin board (the board is the parent widget). You have a tack that you are going to use to keep the paper up on the board. You can put the tack right through the center of the paper, in the upper-left corner ("nw"), or in the lower-right corner ("se"). The point where the tack is going to stick the paper to the board is the -anchor point. The -anchor point on the widget is "tacked" to the coordinates given by -x, -y or -relx, -rely. The default -anchor is "nw". Figure 2-37 shows these -anchor points within the child widget.

It is important to know where the -anchor is because it will affect how we see the widget within the parent.

Figure 2-41. Different -anchor values affect where the widget is placed in the window

 

In Figure 2-41, almost identical place commands were used to put the Exit button in the window, but the -anchor value was changed. The left window's button was created with this command:

$mw->Button(-text => "Exit", 
            -command => sub { exit })->place(-relx => 0.5, 
                                             -rely => 0.5);

The window on the right in Figure 2-41 used this command:

$mw->Button(-text => "Exit", 
            -command => sub { exit })->place(-relx => 0.5,
                                             -anchor => "center",
                                             -rely => 0.5);

As with pack() and grid(), the possible values for -anchor are: 'n', 'e', 's', 'w', 'center', 'nw', 'sw', 'ne', and 'se'. However, the value now applies to the child widget instead of the position within the allocation rectangle.

Width and Height

When you use place(), you can specify the width and height of the widget in one of three ways:

To let the widgets determine their own size, no options are specified at all. The other ways involve the options -width, -height and -relwidth, -relheight respectively.

The -width and -height options allow you to specify the exact width or height of the widget in a screen distance:

-width => amount, -height => amount

Each amount is a valid screen distance (discussed earlier in this chapter under pack). The widget will be these sizes even if it cuts off edges of the items displayed in it. Our button looks quite silly on the screen when we use a -width of 40 pixels (see Figure 2-42).

$mw->Button(-text => "This Button Will Cause the Program to Exit", 
            -command => sub { exit })->place(-x => 0, -y => 0,
                                             -width => 40);
Figure 2-42. Using -width with place()

 

The other two options, -relwidth and -relheight, determine the widget in relation to the parent widget.

-relwidth => ratio, -relheight => ratio

The ratio is a floating point number (similar to that specified by -relx or -rely). A value of 1.0 will make the widget as wide (or as tall) as the parent widget. A value of 0.5 will make the widget half as wide as the parent (see Figure 2-43).

Figure 2-43. Example of the same window resized with -relwidth => 0.5, -relheight => 0.5

 

The options -width and -relwidth are additive when used together, and so are -height and -relheight.

Border Options

Normally, the border of the widget is used as the edge of the possible space in the window, which means any widgets placed with either the absolute or relative coordinate system will be placed inside the border. This can be changed by using the -bordermode option:

-bordermode => 'inside' | 'outside' | 'ignore'

Using 'outside' will allow the coordinate system to use the space occupied by the border as well. A value of 'ignore' will have the coordinate system use the space designated as the official X area. Overall, this option is pretty useless, as you can see from the difference each makes on our example in Figure 2-44.

Figure 2-44. -bordermode examples

 

If you look very closely (get out your magnifying glass), you can see that the 'outside' version is two pixels higher and two pixels to the left than the 'inside' version. This is because on my window manager (fvwm), my border is defined as 2 pixels.

Methods Associated with Place

The methods for place() are simple and don't allow much manipulation of the widgets.

Removing the widget

As with pack and grid, there is a place version of the Forget method:

$widget->placeForget();

If you use this method on the widget, the widget will be removed from view on the screen. It is also removed from the list maintained by the parent widget.

Place information

The placeInfo method returns a list of information related to the widget:

@info = $widget->placeInfo();
print "@info";
 
## Produced these results (there are blanks where there are no values)
-x 0 -relx 0 -y 0 -rely 0 -width   -relwidth  -height  -relheight  -anchor nw

Place slaves

@widgets = $parent->placeSlaves();

placeSlaves returns a list of the slave widgets that are within $parent. The list looks the same as it does when it is returned from packSlaves() or gridSlaves().x

Geometry Management Summary

You now know more about the three different geometry managers than you'll ever need to know to write a successful Perl/Tk application. Here are some helpful hints on deciding which geometry manager to use:

As you read through this book, you'll notice that some of the option names for the geometry managers are also option names when you are creating or configuring a widget type. For example, you can specify the -width of a button without using place(). Always keep in mind the context in which the option is used. Sometimes the functional difference is very subtle.


1. Several people have told me that pack can also take a list of widgets. I didn't cover this because it is not how pack is normally used.

2. When I used the special character "^" with Tk4.002, I got a nasty core dump. This is fixed in Tk8.0, so if you get this error also, check which version you have.


oreilly.com Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies | Privacy Policy

© 2001, O'Reilly & Associates, Inc.