Updated 2012-09-24 14:13:17 by LkpPo

17feb04 jcw - The following code is a quick experiment to explore the use of a virtual grid to show potentially huge amounts of data without having to copy them into the GUI. This "data aware widget" approach would work well with databases (and an upcoming package called Ratcl).

The key idea is to maintain a grid of widgets that is twice the number of widgets displayed in both dimensions. Then, scrolling can reassign the "allocation" of each widget to the cell it currently represents. IOW, widgets stay on a canvas, caching the ones that are visible (plus a few more), and a window moves around, creating the illusion of moving over a huge virtual grid of widgets. By not creating widgets all the time, and by not even moving them around on the canvas, we get the benefit of Tk's double-buffering to cache visual detail and reduce the number of callbacks. In the image below, the arrows indicate reassignment of widgets, not movement or copying:

And here's code that places widgets on a canvas. It does not scroll yet, it's not more than a very first start at creating such a widget:
    package require Tk

    proc celldef {w px py} {
      label $w -anchor w \
        -text "($px,$py) [string repeat . [expr {$px+$py}]]"
    }

    proc ginit {w rows cols} {
      global cells$w
      for {set y 0} {$y < $rows} {incr y} {
        set cmd grid
        for {set x 0} {$x < $cols} {incr x} {
          set cell $w.$x,$y
          celldef $cell $x $y
          lappend cmd $cell
          set cells${w}($x,$y) -1,-1
        }
        eval [lappend cmd -sticky nsew -row $y]
        grid rowconfig $w $y -uniform a
      }
      for {set x 0} {$x < $cols} {incr x} {
        grid columnconfig $w $x -uniform a
      }
    }

    proc gconf {w} {
      $w.c configure -scrollregion [grid bbox $w.c.f]
    }

    proc gview {w} {
      frame $w
      scrollbar $w.h -command "$w.c xview" -orient horiz
      scrollbar $w.v -command "$w.c yview"
      canvas $w.c -xscrollcommand "$w.h set" -yscrollcommand "$w.v set"
      pack $w -expand 1 -fill both
      grid rowconfig $w 0 -weight 1 -minsize 0
      grid columnconfig $w 0 -weight 1 -minsize 0
      grid $w.c -row 0 -column 0 -sticky news
      grid $w.v -row 0 -column 1 -sticky news
      grid $w.h -row 1 -column 0 -sticky news
      bind $w <Configure> "gconf $w"
      frame $w.c.f
      $w.c create window 0 0 -window $w.c.f -anchor nw
      return $w.c.f
    }

    ginit [gview .f] 10 6

Feel free to use, re-use, alter, extend, and/or comment as you like.

D. McC: How would this compare with a set of multi-scrolling listboxes using the -listvariable option, which could be vertically scrolled and horizontally gridded or un-gridded as needed? More, less, or equally efficient?

I'm not sure. I think that's how Maurice Ulis's virtual listbox works. Listvariable's don't reduce the copying requirements though, if I understand them properly. You still need to hold all N data items in a Tcl list.

One reason to explore the above approach is that each cell can contain a widget of arbitrary complexity. -jcw

NEM This kind of reminds me of techniques used in side-scrolling platform games like the original Mario, where the background is made up of a grid of cells of a fixed size. The game draws enough cells to fill the screen, plus a border of cells around the edges. As the view moves to the right (say), the cells that disappear off the screen are deleted, while new cells are added on the other side. The difference here is that the cells (widgets) remain the same, we just re-assign their contents as the user scrolls. Interesting.

KPV A similar technique is used in Hack-O-Matic to speed up the display of what appears to the user to be a scrollable display of 32K x 8 widgets.

PWQ 19 Feb 04 Lets see if I understand this correctly. You scroll the widget down (right) until you get to 2*h-1 (2*w-1) widgets then you snap back to display the first widget (while the scroll bar stays in the same place). I used this technique on my EDA package [1], where a displayed grid scrolls with the canvas until it aligns with the grid size then the grid snaps back to the top left corner, thus simulating a grid of infinite size.

Using a canvas as a toplevel for this example:

As another technique, I ask the question, do you need to have 2 * the number of widgets. I propose the following:
    Given we have a matrix if x by y widgets.

  1. When a widget row (column) disappears from view (due to scrolling) move it to be at the opposite end of the list of widgets (on the canvas).
  2. Reconfigure this widget (row of or column of) with the new data.
   If a <Prior> or <Next> event makes multiple rows (columns) disappear:

  1. Do the same as above but move multiple rows (columns).

It is easy to calculate the starting row by using the mod operator for the scroll origin based on the number of rows (columns).

The above can be greatly simplified if the widget is to be gridded. this way you do not need to have the widgets actually scrolling with the canvas as you will always be shifting a block one way and another block the other (and they will always stay at the origin).

Contra The above assumes that moving objects on the canvas does not force a redraw. This may only be true when the objects bbox do not overlap.

I am not sure if the above matches with the original intent of the proposal since it lacks code to demonstrate what happens to the data when the widget is scrolled. Without coding anything up, I would assume that the above example can be translated to the grid widget, by adding rows (columns) and regridding the widgets from the now off screen (rows), but I don't know how efficient that would be.

11may05 jcw - Here's a demo which might be useful. It puts a grid on a canvas, so that the whole thing can be scrolled around:
  package require Tk

  canvas .c -bg red -xscrollcommand ".x set" -yscrollcommand ".y set" \
    -highlightthickness 0

  frame .c.f
  .c create window 0 0 -window .c.f -anchor nw

  bind .c.f <Configure> { .c configure -scrollregion {0 0 %w %h} }

  label .c.f.l1 -bg blue   -text "bonjour les mecs!" -width 30 -height 5
  label .c.f.l2 -bg yellow -text "hello there!"      -width 30 -height 5
  label .c.f.l3 -bg green  -text "hallo,\ngabbers"   -width 30 -height 5
  label .c.f.l4 -bg white  -text "hi fans!"          -width 30 -height 5

  grid .c.f.l1 .c.f.l3 -sticky news
  grid .c.f.l2 .c.f.l4 -sticky news

  scrollbar .x -command ".c xview" -orient h
  scrollbar .y -command ".c yview"

  grid .c .y -sticky news
  grid .x    -sticky news

  grid columnconfig . 0 -weight 1
  grid rowconfigure . 0 -weight 1

This would work even if the cells in the grid are not uniform in size, i.e. a general HTML-like table, so this thing could be the basis for a gridded widget. If the contents is virtual, it could in fact become a data-aware widget, i.e. a widget which does not contain a copy of all data but pulls cell content in as needed (and forgets it again just as easily!).