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

Great Monks,

I'm new to Perl/Tk. I've read as much documentation as I can: browsing the modules on CPAN, using SuperSearch, Google, PerlDoc.com, and the Perl Cookbook. I must be missing something.

I'm attempting to write an application that does not require input from the user (beyond invocation), but displays a progress bar (I'm using Tk::ProgressBar ) and some status text while it works. I've had success initiating all of the action and output if I give the user a 'Start' button. However, this needs to be suitable for automation.

It seems that if I put my logic before MainLoop, nothing displays until my logic is complete. MainLoop blocks, so I can't flow code after it. The only way I've found to invoke any code after the call to MainLoop is to have the user click a Widget to invoke a callback.

Update {
Found a solution, thanks to Ven'Tatsu: it works, is there anything better, or caveats I should be aware of?

#this is psuedo-codeish use Tk; my $mw = new Tk::MainWindow; my $p_bar = $mw->ProgressBar; # No longer needed: # my $button = $mw->Button(-command=>\&main_logic, -text=>'start'); # $button->pack(); $p_bar->pack(); #this is the solution, thanks to Ven'Tatsu: $mw->after(0,\&main_logic); MainLoop; sub main_logic { # $button->configure(-state=>"disabled"); for (...) { # do some stuff... $p_bar->value($pct_complete); $mw->update(); } $mw->destroy; }
What I'm essentially trying to do is eliminate the need to push the button.
}

Is there any way to cause a Tk::MainWindow to display and immediately invoke a callback?

--
$me = rand($hacker{perl});

All code, unless otherwise noted, is untested
"All it will give you though, are headaches after headaches as it misinterprets your instructions in the most innovative yet useless ways." - Maypole and I - Tales from the Frontier of a Relationship (by Corion)

Replies are listed 'Best First'.
Re: Creating a non-UI event in Perl/Tk
by Ven'Tatsu (Deacon) on Sep 22, 2004 at 16:56 UTC
    I would normaly go with gri6507's solution, $mw->update() will display the main window and return when any events are done (there should be very few with out any user interaction).
    An alternative is to use Tk::after, from it's POD:
    $widget->after(ms,callback)
    In this form the command returns immediately, but it arranges for callback be executed ms milliseconds later as an event handler. The callback will be executed exactly once, at the given time. The command will be executed in context of $widget. If an error occurs while executing the delayed command then the Tk::Error mechanism is used to report the error. The after command returns an identifier (an object in the perl/Tk case) that can be used to cancel the delayed command using afterCancel.
    The downside is you will either have to break up your proccessing into chunks, so that Tk can be given controll to update it's window, or you will still need to call $mw->update() to do that your self.
      Thank you! I now do this, and it solves my issue:
      my $mw = Tk::MainWindow; # set up all the widgets... $mw->after(0,\&main_loop); sub main_loop { ... }
      Each iteration of the loop is a few ms, and I call $mw->update after adjusting the ProgressBar once each loop. Works like a charm, thank you again!
      --
      $me = rand($hacker{perl});

      All code, unless otherwise noted, is untested
      "All it will give you though, are headaches after headaches as it misinterprets your instructions in the most innovative yet useless ways." - Maypole and I - Tales from the Frontier of a Relationship (by Corion)
Re: Creating a non-UI event in Perl/Tk
by gri6507 (Deacon) on Sep 22, 2004 at 16:27 UTC
    I think what you are looking for is a way to update the GUI every time the progress bar changes it's value. If that's the case, then you simply can update the entire MainWindow in the progress bar callback a la

    $mw->update();

      No, that isn't the issue. It's starting the process. MainLoop causes the window to be displayed, with all the widgets. If I create a Button widget and give it a -command => \&main_logic, then everything works when the user presses the button. ProgressBar updates work just fine with ->update or ->idletasks.

      The issue is that I don't want someone to have to press a button to call main_logic(). I want the window to display and main_logic() to be called automagically immediately thereafter.

      --
      $me = rand($hacker{perl});

      All code, unless otherwise noted, is untested
      "All it will give you though, are headaches after headaches as it misinterprets your instructions in the most innovative yet useless ways." - Maypole and I - Tales from the Frontier of a Relationship (by Corion)
Re: Creating a non-UI event in Perl/Tk
by zentara (Cardinal) on Sep 23, 2004 at 12:30 UTC
    Unless I'm missing something, I don't think you even need to use the after(0,...). You can just call the sub directly.
    #!/usr/bin/perl use Tk; use Tk::ProgressBar; my $mw = new Tk::MainWindow; my $p_bar = $mw->ProgressBar( -blocks => 1, -width => 20, -length => 200, -from => 0, -to => 100, -variable => \(my $foo), )->pack(); $foo = 0; #$mw->after(0,\&main_logic); &main_logic; MainLoop; sub main_logic { $mw->repeat(10, sub { $foo = ($foo + 1) % 100 }); }

    I'm not really a human, but I play one on earth. flash japh
      If my main_logic loop was only working with widget operations, yes. It isn't -- it's doing significant I/O. The problem with the code above is that main_logic will run until completion (about 20 minutes on average) before MainLoop is called.

      While I could work some repeat magic, the resulting code is much less readable (I have about 300 lines of code in my main_logic loop).

      require General::Disclaimer;

      All code, unless otherwise noted, is untested

      "All it will give you though, are headaches after headaches as it misinterprets your instructions in the most innovative yet useless ways." - Maypole and I - Tales from the Frontier of a Relationship (by Corion)