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

Here is part of a script, basically, creating a 16x16 matrix:
my $mw = MainWindow->new( -bg=> '#606060', -borderwidth=> 0); my $size = 40; my $y = 0; my $x = 0; my $h = 3*$size; for (my $j=0; $j<16; $j++) { my $x = 0; my $w = 3*$size; for (my $i=0; $i<16; $i++) { $frames{"$i,$j"} = $mw->Button( -text => ' ', -bg => "#60C8BC" +, -fg => "#000000", -borderwidth => 1, -anchor => 'w', ); $frames{"$i,$j"}->bind('<Button-1>' => sub { &ToggleBox("$i,$j +"); } ); $frames{"$i,$j"}->place( -y=>$y, -x=>$x, -width=>$w, -height=> +$h ); $x = $x + $w; $w = $size; $x = $x+2 if ($i % 5 == 0); } $y = $y + $h; $h = $size; $y = $y+2 if ($j % 5 == 0); } sub ToggleBox { my $this = shift; print "$x,$y\n"; }
Whenever I click on a button, it always print "16,16", the last values of $i, $j. How should I pass the values of $i,$j so it passes the value of $i,$j the instant it was iterated? thanks!

Replies are listed 'Best First'.
Re: How to pass parameter to a subroutine?
by stevieb (Canon) on Mar 03, 2020 at 00:08 UTC

    You're not storing the j and i values anywhere, so by the time the buttons have been generated, the values will always be the maximum.

    I've quickly cleaned up the code. I added the ability to store the values during creation, and use the accurate values when any button is pressed. I updated the subroutine to accept these values as arguments. Most importantly, I added strict and warnings.

    use warnings; use strict; use Tk; my $mw = MainWindow->new(-bg=> '#606060', -borderwidth=> 0); my $size = 40; my $y = 0; my $x = 0; my $h = 3*$size; my %frames; for my $j (0..15) { my $x = 0; my $w = 3*$size; for my $i (0..15) { $frames{$j}{$i}{j} = $j; $frames{$j}{$i}{i} = $i; $frames{$j}{$i}{button} = $mw->Button( -text => ' ', -bg => "#60C8BC", -fg => "#000000", -borderwidth => 1, -anchor => 'w', ); $frames{$j}{$i}{button}->bind( '<Button-1>' => sub { &ToggleBox($frames{$j}{$i}{j}, $frames{$j}{$i}{i}); } ); $frames{$j}{$i}{button}->place( -y=>$y, -x=>$x, -width=>$w, -height=>$h ); $x = $x + $w; $w = $size; $x = $x+2 if ($i % 5 == 0); } $y = $y + $h; $h = $size; $y = $y+2 if ($j % 5 == 0); } MainLoop(); sub ToggleBox { my ($j, $i) = @_; print "$j, $i\n"; }
Re: How to pass parameter to a subroutine?
by AnomalousMonk (Archbishop) on Mar 03, 2020 at 00:53 UTC

    An approach very similar to stevieb's solution except:

    • An array is used instead of a hash for button widget storage;
    • The  i, j info is encapsulated in a callback and not stored in the widget array (and thus is not accessible other than through the ToggleBox() function - it's effectively immutable);
    • Uses callbacks (see Tk::callbacks).
    use strict; use warnings; use Tk; my @frames; my $mw = MainWindow->new( -bg=> '#606060', -borderwidth=> 0); my $size = 40; my $y = 0; my $x = 0; my $h = 3*$size; for my $j (0 .. 15) { my $x = 0; my $w = 3*$size; for my $i (0 .. 15) { $frames[$i][$j] = $mw->Button( -text => ' ', -bg => "#60C8BC", + -fg => "#000000", -borderwidth => 1, -anchor => 'w', ); $frames[$i][$j]->bind('<Button-1>' => [ '::ToggleBox', $i, $j +] ); # bind to callback $frames[$i][$j]->place( -y=>$y, -x=>$x, -width=>$w, -height=>$ +h ); $x = $x + $w; $w = $size; $x = $x+2 if ($i % 5 == 0); } $y = $y + $h; $h = $size; $y = $y+2 if ($j % 5 == 0); } MainLoop; exit; sub ToggleBox { my $self = shift; my ($jj, $ii) = @_; print "$jj, $ii\n"; }


    Give a man a fish:  <%-{-{-{-<

Re: How to pass parameter to a subroutine?
by tybalt89 (Monsignor) on Mar 03, 2020 at 02:39 UTC

    This $i does not participate in closures

    for (my $i = 0; $i <= 15, $i++)

    whereas this $i does

    for my $i ( 0..15 )

    so try (slightly tweaked)

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11113672 use warnings; use Tk; my %frames; my $mw = MainWindow->new( -bg=> '#606060', -borderwidth=> 0); my $size = 40; my $y = 0; my $x = 0; my $h = 3*$size; for my $j ( 0 .. 15 ) { my $x = 0; my $w = 3*$size; for my $i ( 0 .. 15 ) { $frames{"$i,$j"} = $mw->Button( -text => ' ', -bg => "#60C8BC" +, -fg => "#000000", -borderwidth => 1, -anchor => 'w', ); $frames{"$i,$j"}->bind('<Button-1>' => sub { &ToggleBox("$i,$j +"); } ); $frames{"$i,$j"}->place( -y=>$y, -x=>$x, -width=>$w, -height=> +$h ); $x = $x + $w; $w = $size; $x = $x+2 if ($i % 5 == 0); } $y = $y + $h; $h = $size; $y = $y+2 if ($j % 5 == 0); } MainLoop; sub ToggleBox { my $this = shift; print "$this\n"; }

    Are you sure you don't want to use the grid geometry manager ?

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11113672 use warnings; use Tk; my %frames; my $mw = MainWindow->new( -bg=> '#606060', -borderwidth=> 0); for my $j ( 0 .. 15 ) { for my $i ( 0 .. 15 ) { $frames{"$i,$j"} = $mw->Button( -text => ' ', -bg => "#60C8BC", -fg => "#000000", -borderwidth => 1, -anchor => 'w', -command => sub { ToggleBox("$i,$j"); }, )->grid( -row => $j, -column => $i); } } MainLoop; sub ToggleBox { my $this = shift; print "$this\n"; }