Perl Tkx:如何将变量作为参数传递给按钮的回调
Perl Tkx: How to pass a variable as a parameter to a button's callback
鉴于此 Perl/Tkx 代码片段:
@itemList = ({'attrib1' => 'name1', 'attrib2' => 'value1'},
{'attrib1' => 'name2', 'attrib2' => 'value2'});
$row = 0;
foreach $item (@itemList) {
push(@btn_list, new_ttk__button(-text => $item->{'attrib1'}, -command => sub {do_something($item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
(在实际程序中,@itemList 是从用户可编辑的配置文件中填充的。)
我确实看到两个标记为 'name1' 和 'name2' 的按钮。但是当我点击任一按钮时,似乎传递给回调的参数总是 $itemList[1]->{'attrib2'}
;即 @itemList 数组最后一个元素的 'attrib2'。我想要的是让第一个按钮调用 do_something($itemList[0]->{'attrib2'}
和第二个按钮调用 do_something($itemList[1]->{'attrib2'}
。
我做错了什么,谢谢你?
您遇到了 Perl 中 for
循环的一个微妙特征。首先解决方案:在 for 循环中使用 my
。然后 $item
将能够在您稍后在循环中声明的匿名子中创建一个适当的闭包。
for my $item (@itemlist) {
push(@btn_list, new_ttk__button(
-text => $item->{'attrib1'},
-command => sub {do_something($item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
进一步说明:Perl 隐式地本地化了 for 循环的主题变量。如果您不在 for 循环中使用 my
,循环将使用包变量的本地化版本。这使您的代码等同于:
package main;
$main::item = undef;
@itemList = ({'attrib1' => 'name1', 'attrib2' => 'value1'},
{'attrib1' => 'name2', 'attrib2' => 'value2'});
$row = 0;
foreach (@itemList) {
local $main::item = $_;
push(@btn_list, new_ttk__button(
-text => $main::item->{'attrib1'},
-command => sub {do_something($main::item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
# at the end of the loop, value of $main::item restored to undef
您的匿名子程序仍然引用 $main::item
包变量,无论该变量在调用这些子例程时持有什么值,这可能是 undef
.
较短的解决方案:use strict
额外的概念验证。尝试猜测以下程序输出的内容:
@foo = ( { foo => 'abc', bar => 123 },
{ foo => 'def', bar => 456 } );
my @fn;
foreach $foo (@foo) {
push @fn, sub { "42" . $foo->{bar} . "\n" };
}
foreach my $foo (@foo) {
push @fn, sub { "19" . $foo->{foo} . "\n" };
}
print $_->() for @fn;
答案如下:
42
42
19abc
19def
鉴于此 Perl/Tkx 代码片段:
@itemList = ({'attrib1' => 'name1', 'attrib2' => 'value1'},
{'attrib1' => 'name2', 'attrib2' => 'value2'});
$row = 0;
foreach $item (@itemList) {
push(@btn_list, new_ttk__button(-text => $item->{'attrib1'}, -command => sub {do_something($item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
(在实际程序中,@itemList 是从用户可编辑的配置文件中填充的。)
我确实看到两个标记为 'name1' 和 'name2' 的按钮。但是当我点击任一按钮时,似乎传递给回调的参数总是 $itemList[1]->{'attrib2'}
;即 @itemList 数组最后一个元素的 'attrib2'。我想要的是让第一个按钮调用 do_something($itemList[0]->{'attrib2'}
和第二个按钮调用 do_something($itemList[1]->{'attrib2'}
。
我做错了什么,谢谢你?
您遇到了 Perl 中 for
循环的一个微妙特征。首先解决方案:在 for 循环中使用 my
。然后 $item
将能够在您稍后在循环中声明的匿名子中创建一个适当的闭包。
for my $item (@itemlist) {
push(@btn_list, new_ttk__button(
-text => $item->{'attrib1'},
-command => sub {do_something($item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
进一步说明:Perl 隐式地本地化了 for 循环的主题变量。如果您不在 for 循环中使用 my
,循环将使用包变量的本地化版本。这使您的代码等同于:
package main;
$main::item = undef;
@itemList = ({'attrib1' => 'name1', 'attrib2' => 'value1'},
{'attrib1' => 'name2', 'attrib2' => 'value2'});
$row = 0;
foreach (@itemList) {
local $main::item = $_;
push(@btn_list, new_ttk__button(
-text => $main::item->{'attrib1'},
-command => sub {do_something($main::item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
# at the end of the loop, value of $main::item restored to undef
您的匿名子程序仍然引用 $main::item
包变量,无论该变量在调用这些子例程时持有什么值,这可能是 undef
.
较短的解决方案:use strict
额外的概念验证。尝试猜测以下程序输出的内容:
@foo = ( { foo => 'abc', bar => 123 },
{ foo => 'def', bar => 456 } );
my @fn;
foreach $foo (@foo) {
push @fn, sub { "42" . $foo->{bar} . "\n" };
}
foreach my $foo (@foo) {
push @fn, sub { "19" . $foo->{foo} . "\n" };
}
print $_->() for @fn;
答案如下:
42
42
19abc
19def