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

Hi gurus, monks, monkeys, and other caffeinated types,

I have a small Tk gui that uses Tk::Entry input to get a MAC. I got most of the code from an old thread here. It works great, but I found a weird bug:

When I put invalid text into a field that represents a MAC octet (e.g. "zz"), and then try to leave the field (via Tab key or mouse-click), it properly forces me back into the field with the errant entry.

The problem is, if I hit the Tab key twice in quick succession, it will pop me out of the bad field, and what's more (worse), will seem to "forget" about the field, as you will see if you run the code.

To replicate, enter "zz" in a field, double-hit Tab quickly, then hit Tab regularly to hop thru the other (blank) fields. When you come back around to the "zz" field, it won't run the validation check again - no matter if the bad string is removed/replaced, etc.

#!/usr/bin/perl use strict; use warnings; use Tk; my $mw = MainWindow->new(-title => 'Mac Address Input Example'); my $fr = $mw->Frame()->pack(-expand => 1, -fill => 'both'); my $lb = $fr->Label(-text => 'MAC Address')->pack(-side => 'left'); my %entries; for(1..6){ $entries{$_}{'entry'} = $fr->Entry(); $entries{$_}{'entry'}->configure( -textvariable => \$entries{$_}{'addy'}, -width => 3, -validate => 'focusout', -validatecommand => [ \&validate,$_ ], -invalidcommand => [ \&show_invalid,$entries{$_}{'entry'} ], ); $entries{$_}{'entry'}->pack(-side => 'left'); } MainLoop(); # returns `1' if valid, and `0' if invalid sub validate { my($num,$val) = @_; unless($val){ print "Field $num has nothing to validate\n";return 1; +} my $valid = ($val =~ /^[0-9a-f]{1,2}$/i) ? 1 : 0; printf "Field %s, validating \`%s'...%s\n",$num,$val,$valid? "ok": " +FAILED"; return $valid; } sub show_invalid { my($widget) = @_; $widget->focus(); # temporarily flash the background of the problem field in red my $bg = $widget->cget('-bg'); $widget->configure(-bg => 'red'); $widget->update(); # effect a sleep of 200 milliseconds select(undef,undef,undef,0.2); # return the background color to the original state $widget->configure(-bg => $bg); $widget->update(); }

Edit: If you can't replicate the problem using the Tab key, it also seems to happen if you pre-populate a field's textvariable with an invalid string.

For example, inserting this line into the loop will make the 3rd field fail initial validation, but will allow focus to pass thru it without subsequently attempting validation:

