Canvas woes

Using canvas sizes with ... exceptional widths can bring some surpises. The following pieces of code show how from a working test sample that was used to check whether some things are possible in Tcl or not.

First, two helper scripts that get loaded in all three examples:

 # ScrolledCanvas.tcl is a scrollable canvas using standard techniques
 # as described for example in Brent B. Welch's book "Practical 
 # Programming in Tcl  and Tk"

 proc ScrolledCanvas {c args} {
   frame $c
   eval {canvas $c.canvas \
          -xscrollcommand [list $c.xscroll set] \
          -yscrollcommand [list $c.yscroll set] \
          -highlightthickness 0 \
          -borderwidth 0} $args
   scrollbar $c.xscroll -orient horizontal \
     -command [list $c.canvas xview]
   scrollbar $c.yscroll -orient vertical \
     -command [list $c.canvas yview]
   grid $c.canvas $c.yscroll -sticky news
   grid $c.xscroll -sticky ew
   grid rowconfigure $c 0 -weight 1
   grid columnconfigure $c 0 -weight 1
   return $c.canvas
 }

and

 # Drawer.tcl will drop a few objects (arrows etc.) onto a canvas

 proc DrawLeftMark {canvas color {width ""} {height ""}} {

   if {$height == ""} {
     set h [$canvas cget -height]
   } else {
     set h $height
   }

   set x1 0
   set x2 [expr {$h - 1}]
   set x3 [expr {($h * 4) - 1}]

   set y1 0
   set y5 [expr {$h - 1}]    
   set y3 [expr {($y1 + $y5) / 2}]    
   set y2 [expr {$y3 - ($h / 4)}]
   set y4 [expr {$y3 + ($h / 4)}]

   $canvas create polygon \
     $x1 $y3 $x2 $y1 $x2 $y2 $x3 $y2 $x3 $y4 $x2 $y4 $x2 $y5 $x1 $y3 \
     -fill $color \
     -outline black
 }

 proc DrawRightMark {canvas color {width ""} {height ""}} {

   if {$width == ""} {
     set w [$canvas cget -width]
   } else {
     set w $width
   }

   if {$height == ""} {
     set h [$canvas cget -height]
   } else {
     set h $height
   }

   set x1 [expr {$w - 1}]
   set x2 [expr {$w - $h - 1}]
   set x3 [expr {$w - ($h * 4) - 1}]

   set y1 0
   set y5 [expr {$h - 1}]    
   set y3 [expr {($y1 + $y5) / 2}]    
   set y2 [expr {$y3 - ($h / 4)}]
   set y4 [expr {$y3 + ($h / 4)}]

   $canvas create polygon \
     $x1 $y3 $x2 $y1 $x2 $y2 $x3 $y2 $x3 $y4 $x2 $y4 $x2 $y5 \
     -fill $color \
     -outline black
 }

 proc DrawTicks {canvas {width ""}} {

   if {$width == ""} {
     set w [$canvas cget -width]
   } else {
     set w $width
   }

   set lm 0
   set rm $w

   set tickSpace [expr {$w / 1000}]
   if {$tickSpace < 200} {set tickSpace 200}

   for {set x $lm} {$x <= $rm} {incr x $tickSpace} {
     $canvas create text $x 0 -text $x -anchor nw
   }

 }

Now for the first example: draw a simple canvas with 800 million pixels and drop a few objects on that. This example will work flawlessly:

 source ScrolledCanvas.tcl
 source Drawer.tcl

 # The following code for a simple canvas works fine for "small"
 # scroll regions and for "large" scroll regions

 #set scrollWidth 20000
 set scrollWidth 800000000
 set scrollHeight 400
 set rowHeight 20

 # Create a ScrolledCanvas and setup its scroll region
 set sc [ScrolledCanvas .c -width 400 -height 200]
 $sc configure -scrollregion "0 0 $scrollWidth $scrollHeight"

 # Make the ScrollableWindow visible
 pack .c -fill both -expand true

 DrawLeftMark $sc yellow $scrollWidth $rowHeight
 DrawRightMark $sc yellow $scrollWidth $rowHeight
 DrawTicks $sc $scrollWidth

 # Show the canvas coordinates of the mouse pointer for
 # validation
 set location [label .location -textvariable cur_x_y]
 pack .location
 bind Canvas <Motion> {ShowLocation %W %x %y} 
 bind Canvas <Leave> {set cur_x_y ""} 

 proc ShowLocation {w x y} {
   global cur_x_y
   set cx [expr int([$w canvasx $x])]
   set cy [expr int([$w canvasy $y])]
   set cur_x_y "x = $cx , y = $cy"
 }

