Version 4 of Multi-column display in text widget

Updated 2007-12-27 00:52:04 by dkf

Peter Berger wrote:

 > I have been charged with the task of presenting data.  I recieve a list in
 > alphabetical order such as: a b c d e f g h i j, and need to present it in a
 > dialog and organized in rows or columns such as:
 > a    e    i
 > b    f    j
 > c    g
 > d    h
 > If the dialog expands the number of columns should increase.  Currently I
 > use this code:
 > 
 > pack [text .text -wrap word -tabs "1.25i left" -setgrid 0]
 > set alist "apple bear cow dear elephant fine grape hello idol"
 > foreach a $alist {.text insert end "$a\t"}
 > 
 > but this orginizes data as:
 > a    b    c
 > d    e    ...
 > Anyone have any ideas on how I can switch to the other format?

KBK replies (20 Feb 2001):

Are you really trying, instead, to do a multi-column listbox? If so, then Bwidget and Tix both have them, and you should probably just pick up one of them. (DKF: In Tk 8.5 or Tile, you can also use the ttk::treeview widget.)

If you really need multi-column display in a text widget, read on...

I started to code this, and it turned out to be more interesting than I thought. I assume that you don't know the height of the text in advance, so you have to bind to the <Configure> event and adjust your columns on the fly. The code I came up with is shown below. Check out what happens when you resize the window.


 # Schedule to lay out the columns of the text from the idle loop

 proc repackWhenIdle { w args } {
     variable repackPending
     if { ! [info exists repackPending($w)] } {
         set repackPending($w) {}
         after idle [list [namespace code repack] $w]
     }
     return
 }

 # Lay out the columns of the text.

 proc repack { w } {

     variable repackPending
     variable list

     # Reset the flag that keeps this from being repeat-scheduled.

     catch { [unset repackPending($w)] }

     # Clear the old content of the widget

     $w configure -state normal
     $w delete 1.0 end

     # Calculate number of lines to display

     set lineHeight [font metrics [$w cget -font] -linespace]
     set textHeight [expr { [winfo height $w] - 2 * [$w cget -borderwidth] }]
     set numLines [expr { int( $textHeight / $lineHeight ) }]

     # Bail out if the widget is too small to display anything

     if { $numLines < 1 } {
         return
     }

     # Insert the requisite number of newlines, plus one

     for { set i 0 } { $i < $numLines } { incr i } {
         $w insert end \n {}
     }

     # Build up the list, in columns

     set line 1
     set sep {}
     foreach item $list {
         incr line
         $w insert "${line}.0-1c" $sep {} $item {}
         if { $line > $numLines } {
             set line 1
             set sep "\t"
         }
     }

     # Delete the excess newline

     $w delete end-1l end
     $w configure -state disabled

     return
 }

 # Set up the text

 grid [text .t \
           -state disabled \
           -wrap none \
           -font {Helvetica 12} \
           -width 40 -height 7 \
           -tabs {1.25i left} ] \
    -sticky nsew
 grid rowconfigure . 0 -weight 1
 grid columnconfigure . 0 -weight 1

 # Arrange to repack the text when either the text geometry or the
 # list content changes

 trace variable list w [list repackWhenIdle .t]
 bind .t <Configure> [list repackWhenIdle %W]

 # Set initial content of the list

 set list {
     apple blackberry blueberry cherry grape grapefruit kiwi kumquat
     lemon nectarine orange peach pear plum prune
     raisin raspberry strawberry tangerine
 }