www.gibmonks.com




  Previous section   Next section

Practical Programming in Tcl & Tk, Third Edition
By Brent B. Welch

Table of Contents
Chapter 25.  The Place Geometry Managery


The Pane Manager

The relative size and placement parameters of the place command can be used to create custom geometry managers. Example 25-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:

Example 25-5 Pane_Create sets up vertical or horizontal panes.

graphics/25fig01.jpg

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
}

Parsing Arguments and Maintaining State

The Pane_Create procedure is given two widgets to manage, and an optional set of parameters. The general syntax of Pane_Create is:

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 pane
array set pane [array get t]

Sticky Geometry Settings

Example 25-5 sets several place parameters on the frames when they are created. These are remembered, and other parameters are adjusted later to dynamically adjust the boundary between the frames. All Tk geometry managers retain settings like this. The initial settings for the vertical layout is shown here:

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 -1
place $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.

Event Bindings

The example uses some event bindings that are described in more detail in Chapter 26. The <Configure> event occurs when the containing frame is resized by the user. When the user presses the mouse button over the grip and drags it, there is a <ButtonPress-1> event, one or more <B1-Motion> events, and finally a <ButtonRelease-1> event. Tcl commands are bound to these events:

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]

Managing the Layout

The code is set up to work with either horizontal or vertical layouts. The pane(D) variable is either X, for a horizontal layout, or Y, for a vertical layout. This value is used in the bindings to get %X or %Y, which are replaced with the X and Y screen positions of the mouse when the bindings fire. This value is passed to PaneDrag as the parameter D. The PaneDrag procedure remembers the previous position in pane(lastD) and uses that to update the percentage split between the two contained panes:

Example 25-6 PaneDrag adjusts the percentage.
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 that this is combined with the fixed height of -1 to get some space between the two frames:

Example 25-7 PaneGeometry updates the layout.
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
}

      Previous section   Next section
    Top