for(1..6){ $entries{$_}{'addy'} = 'zz'.$_ if($_ == 3);

Replies are listed 'Best First'.
Re: Tk::Entry and double-Tab key weirdness
by zentara (Cardinal) on May 25, 2012 at 19:28 UTC
    I don't know exactly what you expected to do with a sleep statement in your show_invalid sub
    # effect a sleep of 200 milliseconds select(undef,undef,undef,0.2);
    but if I comment out that sleep, fast tabbing WILL NOT advance on an invalid entry like 'zz'.

    I get:

    Field 1, validating `zz'...FAILED Field 2 has nothing to validate Field 1, validating `zz'...FAILED Field 2 has nothing to validate Field 1, validating `zz'...FAILED
    So Field 1 is getting revalidated after bouncing back from Field 2.

    Putting any sleep statement in an eventloop system is generally a bad thing to do, and can yield unexpected bugs, as you have found out. :-)


    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh
      @zentara,

      First of all, thanks for pointing that out - you are absolutely right (no shocker there), removing that sleep fixes the double-Tab issue. Why am I using it? B/c that is what the code I borrowed had. Why did the original code have it? I believe to give it that flashing red background effect...something I can (and will) live without.

      However, the issue is still there if a textvariable is pre-defined with a bad string (as I noted in the update to my OP). This is more important than the other issue, b/c this will definitely crop up. The textvariable may or may not be pre-defined before displaying the widget, but if it is, and it is invalid, I would like validation to work.

      Any idea what is going on there?

      Edit: seems to work if I first create the widgets, then go back and configure them:

      for(1..6){ $entries{$_}{'entry'} = $fr->Entry(); $entries{$_}{'entry'}->configure( -textvariable => \$entries{$_}{'addy'}, -width => 3, # -validate => 'focusout', # -validatecommand => [ \&validate,$_,\%entries ], # -invalidcommand => [ \&show_invalid,$entries{$_}{'entry'},$_ ], ); $entries{$_}{'entry'}->pack(-side => 'left'); } for(1..6){ $entries{$_}{'addy'} = 'zz'.$_ if($_ == 3); $entries{$_}{'entry'}->configure( -validate => 'focusout', -validatecommand => [ \&validate,$_,\%entries ], -invalidcommand => [ \&show_invalid,$entries{$_}{'entry'},$_ ], ); }
        This might help you. It changes color, but dosn't flash. Also it helps to use 'any' for the validate method, rather than focusout. The key lines are
        $mw->waitVisibility; $mw->after(100,sub{$entries{3}{'entry'}->focusForce});
        Here is a working script that should show you the way of highlighting a textvariable which is invalid. You can perfect it yourself. :-)
        #!/usr/bin/perl use strict; use warnings; use Tk; my $mw = MainWindow->new(-title => 'Mac Address Input Example'); my $fr = $mw->Frame()->pack(-expand => 1, -fill => 'both'); my $lb = $fr->Label(-text => 'MAC Address')->pack(-side => 'left'); my %entries; for(1..6){ $entries{$_}{'entry'} = $fr->Entry(); $entries{$_}{'entry'}->configure( -textvariable => \$entries{$_}{'addy'}, -width => 3, # -validate => 'focusout', # -validatecommand => [ \&validate,$_,\%entries ], # -invalidcommand => [ \&show_invalid,$entries{$_}{'entry'},$_ ], ); $entries{$_}{'entry'}->pack(-side => 'left'); } for(1..6){ $entries{$_}{'addy'} = 'zz'.$_ if($_ == 3); $entries{$_}{'entry'}->configure( #-validate => 'focusout', -validate => 'all', -validatecommand => [ \&validate,$_,\%entries ], -invalidcommand => [ \&show_invalid,$entries{$_}{'entry'},$_ ], ); } $mw->waitVisibility; $mw->after(100,sub{$entries{3}{'entry'}->focusForce}); MainLoop(); # returns `1' if valid, and `0' if invalid sub validate { my($num,$val) = @_; unless($val){ print "Field $num has nothing to validate\n";return 1; +} my $valid = ($val =~ /^[0-9a-f]{1,2}$/i) ? 1 : 0; printf "Field %s, validating \`%s'...%s\n",$num,$val,$valid? "ok": " +FAILED"; return $valid; } sub show_invalid { my($widget) = @_; # print "@_\n"; $widget->focus(); # background of the problem field in red my $bg = $widget->cget('-bg'); $widget->configure(-bg => 'lightpink'); $widget->update(); }

        I'm not really a human, but I play one on earth.
        Old Perl Programmer Haiku ................... flash japh
        For all sleeping in tk apps gui frameworks use tk that gui framework timer functions, use Tk::after

        ...that is what the code I borrowed had..

        The road to Hell is paved with borrowed code:-).
Re: Tk::Entry and double-Tab key weirdness
by toolic (Bishop) on May 25, 2012 at 17:08 UTC
    The problem is, if I hit the Tab key twice in quick succession, it will pop me out of the bad field
    I can't replicate this behavior. It properly forces me back into the field with the errant entry, just as if I hit tab once. I am using Tk version 804.03 on:
    perl -v This is perl 5, version 12, subversion 2 (v5.12.2) built for x86_64-li +nux
      Hmmm...that's interesting...

      Tk version 804.029

      perl -v

      This is perl 5, version 14, subversion 2 (v5.14.2) built for i386-linux-thread-multi

      I guess I can try the code on another machine w/different versions of perl/Tk...in the mean time, can anyone else replicate it?

      Edit: Seeing the same issue on CentOS 6 box with perl v5.10.1 and Tk 804.028

      Edit2: And in case it is related to WMs...also seeing the issue in Fluxbox in a VNC session, in F16/Gnome 3.2.1 desktop, and RHEL6.0/Gnome 2.28 desktop.