Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to update a Tk widget with *each* element of an array by having a subroutine called from a foreach loop. The problem is that the widget is only updated with the last element of the array - *after* the foreach loop has finished. In fact the widget isn't even created until the foreach loop has finished. Rather I am looking to have it created at the first evaluation of the foreach loop, and updated at each subsequent element. ------
use Tk; $mw = new MainWindow; $rec1 = $mw->Canvas(-height => "90", -width => "450")->pack(); @junk = (0,1,2,3,4,5,6,7,8,9); foreach $n (@junk) { delay_type($n); } MainLoop; sub delay_type { $mw->after(500); $rec1->createRectangle(1,1,450,90, -fill => "black"); $rec1->createText(225,45, -text => "$n", -justify => "center", -fill => "yellow", -font => "times 62"); print $n; }
------ It print $n in <STDOUT> the way I would have it update the widget (except that the widget would erase the previous element). This is a really simplified example of a piece of code that I am working on, and therefore I would prefer to keep the foreach loop, but if anyone has suggestions, I would really appreciate it.

Replies are listed 'Best First'.
Re: Updating a Tk Widget from a foreach loop
by danger (Priest) on Mar 21, 2001 at 11:31 UTC

    The problem is that Tk doesn't actually display widgets until the call to MainLoop(), and you've done all your delays and drawing before that even gets called.

    One way to do this using a for loop (as you prefer) is to set up a series of 'after' timers just before the call to MainLoop. Also, you do not have to create new rectangle and text items in the canvas just to update, you can create those objects once and then update them (that's what I'll do in this example):

    #!/usr/bin/perl -w use strict; use Tk; my $mw = new MainWindow; my $rec1 = $mw->Canvas(-height => "90", -width => "450")->pack(); my $rec_tag = $rec1->createRectangle(1,1,450,90, -fill => "black"); my $txt_tag = $rec1->createText(225,45, -justify => "center", -fill => "yellow", -font => "times 62", ); my @junk = (0,1,2,3,4,5,6,7,8,9); # set up timed updates here: my $delay = 0; foreach my $n (@junk) { $delay += 500; $mw->after($delay,[\&delay_type, $n]); } MainLoop(); sub delay_type { my $n = shift; $rec1->dchars($txt_tag,0); $rec1->insert($txt_tag,0,$n); }

    Alternatively, if you weren't necessarily tied to using a for loop, you could use a 'repeat' timer (and cancel it when done). In this example, everything up to the 'set up timed updates' comment is assumed to be the same as above:

    # set up timed updates here: my $timer = $mw->repeat(500,\&delay_type); MainLoop(); sub delay_type { $timer->cancel() and return unless @junk; my $n = shift @junk; $rec1->dchars($txt_tag,0); $rec1->insert($txt_tag,0,$n); }

    That last example would destroy @junk in the process, so you may want to adjust to use a copy if you need to.