So, if that works, packing some canvases together in one frame to scroll them should work too, right? Nope, sorry, it wont:

 # Demo program for the creation of a scrollable multi row canvas
 # using native Tcl/Tk methods

 source ScrolledCanvas.tcl
 source Drawer.tcl

 # The following code works fine for "small" scroll regions,
 # but runs into problems with "large" scroll regions. Try,
 # for example, a scroll width of 40,000 pixel (let alone 
 # 800,000,000 pixel).
 # Windows will exit abnormally, running under Linux (albeit
 #  with a display on Solaris) gives different effects for
 #  different lengths ... objects not shown, canvas too small etc.)

 set scrollWidth 40000
 #set scrollWidth 800000000
 set scrollHeight 400
 set rowHeight 20

 # Create a ScrolledCanvas and setup its scroll region
 set sc [ScrolledCanvas .c -width 400 -height 200]
 $sc configure -scrollregion "0 0 $scrollWidth $scrollHeight"

 # Create a frame widget within the ScrolledCanvas, which
 # will serve as a container for the individual rows
 set sf [frame $sc.f]
 $sc create window 0 0 -anchor nw -window $sf

 # Make the ScrolledCanvas visible.
 pack .c -fill both -expand true

 # Create some rows (canvas widgets) for displaying data...
 set row1 [canvas $sf.c1 \
               -width $scrollWidth \
               -height $rowHeight \
               -highlightthickness 0 \
               -bg lightyellow]
 DrawLeftMark $row1 yellow $scrollWidth $rowHeight
 DrawRightMark $row1 yellow $scrollWidth $rowHeight
 DrawTicks $row1 $scrollWidth

 set row2 [canvas $sf.c2 \
               -width $scrollWidth \
               -height $rowHeight \
               -highlightthickness 0 \
               -bg orange]
 DrawLeftMark $row2 brown
 DrawRightMark $row2 brown
 DrawTicks $row2

 set row3 [canvas $sf.c3 \
               -width $scrollWidth \
               -height $rowHeight \
               -highlightthickness 0 \
               -bg lightgreen]
 DrawLeftMark $row3 green
 DrawRightMark $row3 green
 DrawTicks $row3

 set row4 [canvas $sf.c4 \
               -width $scrollWidth \
               -height $rowHeight \
               -highlightthickness 0 \
               -bg pink]
 DrawLeftMark $row4 red
 DrawRightMark $row4 red
 DrawTicks $row4

 # ... and put them into the ScrolledCanvas.
 grid $row1 -row 0
 grid $row2 -row 1
 grid $row3 -row 2
 grid $row4 -row 3

 # swap row2 and row3:
 #grid $row2 -row 2
 #grid $row3 -row 1

 # hide row2:
 #grid forget $row2

 # Show the canvas coordinates of the mouse pointer for
 # validation
 set location [label .location -textvariable cur_x_y]
 pack .location
 bind Canvas <Motion> {ShowLocation %W %x %y} 
 bind Canvas <Leave> {set cur_x_y ""} 

 proc ShowLocation {w x y} {
     global cur_x_y
     set cx [expr int([$w canvasx $x])]
     set cy [expr int([$w canvasy $y])]
     set cur_x_y "x = $cx , y = $cy"
 }

The question is now: why?

KJN: Tk widgets are limited to 32k pixels in height and width. The text and canvas widgets are written to allow much larger contents than the widget size (the viewport for the contents). In contrast, the contents of a scrollable frame are limited to 32k x 32k.