data entry validation with Tkinter

Russell E. Owen owen at astroNOJNK.washington.edu.invalid
Tue Aug 7 19:26:29 EDT 2001


In article <mailman.997189391.19119.python-list at python.org>, Ken Guest 
<kwg at renre-europe.com> wrote:

>On 07 Aug 2001 13:16:36 +0100, Martin Franklin wrote:
>> Ken,
>> 
>> Take a look at the Pmw (Python Mega Widgets)
>> they've got an EntryField with validation plus some
>> built in validators (including dates, numbers, etc)
>> I use it for validating filenames.
>
>hi Martin,
>thanks for that, but I'm  fairly much committed to using tkinter.
>Can they both (tkinter and pmw) coexist within the same application?
>That might make things more likely to be reconsidered
>(as we're migrating a tcl+tk app over to python and really don't
>want to change too much of it too quickly).

Pmw is basically a wrapper for Tkinter, so they work together very well.

I found the Pmw widgets did not do what I wanted, so I rolled my own 
using Tk variables to fire a checking function whenever the user types a 
character in the entry widget. A copy of one example is appended. I also 
have integer and sexagesimal (e.g. dd:mm:ss.s) entry widgets (which 
subclass FloatEntry), in case those are of interest.

Notes:
- var.trace_variable is the heart of the checker. var is a Tk variable 
associated with the entry widget. trace_variable sets a callback 
function that is called whenever the variable is changed.
- ROStringUtil.strToFloat is similar to Float but is more liberal in 
what it accepts, for instance "" is 0.0 and None is NaN and is displayed 
as blank. Full source available on request.

Any fixes or suggestions for improvement would be most welcome.

Sorry for the anti-spam return address.

-- Russell

class FloatEntry (Entry):
    """A widget for entering floating point numbers with validity and 
range checking.
    
    Warning: cannot handle exponential notation, yet; as soon as you 
type an e
    the string can no longer be parsed as a float. This could certainly 
be fixed
    but it might be best to make a separate widget for the purpose.
    """
    
    def __init__ (self, master, minValue, maxValue, defValue=None, 
var=None, **kargs):
        """Create a FloatEntry widget.

        Inputs:
            master      master Tk widget -- typically a frame or window
            minValue    minimum acceptable value; a number is required
            maxValue    maximum acceptable value; a number is required
            defValue    default value; if supplied then it must be a 
valid number
            var         a StringVar to use as the widget's Tk variable
            **kargs keyword arguments to configure the widget;
                the default width is 8
                text and textvariable are silently ignored (use var 
instead of textvariable)
        """
        if var:
            self.var = var
        else:
            self.var = StringVar()
        self.minValue = minValue
        self.maxValue = maxValue
        self.defValue = defValue
        if not kargs:
            kargs = {}
        kargs.setdefault("width", 8)
        kargs.setdefault("justify", RIGHT)
        if kargs.has_key("text"):
            del(kargs["text"])
        kargs["textvariable"] = self.var  # overrides user attempt to set
        
        Entry.__init__(self, master, **kargs)
        
        self.oldVal = self.var.get()
        self.var.trace_variable("w", self._checkVar)
        
        # set default -- do after binding _checkVar, so default is 
checked
        self.restoreDefault()

        self.bind("<FocusOut>", self._focusOut)

    def clear(self):
        self.var.set("")
    
    def getDefault(self):
        return self.defValue

    def getFloat(self):
        """Return the floating point value of the field"""
        return ROStringUtil.strToFloat(self.var.get())
    
    def getString(self):
        return self.var.get()
    
    def getVar(self):
        return self.var
    
    def restoreDefault(self):
        if self.defValue:
            # set default value and check it
            self.setFloat(self.defValue)
        else:
            # clear the field
            self.var.set("")

    def set(self, stringValue):
        """Set the field from a string; reject bad data.

        error conditions:
            raises ValueError if the string cannot be parsed
        """
        self.var.set(stringValue)

    def setFloat(self, floatValue):
        """Set the field based on a floating point value"""
        self.var.set(floatValue)
    
    def setRange(self, minValue, maxValue):
        self.minValue = minValue
        self.maxValue = maxValue

    def _checkVar(self, *args, **kargs):
        try:
            newVal = self.var.get()
            # blank is always acceptable
            if not newVal:
                return
            # check format (by convering to float) and that the new 
value is in range
            floatVal = self.getFloat()
            if floatVal < self.minValue or floatVal > self.maxValue:
                raise ValueError, "value %s out of range [%g,%g]" % \
                    (newVal, self.minValue, self.maxValue)
            self.oldVal = newVal
        except StandardError, e:
            print e
            self.var.set(self.oldVal)
            self.bell()

    def _focusOut(self, evt=None):
        """If blank, insert default value"""
        currVal = self.var.get()
        if not currVal:
            self.restoreDefault()



More information about the Python-list mailing list