f



Perl/Tk: can't get -command=> \&function to work 'on-the-fly'!!

Please Help!

I am trying to define buttons 'on-the-fly' mostly because their number
is determined in a config file - I don't know how many I will need.
It's ROYALLY NOT WORKING. I think Tk.pm is having issues. And it's
all driving me very... verrrryyy... batty.

*** I WANT THIS TO WORK *** (pseudo-code)
for i=1;i<100,i++ {
	$handle[i] = menubar->command(-label => "test_i",
		%somecolors, -command => Move2Folder(i));
*** IS THIS TOO MUCH TO ASK??? ***

1) If I 'unroll it' (see end of this message) AND wrap the Move2Folder
function in a static function (ie. _Move2Folder2) it executes fine!

$folder[1]=$menubar->command(-label => "TEST1",
	%menubutton_colors, -command => \&_Move2Folder1);
$folder[2]=$menubar->command(-label => "TEST2",
	%menubutton_colors, -command => \&_Move2Folder2);
etc.

Menu builds fine, clicking on TEST1 executes function A-OK, TESTx too.

2) If I 'unroll it' (see end of this message) BUT do NOT 'wrap' the
Move2Folder function in a static function (ie. _Move2Folder2)
it EXECUTES the functions DURING definition of the button!!
(ie. when window being built, not afterwards, upon mouse click!)

$folder[1]=$menubar->command(-label => "TEST1",
	%menubutton_colors, -command => \&Move2Folder(1));
$folder[2]=$menubar->command(-label => "TEST2",
	%menubutton_colors, -command => \&Move2Folder(2));
etc.

So, I can't pass arguments. Great.

3) I read in some Tk/perl tutorial:
"The -command option specifies which subroutine to call on invoking
the
menu entry. For callbacks, one can use subroutine references,
anonymous
subroutines or an array reference with a subroutine reference as the
first element and arguments in the remaining elements."

So I tried the array reference but, of course, I can't win. It doesn't
help that I don't know EXACTLY what "with a subroutine reference as
the
first element and arguments in the remaining elements" means (they
couldn't
be bothered to give an example...) If I specify the explicit function
call:

for ($cctt=5;$cctt <= $globalcfg->{'morefolders'}; $cctt++) {
	$cmdinfo[$cctt] = "&Move2Folder($cctt)";
	my $folder=$menubar->command(-label => "TEST$cctt",
		%menubutton_colors, -command => \@cmdinfo[$cctt]);

I get this error:

Tk::Error: Undefined subroutine &main::&Move2Folder(5) called at
/usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi/Tk.pm line 228.
 \&Move2Folder(5)
 Tk callback for .toplevel.menu1
 Tk::__ANON__ at /usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi/Tk.pm
line 228
 Tk::Menu::Invoke at
/usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi/Tk/Menu.pm line
530
 (command bound to event)

Why is it putting &main::&Move2Folder? (& twice)?

4) If I do it without the explicit function call:

for ($cctt=5;$cctt <= $globalcfg->{'morefolders'}; $cctt++) {
        $cmdinfo[$cctt] = "Move2Folder($cctt)";
        my $folder=$menubar->command(-label => "TEST$cctt",
                %menubutton_colors, -command => \@cmdinfo[$cctt]);

I get:

Tk::Error: Can't locate auto/Tk/Move2Folder(5).al in @INC (@INC
contains:
/usr/local/vocp/lib /usr/lib/perl5/5.8.0/i386-linux-thread-multi
/usr/lib/perl5/5.8.0
/usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi
[blah blah] /usr/lib/perl5/vendor_perl .) at
/usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi/Tk.pm line 228
 Carp::croak at /usr/lib/perl5/5.8.0/Carp.pm line 191
 AutoLoader::AUTOLOAD at /usr/lib/perl5/5.8.0/AutoLoader.pm line 110
 \Move2Folder(5)
 Tk callback for .toplevel.menu1
 Tk::__ANON__ at /usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi/Tk.pm
line 228
 Tk::Menu::Invoke at
/usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi/Tk/Menu.pm line
530
 (command bound to event)

That's odd, "sub Move2Folder {" is defined on line 810 while this call
takes place on line 2762. What the heck?

5) I've tried more variations on eval than I care to admit. None work
as
desired. The fundamental problem is that Tk.pm, line 228 is PURE
EVIL!  EVIL!  EVIL!  EVIL!  EVIL!  EVIL!  EVIL!  EVIL!  EVIL!  EVIL!

Right now I have the code 'working'... (YEAH, RIGHT). I "unrolled" the
loop and "defined" the first damned 100 folders AND the 100 static
wrapper functions with an awk script (isn't that an insult in a perl
group?). Compilers call this optimized,I call it retarded. Tell me you
agree!

