Paning widgets

A pane is understood here as a frame holding two widgets and a thin borderline with a square button. By grabbing the button, the borderline can be moved in two directions, leading to a relative resize of the two embedded widgets. This was slightly modified after the example code in the Welch book 8.0, Example 22-5..7: vertical or horizontal panes. Suggested exercise: rewrite the code so the whole divider line can be grabbed ;-) See below for a possible solution... (EKB)

See also Bag of algorithms and Bag of Tk algorithms . RS

Tk 8.4 has a panedwindow included.

Namespace-d by DKF


DANGER WILL ROBINSON. Something's not right with the following code. It defines a proc named set (ring any bells, anyone?), and exports it. When I cut and paste the following code it fails with too many nested calls to "set" (gee, that's a surprise, since the proc set uses the set command).


 namespace eval pane {
    namespace export create set

    proc create {f1 f2 args} {
       ### Map optional arguments into array values ###
       set t(-orient) horizontal        rray values ###
       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)
       pack $master -expand true -fill both
       pack propagate $master off
       upvar #0 [namespace current]::Pane$master pane
       array set pane [array get t]

       set pane(1) $f1
       set pane(2) $f2
       set pane(grip) $master.grip

       frame $pane(grip) -background gray75 -width 10 -height 10 -bd 1 -relief raised

       if {[string match vert* $pane(-orient)]} {
           ### Adjust boundary in Y direction ###
           set pane(D) Y
           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
           $pane(grip) configure -cursor sb_v_double_arrow

       } else {
           ### Adjust boundary in X direction ###
           set pane(D) X
           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.2
           $pane(grip) configure -cursor sb_h_double_arrow
       }

       $master configure -background grey75

       ### bindings for resize AKA <Configure>, and dragging the grip. ###
       bind $master     <Configure>       [namespace code [list  Geometry $master]]
       bind $pane(grip) <B1-Motion>       [namespace code [list  Drag $master %$pane(D)]]
       bind $pane(grip) <ButtonRelease-1> [namespace code [list  Stop $master]]

       ### Do the initial layout ###
       Geometry $master
    }
    proc set {master value} {
       set [namespace current]::Pane${master}(-percent) $value
       Geometry $master
    }
    proc Drag {master D} {
       upvar #0 [namespace current]::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
           }
           Geometry $master
       }
       set pane(lastD) $D
    }
    proc Stop {master} {
       upvar #0 [namespace current]::Pane$master pane
       catch {unset pane(lastD)}
    }
    proc Geometry {master} {
       upvar #0 [namespace current]::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 { # $pane(D) == "Y"
           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]
       }
    }
 }

Here's a usage example with horiz and vertical panes:

 proc pane::test {{p .p} {orient hori}} {
    catch {destroy $p}
    frame $p -width 200 -height 200 ;# needed: no propagation 
    message $p.1 -bg bisque -text [info procs] -relief ridge
    frame $p.2
    label $p.2.foo -bg pink -text foo -relief ridge
    label $p.2.bar -bg grey75 -text bar -relief ridge
    pack $p -expand true -fill both
    pack propagate $p off
    pane::create $p.2.foo $p.2.bar -orient vert
    pane::create $p.1 $p.2 -orient $orient -percent 0.7
    raise .
 }

Wow - that's a lot of code up there. Here's a simpler example written by Stephen Uhler. - DL

 # 2 panes, one on top of the other (>= Tk4.0)

 frame .top
 frame .bottom
 frame .handle -borderwidth 2 -relief raised 
                -bg orange -cursor sb_v_double_arrow
 . configure -bg black

 # fixed placement parameters
 place .top  -relwidth 1 -height -1
 place .bottom -relwidth 1 -rely 1 -anchor sw -height -1
 place .handle -relx 0.9 -anchor e -width 10 -height 10

 bind . <Configure> {set H [winfo height .]; set Y0 [winfo rooty .]}
 bind .handle <B1-Motion> {Place [expr {(%Y-$Y0)/double($H)}]}

 proc Place {fract} {
        place .top -relheight $fract
        place .handle -rely $fract
        place .bottom -relheight [expr {1.0 - $fract}]
 }

 Place .5                ;# initialization
 # now "pack" whatever you like in ".top" and ".bottom"

I like the simpler one but I kept losing the pane when I dragged it off of the window. Change the "Place" proc to be

 proc Place {fract} {
     if { ($fract>=0.01) && ($fract<=0.99) } {
        place .top -relheight $fract
        place .handle -rely $fract
        place .bottom -relheight [expr {1.0 - $fract}]
     }
 }

Notice the if statement. Once this was added I could not drag the panes off of the window.


EKB April 9, 2005

A simple change to the simpler version lets you grab the divider line at any point. Replace:

 place .handle -relx 0.9 -anchor e -width 10 -height 10

with

 place .handle -relwidth 1 -x 0 -anchor w -height 5

Then if you turn off "-bg orange" when creating .handle, it looks a lot like pane dividers in most programs.