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

Hi monks,

I am writing a GUI in Tk. The basic idea is that "work" is selected via the GUI and, once the Run button is clicked, this "work" is executed line by line. Of course, if the user wants to cancel the execution, then simply need to click on the Cancel button. As work is executed, a progress bar displays how much "work" has already been done. The relevant code sections are (pardon the lack of detail, but I didn't want to bore everyone - oh, and I do use strict in the code)

use Tk; use strict; require Tk::ProgressBar; require Tk::Button; my ( $mw, $progressBar, $cancelCalButton, $runCalButton, ); my $guiPid = $$; $mw = MainWindow->new(); $cancelCalButton = $mw->Button(-text=>"cancel")->pack(); $runCalButton = $mw->Button(-text=>"run",-command=>\&doit)->pack(); $progressBar = $mw->ProgressBar()->pack(); $SIG{USR1} = sub { $progressBar->value(0); $mw->update(); $runCalButton->configure(-state=>"normal"); $cancelCalButton->configure(-state=>"disabled"); }; MainLoop; sub doit { my $calPid; $runCalButton->configure(-state=>"disabled"); $cancelCalButton->configure(-state=>"normal"); $mw->update; print "Failed to fork" if (!defined($calPid = fork())); return if ($calPid); for (1 .. 6) { $progressBar->value($_); $mw->update(); sleep(1) } # send the signal to main GUI to reconfigure the button states kill('USR1',$guiPid); kill('INT',$$); }

The Run/Cancel button operation works. Even the progress bar updating works. But, after 2 or 3 updates of the bar, the GUI hangs, and I get the following error message

X Error of failed request: BadIDChoice (invalid resource ID chosen fo +r this connection) Major opcode of failed request: 53 (X_CreatePixmap) Resource id in failed request: 0x1e00077 Serial number of failed request: 1374 Current serial number in output stream: 1300

I am lost on this. Please help. Thank you.

Replies are listed 'Best First'.
Re: Tk::Progressbar gives X Error
by jdporter (Paladin) on Nov 03, 2003 at 22:20 UTC
    You're reentering perl unsafely. Rather than have the signal handler try to update the window, have it set some kind of variable, and then test/act on that in an idle task. (See Tk::Widget $widget->idletasks).

    jdporter
    The 6th Rule of Perl Club is -- There is no Rule #6.

Re: Tk::Progressbar gives X Error
by pg (Canon) on Nov 04, 2003 at 01:52 UTC

    You can accomplish this in a much simpler way by setting some sort of cancel indication (a variable):

    use Tk; use Tk::ProgressBar; use strict; use warnings; my $count; my $cancel; my $mw = new MainWindow(-title => "demo"); $mw->Button(-command => sub {do_something(\$count, \$cancel)}, -text = +> "Do Something")->pack(); $mw->Button(-command => sub {cancel(\$cancel)}, -text => "Cancel")->pa +ck(); my $pb = $mw->ProgressBar( -from => 0, -to => 100, -blocks => 100, -colors => [0, 'green', 50, 'yellow' , 80, 'red'], -variable => \$count )->pack(); MainLoop; sub do_something { my ($count_r, $cancel_r) = @_; $$count_r = 0; $$cancel_r = 0; while (!$$cancel_r & $$count_r < 100) { $$count_r ++; $mw->update(); sleep(1); } } sub cancel { my $cancel_r = shift; $$cancel_r = 1; }

    One thing important here is to analyze your "unit of work". You only need to check cancel indication after the completion of each unit of work. This is safe and simple.

    You can choose to check even during the unit of work, if your unit of work takes too long and you want a shorter response time. But you have to make sure the work can be fully cancelled, not leave something half broken there, and demage your data integrity.

    Also if your unit of work is too big, you probably want to look at your analysis again.

Re: Tk::Progressbar gives X Error
by SleepNot (Pilgrim) on Nov 04, 2003 at 02:34 UTC
    I've did a nasty hack and now it works. It doesn't crack, it doesn't hang but it ain't prety.
    I had put the code here for educational purposes only :).
    #!/usr/bin/perl use Tk; use strict; require Tk::ProgressBar; require Tk::Button; my ( $mw, $progressBar, $cancelCalButton, $runCalButton, ); my $guiPid = $$; $mw = MainWindow->new(); $cancelCalButton = $mw->Button(-text=>"cancel")->pack(); $runCalButton = $mw->Button(-text=>"run",-command=>\&doit)->pack(); $progressBar = $mw->ProgressBar()->pack(); my $val = 0; $SIG{USR1} = sub { $val = 0; $progressBar->value(0); $mw->update(); $runCalButton->configure(-state=>"normal"); $cancelCalButton->configure(-state=>"disabled"); }; $SIG{USR2} = sub { $val++; $progressBar->value($val*10); $mw->update(); }; MainLoop; sub doit { my $calPid; $runCalButton->configure(-state=>"disabled"); $cancelCalButton->configure(-state=>"normal"); $mw->update; print "Failed to fork" if (!defined($calPid = fork())); return if ($calPid); for (1 .. 10) { $progressBar->value($_); kill('USR2',$guiPid); sleep(1) ; } # send the signal to main GUI to reconfigure the button states kill('USR1',$guiPid); kill('INT',$$); }

    --------------------------------------
    "Quoth The Raven Nevermore"
    SleepNot a.k.a TheCount
      I have tried this approach, but I still got the error (in a couple of different incarnations). If you ran yours, I wonder if maybe you didn't run it for long enough, because, based on what is written above, it is still unsafe due to asynchronous accesses.
        I've ran it longer enough and I didn't have any problems with it. The trick is to have all the Tk/X operations {mw->update and so...) in a single process. Beside of USR1 and USR2, you can try to trapp different signals.
        I know the aproach it's ugly, but for me it works just fine...

        --------------------------------------
        "Quoth The Raven Nevermore"
        SleepNot a.k.a TheCount
Re: Tk::Progressbar gives X Error
by gri6507 (Deacon) on Nov 04, 2003 at 15:37 UTC
    With all of these suggestions, I came up with the following hack (ugly, but it works). Just for the sake of reference, this works for me.
    use Tk; use strict; require Tk::ProgressBar; require Tk::Button; $|++; my ( $mw, $progressBar, $cancelCalButton, $runCalButton, $cancel, ); my @data = (0 .. 100); my $dataIndex; my $guiPid = $$; my $childPid; pipe(FROM_PARENT, TO_CHILD) or die "pipe: $!\n"; pipe(FROM_CHILD, TO_PARENT) or die "pipe: $!\n"; select((select(TO_CHILD), $| = 1)[0]); select((select(TO_PARENT), $| = 1)[0]); if ($childPid = fork()){ close FROM_PARENT; close TO_PARENT; } else { die "cannot fork :$!\n" unless defined $childPid; close FROM_CHILD; close TO_CHILD; while(my $line = <FROM_PARENT>){ chomp($line); print "Chid got from parent $line\n"; sleep(10); if ($line == 100){ kill('USR1',$guiPid); #tell the GUI child is done with work next; } kill('USR2',$guiPid); #tell the GUI child has update info print "sending stuff to parent\n"; print TO_PARENT "$line\n"; } print "Done with child\n"; exit(); } $mw = MainWindow->new(); $cancelCalButton = $mw->Button(-text=>"cancel", -command=>\&cancel)->p +ack(); $runCalButton = $mw->Button(-text=>"run",-command=>\&doit)->pack(); $progressBar = $mw->ProgressBar()->pack(); $mw->repeat(100,sub{$mw->idletasks}); $SIG{USR1} = sub { $cancel = 1; $progressBar->value(0); $cancelCalButton->configure(-state=>"disabled"); $mw->idletasks; }; $SIG{USR2} = sub { $runCalButton->configure(-state=>"normal"); print "Got USR2 request\n"; my $value = <FROM_CHILD>; print "parent got $value from child\n"; return if ($cancel); $progressBar->value($value); $runCalButton->configure(-state=>"disabled"); print TO_CHILD "$data[$dataIndex++]\n"; }; MainLoop; sub doit { $runCalButton->configure(-state=>"disabled"); $cancelCalButton->configure(-state=>"normal"); $mw->update; $cancel = 0; $dataIndex = 0; print TO_CHILD "$data[$dataIndex++]\n"; } sub cancel { print "@@@@@@@@@ Got Cancel button @@@@@@@@@\n"; kill('USR1',$$); }