proc downFrom {w args} {
# Execute a widget command for a widget and all its descendants.
# Ignores widgets that don't support the command (usually frames
# or other containers). A typical use is to enable or disable
# part of a GUI via [downFrom $frame configure -state $state].
set wtv [list $w] ;# list of widgets to visit
while {[llength $wtv]} {
set w [lindex $wtv 0] ;# get current widget
# Remove current widget from list and add its children.
# Adding the children at the end (as shown) gives breadth-first
# traversal; if you want depth-first, swap the args to [concat].
set wtv [concat [lrange $wtv 1 end] [winfo children $w]]
catch {eval [list $w] $args} ;# call widget command
}
}
For example, where [$w config -bg red] affects just $w, [downFrom $w
config -bg red] affects $w and all its subwidgets. I like how the
[downFrom] args are exactly the same as the command for the root widget
alone. Offhand, I can't think of uses for widget commands other than
"configure", but I love the way Tcl makes it easy to generalize... if I
*do* think of something, it'll Just Work.
Are there problems with this that I haven't forseen? Improvements to be
made?
Depending on your definition of "improvement"... You don't need the
eval, and I generally consider it an improvement any time you can remove
the use of eval in a block of code.
I'd go with one of these variations:
tcl 8.4 and below: catch [concat [list $w] $args]
tcl 8.5 and above: catch {$w {expand}$args}
Another change I would make -- but this is personal preference not a
performance improvement -- I think "while {[llength $wtv] > 0}" is much
more readable than "while {[llength $wtv]}".
I wholeheartedly agree about avoiding eval whenever possible. But
without the catch, eval would be required, right? When I added the
catch, I just wrapped it around what was already there, not realizing
that the eval-like behavior of catch eliminated the need for an explicit
eval. Thanks for catching that!
> tcl 8.4 and below: catch [concat [list $w] $args]
I like!
> Another change I would make -- but this is personal preference not a
> performance improvement -- I think "while {[llength $wtv] > 0}" is much
> more readable than "while {[llength $wtv]}".
You are right, of course, and it was sheer laziness on my part to leave
out the comparison.
[ slightly edited, -JE ]
>proc downFrom {w args} {
> set wtv [list $w] ;# list of widgets to visit
> while {[llength $wtv]} {
> set w [lindex $wtv 0] ;# get current widget
> set wtv [concat [lrange $wtv 1 end] [winfo children $w]]
> catch {eval [list $w] $args} ;# call widget command
> }
>}
Using [concat] and [lrange] to maintain the breadth-first-search
queue leads to a polynomial-time algorithm, since the list is
copied at each step. This probably isn't a big deal for this
use case, but if performance becomes an issue the following
breadth-first search idiom might be useful:
set queue [list $w]
while {[llength $queue] != 0} {
set next [list]
foreach w $queue {
# ... process widget $w here ...
foreach child [winfo children $w] {
lappend next $child
}
}
set queue $next
}
The idea here is that $queue contains the list of elements
at the current level, and $next contains the list of elements
one level down; each iteration of the outer [while] loop
processes one level of the tree. Using [lappend] (which is
an amortized O(1) operation in Tcl) minimizes copying and
leads to a (nearly-) linear-time algorithm.
Again, performance probably isn't an issue with the original code
unless you're dealing with thousands of widgets, but for larger
trees the $queue/$next idiom can be a lot faster.
--Joe English