Updated 2011-07-05 02:17:59 by RLE

A few general aspects are discussed in entry validation.

The validation is performed at two levels:

1. Weak validation:
   - Allow empty string or sign only
   - Verify sign

2. Strong validation:
   - Strong integer and range check
   - Change the entry background color to yellow/red on error

The "focusout" validation is optional, it can be removed.

Sample code:
    proc validInteger {win event X oldX min max} {
        # Make sure min<=max
        if {$min > $max} {
            set tmp $min; set min $max; set max $tmp
        }
        # Allow valid integers, empty strings, sign without number
        # Reject Octal numbers, but allow a single "0"
        # Which signes are allowed ?
        if {($min <= 0) && ($max >= 0)} {   ;# positive & negative sign
            set pattern {^[+-]?(()|0|([1-9][0-9]*))$}
        } elseif {$max < 0} {               ;# negative sign
            set pattern {^[-]?(()|0|([1-9][0-9]*))$}
        } else {                            ;# positive sign
            set pattern {^[+]?(()|0|([1-9][0-9]*))$}
        }
        # Weak integer checking: allow empty string, empty sign, reject octals
        set weakCheck [regexp $pattern $X]
        # if weak check fails, continue with old value
        if {! $weakCheck} {set X $oldX}
        # Strong integer checking with range 
        set strongCheck [expr {[string is int -strict $X] && ($X >= $min) && ($X <= $max)}]

        switch $event {
            key {
                $win configure -bg [expr {$strongCheck ? "white" : "yellow"}]
                return $weakCheck
            }
            focusout {
                if {! $strongCheck} {$win configure -bg red}
                return $strongCheck
            }
            default {
                return 1
            }
        }
    }

    proc checkForm {args} {
        set err 0
        foreach win $args {
            switch -- [$win cget -bg] {
                red -
                yellow {
                    incr err
                }
            }
        }
        if {$err} {
            tk_messageBox -type ok -icon error -message "Check $err invalid field(s)"
        } else {
            tk_messageBox -type ok -message "Changes applied successfully"
        }
    }

    set x1 12; set x2 345; set x3 -678

    label .t1 -text "int (10..92)"
    entry .e1  -textvariable x1 -validate all -vcmd {validInteger %W %V %P %s +10 +92} 
    label .t2 -text "int (-256..+1024)" 
    entry .e2 -textvariable x2 -validate all -vcmd {validInteger %W %V %P %s +1024 -256}
    label .t3 -text "int (-768..0)"
    entry .e3 -textvariable x3 -validate all -vcmd {validInteger %W %V %P %s -768 0}

    button .apply -text "Apply changes" -command {checkForm .e1 .e2 .e3}

    grid .t1 .e1 -sticky w
    grid .t2 .e2 -sticky w
    grid .t3 .e3 -sticky w
    grid .apply - -sticky ew

Rolf Schroedter using hints given by Jeffrey Hobbs and DKF

RJM: The textvariable can still have invalid values, so this example may use the textvariable only as a temporary value, which would be copied in other variables after hitting the "Apply changes" button.

Sometimes however it is useful to have the -vcmd also update another data representation during the alteration of the value (e.g. while spinning a spinbox). This may be desired when a graphical representation of the value must be updated simultaneously. Moreover, a dual variable concept complicates entry or spinbox updates when associated variables are updated anywhere else.

The following example not only checks limits, but also restricts to limit during entry. This is suboptimal and quite dangerous (the entry manual warns in this respect). Actually, upon autolimitation, the -vcmd is called again after the textvariable's alteration is recognized in the Tk event loop.
 ..... -vcmd "after idle {%W config -validate %v}; validate_time $index %P"
 proc validate_time {index value} {
    global sonopar

    if {![string is double $value]} {return 0}
    if {!$sonopar(update)} {
        after idle graphupdate . ;# ensure update AFTER validation accepted by spinbox widget
    }
    if {$value < 0.2} {set sonopar(time,$index) 0.2; return 0}
    if {$value > 25.5} {set sonopar(time,$index) 25.5; return 0}
    return 1
 }

Of course, extensions in the sense of background colouring and others may be considered.