place $w -in $parent -relx 0.5 -rely 0.5 -anchor centerThe -relx and -rely specify the relative X and Y positions of the anchor point of the widget $w in $parent. A relative X (or Y) value of zero corresponds to the left (or top) edge of $parent. A value of one corresponds to the right (or bottom) edge of $parent. A value of 0.5 specifies the middle. The anchor point determines what point in $w is positioned according to the specifications. In Example 22-1 the center anchor point is used so that the center of $w is centered in $parent.
The relative height and width settings are used to base a widget's size on another widget. Example 22-2 completely covers one window with another window. It uses the default anchor point for windows, which is their upper-left hand corner (nw):
place $w -in $parent -relwidth 1 -relheight 1 -x 0 -y 0The absolute and relative size and position parameters are additive (e.g., -width and -relwidth). You can make a window slightly larger or smaller than the parent by specifying both parameters. In Example 22-3 a negative width and height are used to make a window smaller than another one:
place $w -in $parent -relwidth 1 -relheight 1 -x 0 -y 0 \
-width -4 -height -4It is not necessary for $parent to actually be the parent widget of $w. The requirement is that $parent be the parent, or a descendent of the parent, of $w. It also has to be in the same top-level window. This guarantees that $w is visible whenever $parent is visible. These are the same restrictions imposed by the pack geometry manager.
It is not necessary to position a widget inside another widget, either. Example 22-4 positions a window five pixels above a sibling widget. If $sibling is repositioned, then $w moves with it. This approach is useful when you decorate a resizable window by placing other widgets at its corners or edges. When the window is resized, the decorations automatically move into place:
place $w -in $sibling -relx 0.5 -y -5 -anchor s \
-bordermode outsideThe -bordermode outside option is specified so that any decorative border in $sibling is ignored when positioning $w. In this case the position is relative to the outside edge of $sibling. By default, the border is taken into account to make it easy to position widgets inside their parent's border.
The parent widget does not have to be a frame. Example 22-1 can be used to place a dialog in the middle of a text widget. In Example 22-4, $sibling and $w can both be label widgets.
The Pane Manager
The relative size and placement parameters of the place command can be used to create custom geometry managers. Example 22-5 shows a paned layout manager. Two frames, or panes, are placed inside another frame. A small third frame represents a grip that is used to adjust the boundary between the two panes:
proc Pane_Create {f1 f2 args} {
# Map optional arguments into array values
set t(-orient) vertical
set t(-percent) 0.5
set t(-in) [winfo parent $f1]
array set t $args
# Keep state in an array associated with the master frame
set master $t(-in)
upvar #0 Pane$master pane
array set pane [array get t]
# Create the grip and set placement attributes that
# will not change. A thin divider line is achieved by
# making the two frames one pixel smaller in the
# adjustable dimension and making the main frame black.
set pane(1) $f1
set pane(2) $f2
set pane(grip) [frame $master.grip -background gray50 \
-width 10 -height 10 -bd 1 -relief raised \
-cursor crosshair]
if {[string match vert* $pane(-orient)]} {
set pane(D) Y ;# Adjust boundary in Y direction
place $pane(1) -in $master -x 0 -rely 0.0 -anchor nw \
-relwidth 1.0 -height -1
place $pane(2) -in $master -x 0 -rely 1.0 -anchor sw \
-relwidth 1.0 -height -1
place $pane(grip) -in $master -anchor c -relx 0.8
} else {
set pane(D) X ;# Adjust boundary in X direction
place $pane(1) -in $master -relx 0.0 -y 0 -anchor nw \
-relheight 1.0 -width -1
place $pane(2) -in $master -relx 1.0 -y 0 -anchor ne \
-relheight 1.0 -width -1
place $pane(grip) -in $master -anchor c -rely 0.8
}
$master configure -background black
# Set up bindings for resize, <Configure>, and
# for dragging the grip.
bind $master <Configure> [list PaneGeometry $master]
bind $pane(grip) <ButtonPress-1> \
[list PaneDrag $master %$pane(D)]
bind $pane(grip) <B1-Motion> \
[list PaneDrag $master %$pane(D)]
bind $pane(grip) <ButtonRelease-1> \
[list PaneStop $master]
# Do the initial layout
PaneGeometry $master
}
Pane_Create f1 f2 ?-orient xy? ?-percent p? ?-in master?All the optional arguments are available in $args. Its attribute-value structure is used to initialize a temporary array t. Default values are set before the assignment from $args. The following code is compact but doesn't check errors in the optional arguments.
set t(-orient) vertical
set t(-percent) 0.5
set t(-in) [winfo parent $f1]array set t $args
Global state about the layout is kept in an array whose name is based on the master frame. The name of the master frame isn't known until after arguments are parsed, which is why t is used. After the upvar the argument values are copied from the temporary array into the global state array:
set master $t(-in)
upvar #0 Pane$master panearray set pane [array get t]
place $pane(1) -in $parent -x 0 -rely 0.0 -anchor nw \
-relwidth 1.0 -height -1
place $pane(2) -in $parent -x 0 -rely 1.0 -anchor sw \
-relwidth 1.0 -height -1place $pane(grip) -in $parent -anchor c -relx 0.8
The position of the upper and lower frames is specified with an absolute X and a relative Y position, and the anchor setting is chosen to keep the frame visible inside the main frame. For example, the lower frame is positioned at the bottom-left corner of the container with -x 0 and -rely 1.0. The -anchor sw attaches the lower-left corner of the frame to this position.
The size of the contained frames is also a combination of absolute and relative values. The width is set to the full width of the container with -relwidth 1.0. The height is set to minus one with -height -1. This value gets added to a relative height that is determined later. It will leave a little space between the two contained frames.
The resize grip is just a small frame positioned at the boundary. Initially it is just placed over toward one size with -relx 0.8. It gets positioned on the boundary with a -rely setting later. It has a different cursor to indicate it is active.
bind $parent <Configure> [list PaneGeometry $parent]
bind $pane(grip) <ButtonPress-1> \
[list PaneDrag $parent %$pane(D)]
bind $pane(grip) <B1-Motion> \
[list PaneDrag $parent %$pane(D)]bind $pane(grip) <ButtonRelease-1> [list PaneStop $parent]
proc PaneDrag {master D} {
upvar #0 Pane$master pane
if [info exists pane(lastD)] {
set delta [expr double($pane(lastD) - $D) \
/ $pane(size)]
set pane(-percent) [expr $pane(-percent) - $delta]
if {$pane(-percent) < 0.0} {
set pane(-percent) 0.0
} elseif {$pane(-percent) > 1.0} {
set pane(-percent) 1.0
}
PaneGeometry $master
}
set pane(lastD) $D
}
proc PaneStop {master} {
upvar #0 Pane$master pane
catch {unset pane(lastD)}
}The PaneGeometry procedure adjusts the positions of the frames. It is called when the main window is resized, so it updates pane(size). It is also called as the user drags the grip. For a vertical layout, the grip is moved by setting its relative Y position. The size of the two contained frames is set with a relative height. Remember this is combined with the fixed height of -1 to get some space between the two frames:
proc PaneGeometry {master} {
upvar #0 Pane$master pane
if {$pane(D) == "X"} {
place $pane(1) -relwidth $pane(-percent)
place $pane(2) -relwidth [expr 1.0 - $pane(-percent)]
place $pane(grip) -relx $pane(-percent)
set pane(size) [winfo width $master]
} else {
place $pane(1) -relheight $pane(-percent)
place $pane(2) -relheight [expr 1.0 - $pane(-percent)]
place $pane(grip) -rely $pane(-percent)
set pane(size) [winfo height $master]
}
}
proc PaneTest {{p .p} {orient vert}} {
catch {destroy $p}
frame $p -width 200 -height 200
label $p.1 -bg blue -text foo
label $p.2 -bg green -text bar
pack $p -expand true -fill both
pack propagate $p off
Pane_Create $p.1 $p.2 -in $p -orient $orient -percent 0.3
}
Table 22-2 summarizes the placement options for a widget. These are set with the place configure command, and the current settings are returned by the place info command.