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

Dear Monks,

this is a general question on how I can not let a GUI frozen while a process undelying the GUI is running. If required I can provide an example

My gui reads a XLS file and print it out as CSV (using Spreadsheet::ParseExcel). During this operation, the gui (mainwindow) is frozen, meaning that nothing more can be done. I would like to be able to use the mw, for example to give the user the possibility to stop the procedure by clicing a button, or by showing in an extra window (splashscreen)in real time the number of records being converted.

What is the best approach? Any suggestion will be much appreciated. Cla

Replies are listed 'Best First'.
Re: TK GUI frozen windows
by zentara (Cardinal) on Aug 04, 2010 at 15:52 UTC
    You really need to provide an example, so we can see how the long process is called, and blocking the Tk gui. If you are calling a code block, which can be sprinkled with $mw->update, that may be an easy way out; but generally you want to spawn off your long process into a separate thread on Windows, and read back the output to the main program thru shared variables or a shared filehandle.

    See Please suggest a non-forking way to do this (OS: windows) and Perl/Tk App and Interprocess Communication and How does system(1,"foo") work on Windows? for similar discussions. Here is a simple example where you can try to put your long process into it's own thread.

    #!/usr/bin/perl use warnings; use strict; use threads; use threads::shared; use Tk; my %shash; #share(%shash); #will work only for first level keys my %hash; share ($shash{'go'}); share ($shash{'fileno'}); share ($shash{'pid'}); share ($shash{'die'}); $shash{'go'} = 0; $shash{'fileno'} = -1; $shash{'pid'} = -1; $shash{'die'} = 0; $hash{'thread'} = threads->new(\&work); my $mw = MainWindow->new(-background => 'gray50'); my $text = $mw->Scrolled('Text')->pack(); my $startb = $mw->Button( -text => 'Start', -command=>sub{ $shash{'go'} = 1; $mw->after(100); #give pipe chance to startup my $fileno = $shash{'fileno'}; print "fileno_m $fileno\n"; open (my $fh, "<&=$fileno") or warn "$!\n"; # filevent works but may not work on win32, # but you can use a timer instead as shown below $mw->fileevent(\*$fh, 'readable', ); while(<$fh>){ $text->insert('end',$_); $text->see('end'); $mw->update; } # on Win32 (untested by me) you may need # a timer instead of fileevent # my $repeater; # $repeater = $mw->repeat(10, # sub { # my $bytes = sysread( "<&=$fileno", my $buf, 8192); # $text->insert('end',$buf); # $text->see('end'); # if( $shash{'go'} == 0 ){ $repeater->cancel } # } # ); } )->pack(); my $stopb = $mw->Button( -text => 'Stop/Exit', -command=>sub{ $shash{'die'} = 1; kill 9,$shash{'pid'}; $hash{'thread'}->join; exit; }, )->pack(); MainLoop; ################################################################## sub work{ $|++; while(1){ if($shash{'die'} == 1){ return }; if ( $shash{'go'} == 1 ){ #run your command here, and try to find a way to capture it's +output # into a filehandle, then use fileevent or a timer in the main + Tk program # to read the filehandle # on win32 you may need to use IPC::Run in the thread #use a win32 command that gives continous output # on linux I use top my $pid = open(FH, "top -b |" ) or warn "$!\n"; #on win32 you can use system(1, $command) IIRC # see link above my $fileno = fileno(FH); print "fileno_t->$fileno\n"; $shash{'fileno'} = $fileno; $shash{'pid'} = $pid; $shash{'go'} = 0; #turn off self before returning }else { select(undef,undef,undef,.1) } #short sleep } }

    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku
Re: TK GUI frozen windows
by SuicideJunkie (Vicar) on Aug 04, 2010 at 14:17 UTC

    You need to inject $mw->update; or DoOneEvent() calls into your work loop so that the window can get some CPU cycles.

    Be sure to put some form of protection against clicking "go" multiple times so you don't get recursive.

      Thank you all!

      Putting an 'update' at the right place can do miracles.<\p>

Re: TK GUI frozen windows
by Marshall (Canon) on Aug 04, 2010 at 15:35 UTC
    I usually make a progress window that pops up and shows the percent completed. If you can predict or calculate in advance how many "work units" are to be done, I prefer that over just some raw count of lines processed as the user can tell how fast things are going.

    Below is one code snippet from something I wrote many moons ago - just to demo the idea. You call something like this within your data processing loop. It is important to note that updating the GUI is an "expensive" operation and you don't want to be going crazy updating it otherwise actual work progress will grind to a snail's pace! Below I check if the percentage value has changed, if not then I return without doing a GUI operation. You will have to decide what kind of "throttle" makes sense for your application. I've got other code with an "abort" button - can't find it right now, but you can check if user has pressed that button and take appropriate action (like exit your processing loop).

    When I'm finished with the progress window, I just hide it instead of destroying it. When I need it again, I just put it back on the screen.

    sub update_WorkingScreen_Progress { my $ratio = shift; #number like .32 means 32% my $new_percent = int ($ratio*100); #updating screen is expensive computationally #if number hasn't changed, then forget it... #let 0% go through first time if ( ($new_percent == $percent) && ($new_percent > 0)) {return;} $percent = $new_percent; $text = "Program is working - $extra_text...\n\n". "$percent percent complete\n"; $workingScreen->update(); #this is necessary for to #get the new value of $text to display!!!!! }
    Update: I am updating this post as perhaps I assumed that you have more control over this cpu intensive loop than you really do? If you don't have much control, then things do get more complex and zentara's post is right on target. However from the problem statement, it sounds to me like you are translating spreadsheet lines into CSV. An Excel spreadsheet can get huge nowadays. But if you can put your program into a loop that say translates X lines at a time (10, 100, or whatever) and call some simple subroutine on each loop iteration, I think you'll be happy. Updating the screen only when you need to will make a HUGE difference.