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

Hey all, I humbly request the help of the great Monks of Perl.

I am working on learning TK and I am stuck. I have a main window that pops a sub window with a listing of certain text files in a directory. I can create the sub window as scrolled text with no problem. I can create the buttons with the Text on each button showing the filename, but I can't get the filename to stick to the button, the filename does not carry through to the subroutine called when you click the button. Please help!!


the sub that creates the mini page } opendir(SPAM,$spam_dir); my @files=readdir(SPAM); close(SPAM); my @deleted=grep(/^deleted/,@files); my $new=$mw->Toplevel; $new->title("List of Files"); foreach $open (@deleted) { $temp{$open}=$new->Button(-text=>"$open", -command=>sub{&view($open)}, -fg=>'black', -bg=>'tan')->pack; } } sub view { ($file)=@_; my $new=$mw->Toplevel; $new->title("$file Listing"); $open_curr=$new->Scrolled("Text")->pack(-side=>'bottom', -fill=>'both',-expand=>1); open (FH,"$file"); my @temp=<FH>; close(FH); foreach $line (@temp) { $open_curr->insert("end",$line); } }

Replies are listed 'Best First'.
Re: creating tk buttons with for loop
by graff (Chancellor) on Apr 09, 2004 at 03:06 UTC
    If you have code that actually runs, you should paste the relevant part of the running code when you post (or at least be more careful about typing/pasting it in). What you have here has some glaring errors. Update as you see fit (and mention what you've updated), but when I first saw this post, there were the following problems:
    • The first curly brace is a close-brace, not an open-brace -- it doesn't compile.
    • You have "opendir(SPAM...)" followed by "close(SPAM)", not "closedir(SPAM)" -- maybe that actually works, but it just seems wrong.
    • Is "%temp" actually a global hash (like $mw appears to be a globally declared "MainWindow" widget)? If not, it isn't being declared with "my" where it should be. Likewise for $new in "sub view". (Does the script "use strict", or not? If not, try it, please.)
    • You aren't checking for success or errors when opening files in the "view()" sub -- likewise for opening the directory.

    As for the symptom -- the buttons appear to be labeled as intended, but you aren't seeing the file name or its contents in the new Toplevel window when you hit a button -- well, it's hard to comment on this, because you say your code runs, but what you've posted won't run, so you haven't posted the code that produces that symptom.

    Anyway, you might try initializing the "-command" parameter on the button like this:

    ... -command => [ \&view, $open ], ...
    This way, the "-command" option gets a reference to an anonymous array; the first element of the array is a reference to a subroutine to be run when the button is clicked, and subsequent elements in the array (one in this case) are parameters to be passed to the subroutine in @_.
Re: creating tk buttons with for loop
by converter (Priest) on Apr 09, 2004 at 03:11 UTC

    From the Foreach section of the perlsyn POD:

    Foreach Loops The "foreach" loop iterates over a normal list value and sets the variable VAR to be each element of the list in turn. If the variable is preceded with the keyword "my", then it is lexically scoped, and is therefore visible only within the loop. Otherwise, the variable is implicitly local to the loop and regains its former value upon exiting the loop. If the variable was previously declared with "my", it uses that variable instead of the global one, but it's still localized to the loop. This implicit localisation occurs only in a "foreach" loop.

    The variable $open is local to the loop and regains its former value at the end of the loop. You're calling &view with a localized package variable that is uninitialized. Use a lexical iterator instead so that the anonymous subroutine definition keeps a reference to the $open that exists at the time of the definition. Change this:

    foreach $open (@deleted) { .... }

    to this:

    foreach my $open (@deleted) { ... }
Re: creating tk buttons with for loop
by K_M_McMahon (Hermit) on Apr 09, 2004 at 03:29 UTC
    UPDATE: Well, I don't know for sure if it was the additon of the my because I found some other errors too, but the whole thing works as planned now!
    THANX!!

    I was working on another computer at the time so I didn't have the whole code with me, I typed in the part that I had a problem with. I will post the entire code here.

    I also included the second suggestion to add the my into the for loop itself, same result. It names the button properly, but it doesn't pass the argument to the subroutine VIEW.

    -Kevin
      So, you've posted the version that actually works as intended? No way for any of us to tell, since the code is, um, quite "personalized". Anyway, you might enjoy a few tricks for reducing the amount of repetition:
      my %black_on_tan = (-fg=>'black', -bg=>'tan', -activebackground=>'blue', -activeforeground=>'red'); my %black_on_yel = (-fg=>'black', -bg=>'yellow', -activebackground=>'yellow', -activeforeground=>'black'); # ... my $see_list=$w->Button(-text=>'See Deleted List', %black_on_tan); # likewise for other buttons...
      Next, all those ugly regex tests on the output of "ps -ef | grep ..." can certainly be simplified -- look up the unix man page for "ps" to see how you can control what it prints, and just have it print the info you need. And, as with the attribute hash example above, assign the needed regex to a scalar variable, and use the scalar in your various "if" statements.

      Alternatively, if this GUI is the only thing you use to start and stop the "check.pl" and "email.pl" scripts, try starting them like this, and avoid using ps altogether:

      my %pid; # this should have global scope sub start_proc { my $proc = shift; # use one sub for both jobs return if ( $pid{$proc} ); # already set means it's running $pid{$proc} = fork; if ( $pid{$proc} == 0 ) { # true for the child process $path = ( $proc eq "email" ) ? $spam_dir : $check_dir; exec "perl $path/$proc.pl"; } # in the parent, $pid{$proc} is >0 } sub stop_proc { my $proc = shift; return unless ( $pid{$proc} ); `kill -9 $pid{$proc}`; $pid{$proc} = 0; }
      I haven't tested that, and it may be inappropriate if these jobs have a tendancy to die unexpectedly, or are liable to be started from outside the GUI. But it should at least give some ideas about writing less redundant code.

      (Actually, by starting the jobs with "fork", it becomes easier to check their status at any time afterwards, because you know what pid to look for -- e.g. you could use "ps -p $pid{$proc}", and just look for $pid{$proc} in its output -- if the pid doesn't show up there, it's not running.)