Updated 2014-09-11 14:25:29 by bll

This is my answer to my own question originally posted in Ask-11.

The problem was in handling large numbers of independent items on a dragable background. I was not asking for help because I could not do it - rather I was concerned about performance and so was interested in which way was best.

Here is the original post:

I am curious about a particular GUI feature. I could (and may yet!) answer the question by spending many hours writing test code for many different versions - but I thought that I would ask the experts first. I am thinking of a scrolling viewport containing a large number of independent, simple items. Large is a subjective term, but certainly hundreds - possibly thousands. As far as I can tell there are several different approaches which could be taken:

  1. Viewport is a frame, items are normal widgets (labels/frames etc.) placed upon it.
  2. Viewport is a canvas, items are normal widgets (labels/frames etc.) placed upon it.
  3. Viewport is a canvas, items are normal widgets (labels/frames etc.) added not with place but with the 'canvas create window' command.
  4. Viewport is a canvas, items are canvas text/shape etc. drawn upon it.

(Actually there are possibly 6 options - since for both 3 and 4 the scrolling could be accomplished in either of two ways: the canvas specific 'scan mark'/'scan dragto' and the generic 'place config' to move the container). I am confident that given enough time I could code all of these successfully, however I was wondering which would be faster? Given that frame seems like a more 'lightweight' widget than canvas I am naïvely assuming that option 1 would be best - however I know that considerable effort has gone into making the canvas very efficient... Does anyone know the answer - before I spend all weekend coding five wrong answers in order to find the right one? Many thanks, RJH.

RJH: 22 Jan 2014: I found the time to test all of these myself and was amazed at the difference between certain cases. I'll tidy the code ans post here if anyone is interested? AM Please do: make a separate page for that and use a suitable set of categories. Even if no one is interested right now, that may change in the near future. And it is probably a good exercise as well ;).

bll 2014-9-11: "Large" is certainly subjective. 100 isn't that many. Try 30000. I found that the memory requirements for a scrolling frame starts becoming unmanageable after a couple thousand lines of multiple widgets. I ended up with Virtual Scrolling. No frame or canvas wrapper.

... and here is my testbed. I was amazed to find that 'canvas create window' was so slow when dragging the viewport. It turned out not to be as onerous a task as I expected, the testbed took shape in few hours.
proc go "items container widgets drag" {
  destroy .background

  if {$container=="canvas"} {
    canvas .background -bg ivory -width 500 -height 500
    .background create text 0 0 -text CANVAS
  } else {frame .background -bg ivory -width 500 -height 500}

  if {$widgets=="place"} {
    proc make-item "x y text" {place [label .background.t-$text -text $text -bd 1 -relief solid -bg beige] -x $x -y $y}
  } elseif {$widgets=="window"} {
    proc make-item "x y text" {label .background.t-$text -text $text -bd 1 -relief solid -bg beige
                                .background create window $x $y -window .background.t-$text}
  } else {
    proc make-item "x y text" {set a [.background create text $x $y -text $text]
                               set b [.background create rectangle [.background bbox $a] -fill beige]
                               .background lower $b $a}

  if {$drag=="canvas"} {
    # Dragging by the 'canvas scan mark/dragto' methods.
    proc start "x y" {.background scan mark $x $y
                      .background conf -cursor fleur}
    proc drag  "x y" {.background scan dragto $x $y 1}
  } else {
    # Dragging by moving the container.
    proc start "x y" {set ::dx $x; set ::dy $y}
    proc drag  "x y" {
      place conf .background -x [expr [winfo x .background] + $x - $::dx] -y [expr [winfo y .background] + $y - $::dy]
      .background conf -cursor fleur

  place .background -x 0 -y 0

  for {set i 0} {$i < $items} {incr i} {make-item [expr rand()*400] [expr rand()*400] "Item $i"}

  bind .background <ButtonPress-1>    {start %x %y}
  bind .background <B1-Motion>        {drag %x %y}
  bind .background <ButtonRelease-1>  {.background conf -cursor ""}

. conf -height 500 -width 500

toplevel .config
tk_optionMenu .config.canvas  ::container canvas frame
tk_optionMenu .config.drag    ::drag canvas container
tk_optionMenu .config.widgets ::widgets canvas window place
pack [entry .config.items -textvariable ::items ]
pack .config.canvas .config.widgets .config.drag
pack [button .config.go -command {go $::items $::container $::widgets $::drag} -text GO]
proc fixwidgets   args {if {$::widgets=="place"}   {set ::drag container}}
proc fixcontainer args {if {$::container=="frame"} {set ::widgets place}}
trace add variable ::widgets write fixwidgets
trace add variable ::container write fixcontainer
set items 100