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

UPDATE:
I forgot about the '$mw->update;' commands that would need to go in there as well, so that the Cancel button press is seen. However, in the example script this is handled by the output sub.

Hello World!\n

Often when I'm creating Tk GUI based scripts, I include a Cancel button for the longer operations.

However, the only way I have found to make the cancel button work is to have many 'return if $cancel;' type commands interspersed all through the subroutine, which can get very tedious if the routine is quite long and complicated.

So, I was wondering, is there an easier way to get a subroutine to terminate?

Thanks,

Spike.

In the following simple example, as the loop is small, it's easy to include a 'return if $cancel;' command, but imagine that the start subroutine is a very long and complicated routine, without a simple loop structure. Then you've got to add 'return if $cancel;' over and over again.

use strict; use warnings; use Tk; use Tk::ROText; my $cancel = 0; my $mw = MainWindow -> new (-title => " loop test"); $mw -> withdraw; $mw -> minsize (qw(700 400)); my $status = $mw -> Scrolled ("ROText", -scrollbars => 'e', -background => 'white', ) ->pack( -expand => 1, -fill => 'both', ); $status -> configure(-wrap => 'word'); my $exit = $mw -> Button ( -text , 'Exit', -command, \&my_exit, ) -> pack (-side, 'left'); my $start = $mw -> Button ( -text , 'Start', -command, \&start, ) -> pack (-side, 'right'); $mw -> Popup; $mw -> focus; MainLoop(); sub start { $cancel = 0; $start -> configure ( -state => 'disabled', -relief => 'sunken'); $exit -> configure ( -text => 'Cancel', -command => \&cancel); for (1..100) { # last if $cancel; output ("$_\n"); sleep 2; } done(); } sub cancel { output ("Cancelling...\n"); $cancel =1; } sub my_exit { exit(); } sub done { $start -> configure ( -state => 'normal', -relief => 'raised'); $exit -> configure ( -text => "Exit", -command => \&my_exit); output ("Done.\n"); } sub output { my $text = $_[0]; $status->insert('end', "$text"); $status -> see ('end'); $mw->update; }

Replies are listed 'Best First'.
Re: One shot way to end a sub?
by Fletch (Bishop) on Oct 25, 2004 at 10:59 UTC

    The normal way to do this in an event driven program is to have the cancel button set the flag, and then have the sub you want interruptible doing the work calls DoOneEvent() every time through its loop. This lets the GUI still process user input periodically while your sub is doing its thing. You might also check out Tk::Event.

Re: One shot way to end a sub?
by thospel (Hermit) on Oct 25, 2004 at 11:18 UTC
    Assuming your program isn't threaded, your cancel buttonpress won't get handled anyways until your long running calculation is over. In fact, your GUI will be totally unresponsive to everything during that period. Either have your loop periodically process at least one event (and you can then add your "canceled" check there), or invert your loop to do it's work in small chunks (this is usually the proper thing to do). Have it being called by a period zero alarm and cancel the alarm when "cancel" gets pressed or when the work is finished (or use an Idle handler depending on whether you consider your work high or low priority).
Re: One shot way to end a sub?
by Anonymous Monk on Oct 25, 2004 at 11:23 UTC
    I don't think this is going to work. User input can only be processed (or even detected) by the application when control is given back to Tk. Your loop never gives back control to Tk, so any mouse-clicks made by the user on the cancel button will be queued by X (or someother GUI system) until the program returns control to Tk. In your case, this happens not before your start() function exits. You may want to call DoOneEvent(), a poorly documented function, but calling this forces Tk to handle any outstanding events.