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

I have been using Perl for a while, but have recently started using the Perl Tk module. I am trying to make a GUI that will ask for a bunch of information and then output it to a file, among other things. (Actually, I am trying to update a Perl program that does this via text inquiries in a command line.) I have successfully added Radiobuttons, text Entry and a regular Button to close the window and print out values at the end. I am stuck on trying to use the Button to call a subroutine that will send a value back to the main program.

I want to use a button to call a subroutine that will use ChooseDirectory to select a subdirectory and send it back to the main program. I tried several variations on the -command value, but none of them worked. If I put ChooseDirectory in the main window, instead of calling it from a button, it pops up before the general window asking for other information. It does send the correct information though.

I am copying some code here. This is for a smaller program that only tries to ask for the directory. Thanks for any help.

# Test to enter select directory with gui # Good to use next 3 lines for all perl programs. #! /usr/bin/perl use warnings; use strict; # Use Tk module for gui and create window use Tk; my $mw = new MainWindow; use Encode; #so can read directories #Button to open directory and see tissue types my $tissue="plasma";#default value my $tissue_but = $mw -> Button(-text => 'Tissue Type', -command =>sub +{select_tissue()}); $tissue_but->grid(-row=>3,-column=>1); #-command => [ sub { ... }, arg1, arg2, ... ] #Button to close gui window my $but=$mw -> Button(-text => 'Data Entry Complete', -command =>sub { +do_end()}); $but->grid(-row=>10,-column=>1); #This must be at the end of every Tk section MainLoop; sub select_tissue { my $dir=$mw-> chooseDirectory(-initialdir =>'Z:\Projects\RunMSMS\M +GF\human\ion_trap'); $dir = encode("windows-1252", $dir); my @fulldir=split(/\//,$dir);#split out tissue type from full dire +ctory my $tis=$fulldir[6]; return $tis; } sub do_end # { my ($tissue)=@_; open INFO_OUT, ">>test_gui_output.txt" or die $!; print INFO_OUT "$tissue\n"; exit; #close gui window }
  • Comment on Perk Tk - how to send a value from a subroutine back to the main program using Button
  • Download Code

Replies are listed 'Best First'.
Re: Perl Tk - how to send a value from a subroutine back to the main program using Button
by kcott (Archbishop) on Jun 07, 2013 at 01:49 UTC

    In order to do this you need to pass around a reference. Here's a working (albeit barebones) script to demonstrate this:

    #!/usr/bin/env perl use strict; use warnings; use Tk; my $tissue = 'plasma'; my $mw = MainWindow->new; my $f1 = $mw->Frame()->pack; $f1->Label(-textvariable => \$tissue)->pack; $f1->Button(-text => 'Select Tissue', -command => sub { select_tissue( +\$tissue) })->pack; $f1->Button(-text => 'Quit', -command => sub { do_end(\$tissue) })->pa +ck; MainLoop; sub select_tissue { my $tissue_ref = shift; # Tissue selection code here - assume 'muscle' selected $$tissue_ref = 'muscle'; return; } sub do_end { my $tissue_ref = shift; # Print to STDOUT for demo purposes print "$$tissue_ref\n"; exit; }

    Notes:

    • $tissue needs to be declared in a scope that's visible to the -command coderefs.
    • The callbacks have a reference to this variable, i.e. \$tissue.
    • The callbacks return nothing!
    • The value of $tissue is accessed or modified via $$tissue_ref.
    • Most widgets that have a -text option also have a -textvariable option. Here I've used a Label - it starts with the default vaue of "plasma", If you click the "Select Tissue" button, the Label text will change to "muscle".
    • The output (when you click "Quit") will depend on whether or not the "Select Tissue" button was pressed.

    Update: fixed title s/Perk Tk/Perl Tk/

    -- Ken

      I am the original poster and have just joined perlmonks. Thanks so much for the advice. I have now gotten it working in the main program.

      Just want to be sure I am correctly understanding what is going on. You can't actually pass a value from a subroutine to the main program using a button or other widget. Instead, you create a reference to the value of interest in the main program, send that reference to the subroutine and change the reference value in the subroutine. That changed value can then be accessed from the main program. Correct?

        G'day jeri_rl,

        Welcome to the monastery.

        Just to clarify, if you have something like "... -command => sub { ... } ..." in your code, the "sub { ... }" part is the callback and it cannot return anything to the main program. However, code within the callback can return values. Here's another version of my original script that does just that:

        #!/usr/bin/env perl use strict; use warnings; use Tk; my $tissue = 'plasma'; my $mw = MainWindow->new; my $f1 = $mw->Frame()->pack; $f1->Label(-textvariable => \$tissue)->pack; $f1->Button(-text => 'Select Tissue', -command => sub { $tissue = sele +ct_tissue() })->pack; $f1->Button(-text => 'Quit', -command => sub { do_end($tissue) })->pac +k; MainLoop; sub select_tissue { # Tissue selection code here - assume 'muscle' selected my $selected_tissue = 'muscle'; return $selected_tissue; } sub do_end { my $tissue_to_print = shift; # Print to STDOUT for demo purposes print "$tissue_to_print\n"; exit; }

        If you run this code, you'll see it works identically to my original script. Of course, this is a very simple GUI with very straightforward subroutines. With more complex code, you can run into problems when passing values instead of references; on occasion, these can be hard to track down and rectifying them can involve reworking large parts of your code. I'd recommend references over values in this scenario. There's an added benefit of references being scalars, so you're potentially moving a lot less data around than you would with, say, @array_with_lots_of_elements or %complex_data_structure.

        -- Ken

