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

Hello, wise perl users!

Let's consider quite simple task and it's solution.

It is required to place Tk::Entry widget and allow only digits inside it, but there must be at least one digit.

Problem is quite common, and after reading documentation I wrote following code:

use Tk; my $x = 0; tkinit->Entry(-textvariable=>\$x,-validate=>'key', -validatecommand=>sub{ return 0 unless $_[0]=~/^\d+$/; 1; })->pack; MainLoop;
Nothing complicated here, so all seems to be okay.

Let's start it and type digits. Validating works good.

But sometimes not that good. Not very good is when I select all contents inside that widget and after that will try typing a number. Unexpectedly, instead of replacing previous text with a new one, following if occured: number appears at the very start of Entry widget, and all previous text remains and furthermore it is selected!
This is not very intuitive.

I've searched a bit inside Tk::Entry module and found the reason of this behaviour, which is not very hard to explain.

<KeyPress> callback calls 'Insert' function and it deletes selection at first step and insert new text at a second. So, when selection is tried to be deleted at first, validation fails, and after that next keystroke appended.

And not only validation for number will misbehave, but many other validations as well, so I consider current implementation of validation should be improved somehow.

Does anyone faced similar problem? How this could be solved?

Needless to say, the most elegant way will be found the best :)

Best wishes,

Replies are listed 'Best First'.
Re: Tk::Entry validate question
by graff (Chancellor) on Jun 16, 2002 at 04:54 UTC
    Maybe it would be appropriate to use two different variables; one for the Entry "-textvariable", and another for the Scale "-variable"; then, use callbacks to keep the two in sync -- something like this:
    #!/usr/bin/perl -w use strict; use Tk; my $scale_var = 0; my $entry_var; my $main = MainWindow->new(); my $entry = $main->Entry(-textvariable => \$entry_var, )->pack(); my $scale = $main->Scale(-variable => \$scale_var, -length => '100p', -from => 0, -to => 100, -command => sub { $entry_var = $scale_var }, )->pack(); $entry->bind( '<Return>', sub { if ( $entry_var =~ /^\d+$/ && $entry_var <= 100 ) { $scale_var = $entry_var; } else { print STDOUT "\a\a"; $entry->delete( 0, 'end' ); } } ); MainLoop;
    Here, the "-command" callback of the Scale widget will update the "-textvariable" of the Entry widget whenever the Scale is adjusted manually -- works just as well as using the same variable for both widgets.

    When the user decides to type a value into the Entry to control the Scale position, this approach requires that the "<Return>" key be used to signal that the user is done editing the Entry content and the string value is ready for application to the Scale variable. The binding of the "<Return>" event in the Entry widget will handle the validation -- checking not only that it is all digits, but also that the (integer) number value falls within the min-max range for the Scale widget.

    Personally, I tend to prefer this sort of approach for using strings in an Entry -- let users make as many edit operations as they want, of whatever sort, so long as they issue an explicit event once they decide that the string value is ready for use. Tk::Entry's "validate" mechanism does not support this sort of trigger -- I suppose because it would be redundant, since "bind" does this perfectly well.

    update: I know a lot of people do prefer instant feedback, so instead of binding on the "<Return>" event, one could instead bind on "<KeyRelease>", and change (simplify) the callback slightly:

    $entry->bind( '<KeyRelease>', sub { if ( $entry_var =~ /^\d+$/ && $entry_var <= 100 ) { $scale_var = $entry_var; } } );
    In this case, the Entry variable is simply ignored if it is empty or contains invalid data; otherwise, it is applied to the Scale variable as soon as each valid character is entered each time a KeyRelease event leaves a valid string in the Entry variable.
Re: Tk::Entry validate question
by Abigail-II (Bishop) on Jun 14, 2002 at 18:10 UTC
    That's because your validation check requires at least one number to be present. If you select all, then type, Tk will first try to delete everything - however, an empty entry is invalid, so the deletion fails.

    The fix is simple. Just change the first line of your sub to:

    return 0 if $_[0]=~/\D/;


      I tried my best to explain a problem but seem not to succeed:

       1. There was an analyzis of a problem inside my message, so your explanation just rewords mine
       2. return 0 if $_[0]=~/\D/; is not enough, because I said there should be at least one digit.
       3. I noticed that I'm worried about current validation scheme in general, where validation occures one extra time, and not just once at the end of change. Number validation was just an example.

      Best wishes,

        Ah, I see. Well, one thing you could do is to not validate when there's a delete (using $_ [3]). But that would enable people to erase everything (leaving nothing). I guess you could counteract that by validating on focusout as well, and using invalidCommand to put 0 or so back in the widget (or the last legal entry).