Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Disable (or anything else) a widget and all its descendants

6 views
Skip to first unread message

John Seal

unread,
Nov 21, 2005, 3:06:40 PM11/21/05
to
I recently had need to enable/disable part of a GUI (a frame and
everything in it). That's easily done; I whipped up a [setState]
procedure that walked the tree and did its thing, but that night it
occurred to me that the procedure could be generalized to handle *any*
widget command and option/value pairs. The question then was how to
name it? Today it came to me, and I think it flows pretty well:

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?

Bryan Oakley

unread,
Nov 21, 2005, 3:42:26 PM11/21/05
to
John Seal wrote:
> ...

> catch {eval [list $w] $args} ;# call widget command
> }
> }
>
> ... 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]}".

John Seal

unread,
Nov 22, 2005, 1:20:03 PM11/22/05
to
Bryan Oakley wrote:
> 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 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.

Joe English

unread,
Nov 24, 2005, 4:14:05 PM11/24/05
to
John Seal wrote:

[ 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

0 new messages