Updated 2004-04-01 11:00:11

Arjen Markus (1 april 2004) When using the after command to create an animated display, I often come up with a pattern like this:
 proc myproc { arg1 arg2 ... } {
    ... do some processing ...

    # The animation bit
    if { ... another step reauired? .... } {
       after 1000 [list myproc $arg1 $arg2 ...]
    }
    return
 }

For a lazy programmer like me that is too much work:

  • Repeat the name of the procedure
  • Construct a list for the after command with the right arguments (a repetition of the proc's header)
  • What if I need another list of arguments or change the namespace?

So, I came up with this little tool ...
 # schedule.tcl --
 #    Automatic (re)scheduling of the current procedure
 #
 # Motivation:
 #    In some circumstances, like with animation, you get the
 #    following pattern:
 #       proc someproc {arguments} {
 #          ... do whatever necessary ...
 #          if { condition } {
 #             # Schedule the next call
 #             after $time [list someproc $arguments]
 #          }
 #       }
 #    The problem here is that the code contains both the
 #    name of the procedure and all of its arguments, making
 #    changes a bit difficult.
 #    The procedure below automates this:
 #       proc someproc {arguments} {
 #          ... do whatever necessary ...
 #          if { condition } {
 #             reschedule $time
 #          }
 #       }
 #    If the delay time is not given, it defaults to 0
 #    Note that the reschedule procedure uses the current values
 #    of the argument variables, so that you can use it for instance
 #    in a countdown
 #       proc countdown {count} {
 #          puts $count
 #          incr count -1
 #          if { $count > 0 } {
 #             reschedule 1000
 #          }
 #       }
 #

 # reschedule --
 #    Schedule the current (calling) procedure via the after command
 # Arguments:
 #    delay       Delay time in ms (defaults to 0)
 # Result:
 #    None
 # Side effect:
 #    The calling procedure will be called again via an after command
 #
 proc reschedule {{delay 0}} {
    set call_list [lindex [info level -1] 0]
    set argvars   [info args $call_list]
    foreach var $argvars {
       lappend call_list [uplevel [list set $var]]
    }

    after $delay $call_list
 }

 #
 # Small test
 #
 proc print { count msg {default 0}} {
    puts "$count ... $msg ($default)"
    set msg [string range $msg 0 end-1]
    incr count -1
    if { $count > 0 } {
       reschedule 100
    } else {
       set ::forever 1
    }
 }

 print 10 "and counting"
 vwait forever

RS uses every for such purposes, here in its full code:
 proc every {ms body} {eval $body; after $ms [info level 0]}

AM Ah, but that fails with my countdown example as I do not use the values of the original call, but the values of the arguments when rescheduling.

Of course, your solution is more concise :)

[ [Arts and crafts of Tcl-Tk programming ]]