Re: Perk Tk - how to send a value from a subroutine back to the main program using Button (without MainLoop)
by Anonymous Monk on Jun 07, 2013 at 02:25 UTC

    You probably want to use Tk::Wizard, see http://search.cpan.org/dist/Tk-Wizard/MANIFEST, Confused by variable scope in Tk::Wizard (hello closures) , Re: Confused by variable scope in Tk::Wizard (hello closures)

    But if you want to stick with MainLoop you could write that like this (all lexically scoped and not-memory leaking )

    #!/usr/bin/perl -- # Test to enter select directory with gui # # # # # perltidy -olq -csc -csci=10 -cscl="sub : BEGIN END if " -otr -opr - +ce -nibc -i=4 -pt=0 "-nsak=*" #!/usr/bin/perl -- use strict; use warnings; use Encode; use Tk; use Path::Class; use Devel::CheckOS qw/ os_is /; Main( @ARGV ); exit( 0 ); sub Main { &wadebug; my $initialdir = shift || 'Z:\Projects\RunMSMS\MGF\human\ion_trap' +; my $end_file = shift || 'Z:\Projects\RunMSMS\MGF\human\ion_trap\ +test_gui_output.txt'; my $tissue = shift || "plasma"; tkgui( $initialdir, $end_file, $tissue ); } sub tkgui { &wadebug; my( $initialdir, $end_file, $tissue ) = @_; my $mw = Tk::MainWindow->new; my $tissue_but = $mw->Button( -text => 'Tissue Type', -command => [ \&select_tissue, $mw, $initialdir, \$tissue, ], ); $tissue_but->grid( -row => 3, -column => 1 ); $mw->Button( -text => 'Data Entry Complete', -command => [ \&do_end, $mw, $end_file, \$tissue, ], )->grid( -row => 10, -column => 1 ); Tk::MainLoop(); } sub select_tissue { &wadebug; my( $mw, $initialdir, $choiceref ) = @_; my $dir = $mw->chooseDirectory( -initialdir => $initialdir ); if( os_is(qw( MicrosoftWindows )) ){ $dir = encode( "windows-1252", $dir ); } if( defined $dir and length $dir ){ $dir = dir( $dir )->basename; if( length $dir ){ $$choiceref = $dir; } } return; } sub do_end { use autodie qw/ open close /; ## make open(...) or die(...) automa +tic &wadebug; my( $mw, $outfile, $tissueref ) = @_; print join " ", tissue => $$tissueref , "\n"; # open my($FOUT), '>>', $outfile; # print $FOUT "$$tissue\n"; # close $FOUT; $mw->destroy; ## exit mainloop } sub wadebug { my( $package, $filename, $line, $subroutine, $hasargs ) = caller( +1 ); for my $var ( @_ ){ if( not ref $var ){ use Data::Dump ();; print STDERR "# $line:$subroutine: @{[ Data::Dump::pp( $va +r ) ]}\n"; } elsif( ref($var) and ref($var) =~ m{ SCALAR | ARRAY | HASH } +x ) { print STDERR "# $line:$subroutine: @{[ Data::Dump::pp( $va +r ) ]}\n"; } else { print STDERR "# $line:$subroutine: $var\n"; } } print STDERR "\n"; } __END__ # 28:main::tkgui: "Z:\\Projects\\RunMSMS\\MGF\\human\\ion_trap" # 28:main::tkgui: "Z:\\Projects\\RunMSMS\\MGF\\human\\ion_trap\\test_g +ui_output.txt" # 28:main::tkgui: "plasma" # 251:main::select_tissue: Tk::MainWindow=HASH(0x9a1804) # 251:main::select_tissue: "Z:\\Projects\\RunMSMS\\MGF\\human\\ion_tra +p" # 251:main::select_tissue: \"plasma" # 251:main::do_end: Tk::MainWindow=HASH(0x9a1804) # 251:main::do_end: "Z:\\Projects\\RunMSMS\\MGF\\human\\ion_trap\\test +_gui_output.txt" # 251:main::do_end: \"x64" tissue x64