Please help! An example of HOW the heck this is done would help in me
not
rewriting this whole mess in shell script or maybe logo or cobol or C!

Specs:
Vanilla RedHat 8.0, k2.4.20
Perl 5.8.0
Tk 804.024
(hacking vocphax 0.9.3, if you must ask)

Cheers,
Fil
www.repairfaq.org
0
filipg
7/25/2003 5:25:13 PM
comp.lang.perl.moderated 512 articles. 0 followers. Post Follow

8 Replies
781 Views

Similar Articles

[PageSpeed] 17

Filip Gieszczykiewicz wrote:
> I think Tk.pm is having issues.

Well, something (or someone) is having issues...


> *** I WANT THIS TO WORK *** (pseudo-code)
> for i=1;i<100,i++ {
> 	$handle[i] = menubar->command(-label => "test_i",
> 		%somecolors, -command => Move2Folder(i));


> The fundamental problem is that Tk.pm, line 228 is PURE
> EVIL!  EVIL!  EVIL!  EVIL!  EVIL!  EVIL!  EVIL!  EVIL!

The fundamental problem is that your perl prowess is
insufficient for the task.  It certainly isn't Tk's fault.
If you're looking for a scapegoat, blame perl's steep
learning curve. [1]

The way you would do it is as follows.
(But as always, there's more than one way.)

	$handle[$i] = $menubar->command( -label => "test_$i",
		%somecolors,
		-command => sub { Move2Folder($i) } );

Does that make sense, I hope?

-- 
John Douglas Porter

[1] Please let's not reopen that semantic debate.


__________________________________
Do you Yahoo!?
Yahoo! SiteBuilder - Free, easy-to-use web site design software
http://sitebuilder.yahoo.com
0
John
7/25/2003 9:01:15 PM
On 25 Jul 2003 10:25:13 -0700, Filip Gieszczykiewicz wrote:
>1) If I 'unroll it' (see end of this message) AND wrap the Move2Folder
>function in a static function (ie. _Move2Folder2) it executes fine!
>
>$folder[1]=$menubar->command(-label => "TEST1",
>	%menubutton_colors, -command => \&_Move2Folder1);
>$folder[2]=$menubar->command(-label => "TEST2",
>	%menubutton_colors, -command => \&_Move2Folder2);
>etc.

This is correct: you are supplying the name of a function and taking
a reference of that function's address.

>2) If I 'unroll it' (see end of this message) BUT do NOT 'wrap' the
>Move2Folder function in a static function (ie. _Move2Folder2)
>it EXECUTES the functions DURING definition of the button!!
>(ie. when window being built, not afterwards, upon mouse click!)
>
>$folder[1]=$menubar->command(-label => "TEST1",
>	%menubutton_colors, -command => \&Move2Folder(1));
>$folder[2]=$menubar->command(-label => "TEST2",
>	%menubutton_colors, -command => \&Move2Folder(2));
>etc.

That's correct behaviour: you are supplying a function with
some arguements so it'll execute the function. What you'll
get back is a reference to the result of the function.

>So, I can't pass arguments. Great.

Not like that you can't.

The correct solution is to use an anonymous subroutine as
described by the other person who replied. There, the
subroutine is only called on the button click TK action
thing but the $i was set earlier.

Because you're not actually calling a function with some
arguments in the TK structure, it won't execute when
building the menu.[0]

Matthew
-- 
Matthew Sackman

[0] I suspect the real reason is somewhat more complex but
this one may be considered good enough!
0
ms02
7/25/2003 10:24:28 PM
John Douglas Porter <johndporter@yahoo.com> wrote in message news:<20030725210115.27517.qmail@web10808.mail.yahoo.com>...
> Filip Gieszczykiewicz wrote:
> > I think Tk.pm is having issues.
> > *** I WANT THIS TO WORK *** (pseudo-code)
> > for i=1;i<100,i++ {
> > 	$handle[i] = menubar->command(-label => "test_i",
> > 		%somecolors, -command => Move2Folder(i));
>
> The fundamental problem is that your perl prowess is
> insufficient for the task.  It certainly isn't Tk's fault.
> If you're looking for a scapegoat, blame perl's steep
> learning curve. [1]

I will give you that. I'm a weekend-perl-hacker not a full-timer.

> The way you would do it is as follows.
> (But as always, there's more than one way.)

I hope the other way works because this way does not.

> 	$handle[$i] = $menubar->command( -label => "test_$i",
> 		%somecolors,
> 		-command => sub { Move2Folder($i) } );

The problem is that while the menu now is built correctly, clicking
on the buttons (any of them) ALWAYS calls Move2Folder with the LAST value
$i takes on AFTER the for() loop. So, if the limit is 49, Move2Folder(50)
is called. I had this exact problem with my eval() attempts - it was
calling the correct function BUT the argument was always of the form above.
This is the part that is driving me bonkers. Alas, I am too poor of a 
programmer to construct a proper script to isolate this problem... so I'm 
stuck with Tk and don't know if there's a bug there or it's me that sucks. 

> Does that make sense, I hope?

It would have made sense if it worked. Please understand that I've
gotten this to work in other programs before, but here it does not.
I understand the difference between passing the address of the function
as an argument but perl or Tk is not doing that for the argument. 

I tried also:

        my $func2call = 'sub { Move2Folder($i); }';
 	$handle[$i] = $menubar->command( -label => "test_$i",
 		%somecolors,
 		-command => eval $func2call );

which does exactly what your suggestion does, ie. build menu fine but
then call the value of $i which is 1+ the test value in the for() loop.

I would like to know WHY. How can I force it to just take the literal
value of the $i (as the rest of the line does just fine) and NOT refer
to $i's value 'later'?

Please advise what I am doing wrong or how I can kludge around this
'feature'.

Cheers,
Filip

P.S. Given my perl-prowess, I was the first to doubt my abilities... but it's
been a whole day pretty much wasted trying to get this to work. While my
awk script works dandy, it's not a sane solution to this problem as there
are MANY places in the code where similar mechanisms are used... and if I
'unroll' all that code (and over-estimate the users' needs) the source
will be 2MB, not 90Kb....
0
filipg
7/26/2003 4:40:51 AM
I think your difficulty is with anonymous functions and code
references in general, not particularly with Tk.  To understand what
is going on you could write a standalone program like:

    use warnings; 
    use strict; 
    sub f( $ ) { print "hello $_[0]\n" } 
    my $a; 
 
    # Now, what do the following do? 
    $a = &f; 
    $a = \&f; 
    $a = &f(55); 
    $a = \&f(66);  # (I don't know if this is legal) 
    $a = sub { f(77) }; 
 
    # Perhaps there is a difference between capturing a 'my' variable 
    # in a closure and referring to a global variable. 
    # 
    my (@l0, @l1); 
    our $global;
    for ($global = 1; $global <= 2; $global++) {
	push @l0, sub { f($global) };
    }
    foreach my $lexical (1, 2) { push @l1, sub { f($lexical) } } 

    # Now try calling some of the coderefs defined. 
    print "calling\n";
    $a->(); 
    $_->() foreach @l0; 
    $_->() foreach @l1; 

>> 	$handle[$i] = $menubar->command( -label => "test_$i",
>> 		%somecolors,
>> 		-command => sub { Move2Folder($i) } );
>
>The problem is that while the menu now is built correctly, clicking
>on the buttons (any of them) ALWAYS calls Move2Folder with the LAST
>value $i takes on AFTER the for() loop.

Yes - your sub {} asks for the value of $i, and gets it.  If you want
to capture the $i from when the sub was defined, you need to make $i a
lexically scoped ('my') variable.

    my $f; # code reference to be set below.
    {
        my $i = 5;
        # Inside this block $i is in scope.
        $f = sub { print "$i\n" };
    }

    # Outside the block, $i is not in scope:
    print $i;  # FAILS

    # But the anonymous sub referring to it still works:
    $f->();

I suggest turning on 'use strict' for your program, which will force
you to state whether each variable should be lexically scoped, global,
or whatever.  Adding 'use strict' to an existing program can be quite
a task however.  But certainly try it for any new programs you write.

-- 
Ed Avis <ed@membled.com>
0
Ed
7/26/2003 8:35:14 AM
On 25 Jul 2003 21:40:51 -0700, Filip Gieszczykiewicz wrote:
>John Douglas Porter <johndporter@yahoo.com> wrote:
>> 	$handle[$i] = $menubar->command( -label => "test_$i",
>> 		%somecolors,
>> 		-command => sub { Move2Folder($i) } );
>
>The problem is that while the menu now is built correctly, clicking
>on the buttons (any of them) ALWAYS calls Move2Folder with the LAST value
>$i takes on AFTER the for() loop. So, if the limit is 49, Move2Folder(50)
>is called. I had this exact problem with my eval() attempts - it was
>calling the correct function BUT the argument was always of the form above.

Try the following which works:

$> perl -e '$self = {};
	    for ($i = 0; $i < 50; $i++) {
	        my $g = $i;
	        $self->{$i} = sub {hello($g)};
            }
	    sub hello {$a = shift; print "$a\n"}
            foreach my $key (sort {$a <=> $b} keys %$self) {&{$self->{$key}}}'

The point is that when you are in the anonymous sub routine, the value stored
is local to the particular iteration of the loop due to the 'my $g = $i'
whereas before, you were storing the global value $i. You just need to
implement this idea in your code - do a my $t = $i at the start of your loop
and then in your $handle[$i] code, the sub { Move2Folder($i) } becomes
sub { Move2Folder($t) }. That should fix it.

Good luck

Matthew
-- 
Matthew Sackman
0
ms02
7/26/2003 12:06:08 PM
ms02@doc.ic.ac.uk (Matthew Sackman) wrote in message news:<slrnbi3bgr.5m7.ms02@fig.doc.ic.ac.uk>...
> The correct solution is to use an anonymous subroutine as
> described by the other person who replied. There, the
> subroutine is only called on the button click TK action
> thing but the $i was set earlier.

Greetings. But as I wrote in my followup, that is not happening -
especially the latter part. While the function is being called
correctly, the WRONG value of $i is being received:

sub doit {
  my $argument = shift;
  warn "DBG: doit($argument) called.";
}#return

sub build_menu {
    [...]
    my $i;
    for ($i=5;$i<100;$i++) {
        $menubar->command([...] -command=> sub {doit($i);});
    }#for
    return;
}#return

Menu is being built correctly, clicking on button 5 through 100 gives:

DBG: doit(101) called.

Is THIS correct behaviour? I am unsure if your statement "subroutine
is only called on the button click TK action thing but the $i was set
earlier" is incorrect either way? $i "[that] was set earlier" is being
used, allright, just not the value of $i that I WANT :-)

I tried this too:

    my $callfunc = 'sub { doit($i);}';
    for ($i=5;$i<100;$i++) {
        $menubar->command([...] -command=> eval $callfunc );
    }#for

with exactly the same results. How can I FORCE perl to evaluate
$i inside the sub{} so that it 'saves' the value from each iteration?
It seems to be leaving the variable reference $i and OF COURSE, when the
menu is done building, $i really IS 101. But that is EXACTLY what I do NOT
want. I was doit(6); doit(7), etc... Not happening. Why?

> Because you're not actually calling a function with some
> arguments in the TK structure, it won't execute when
> building the menu.[0]

Yes, I understand.

Thank you to all who have replied. Maybe I'll beat it to work today.

Cheers,
Fil
0
filipg
7/26/2003 4:10:48 PM
In article <bc598ed6.0307252040.1ad13341@posting.google.com>,
Filip Gieszczykiewicz <filipg@repairfaq.org> wrote:
>John Douglas Porter <johndporter@yahoo.com> wrote in message
>news:<20030725210115.27517.qmail@web10808.mail.yahoo.com>...
>> Filip Gieszczykiewicz wrote:
>> > I think Tk.pm is having issues.
>> > *** I WANT THIS TO WORK *** (pseudo-code)
>> > for i=1;i<100,i++ {
>> > 	$handle[i] = menubar->command(-label => "test_i",
>> > 		%somecolors, -command => Move2Folder(i));
>>
>> 		-command => sub { Move2Folder($i) } );
>
>The problem is that while the menu now is built correctly, clicking
>on the buttons (any of them) ALWAYS calls Move2Folder with the LAST value
>$i takes on AFTER the for() loop. So, if the limit is 49, Move2Folder(50)

Have you tried copying the value to a lexical (my) variable?

  foreach (1 .. 100) {
    my $ii = $_;	# Use lexical variable for anon subroutines
    $handle[$ii] = menubar->command(
			-label => "test_$ii", %somecolors,
		    	-command => sub { Move2Folder($ii) }
				  );
  }
-- 
See http://www.inwap.com/ for PDP-10 and "ReBoot" pages.
0
inwap
7/28/2003 8:21:36 AM
Filip Gieszczykiewicz wrote:
> 
> Please Help!
> 
> I am trying to define buttons 'on-the-fly' mostly because their number
> is determined in a config file - I don't know how many I will need.
> It's ROYALLY NOT WORKING. I think Tk.pm is having issues. And it's
> all driving me very... verrrryyy... batty.
> 
> *** I WANT THIS TO WORK *** (pseudo-code)
> for i=1;i<100,i++ {
>         $handle[i] = menubar->command(-label => "test_i",
>                 %somecolors, -command => Move2Folder(i));
> *** IS THIS TOO MUCH TO ASK??? ***

   for my $i ( 1 .. 100 ) {
      $handle[$i] = $menuar->command(
         -label => "test_$i", %somecolors,
         -command => [ \&Move2Folder, $i ]
      );
   }

-- 
$a=24;split//,240513;s/\B/ => /for@@=qw(ac ab bc ba cb ca
);{push(@b,$a),($a-=6)^=1 for 2..$a/6x--$|;print "$@[$a%6
]\n";((6<=($a-=6))?$a+=$_[$a%6]-$a%6:($a=pop @b))&&redo;}
0
Benjamin
7/28/2003 10:22:11 PM
Reply: