# File: entryvalidation.py # References: # http://stackoverflow.com/questions/4140437/python-tkinter-interactively-validating-entry-widget-content # http://infohost.nmt.edu/tcc/help/pubs/tkinter//entry.html # http://infohost.nmt.edu/tcc/help/pubs/tkinter//events.html # http://www.tcl.tk/man/tcl8.5/TkCmd/ttk_entry.htm # # valid percent substitutions (see first link above) # %d = Type of action (1=insert, 0=delete, -1 for others) # %i = index of char string to be inserted/deleted, or -1 # %P = value of the entry if the edit is allowed # %s = value of entry prior to editing # %S = the text string being inserted or deleted, if any # %v = the type of validation that is currently set # %V = the type of validation that triggered the callback # (key, focusin, focusout, forced) # %W = the tk name of the widget from tkinter import * from tkinter import ttk from demopanels import MsgPanel, SeeDismissPanel class EntryValidationDemo(ttk.Frame): def __init__(self, isapp=True, name='entryvalidationdemo'): ttk.Frame.__init__(self, name=name) self.pack(expand=Y, fill=BOTH) self.master.title('Entry Validation Demo') self.isapp = isapp self._create_widgets() def _create_widgets(self): if self.isapp: MsgPanel(self, ["Four different entries are displayed below. ", "You can add characters by pointing, ", "clicking and typing, though each is constrained ", "in what it will accept.\n\n", " The first only accepts 32-bit integers or an empty ", " string (validation on leaving the field).\n", " The second only accepts strings with fewer than ten ", "characters and sounds the bell when an attempt to go ", "over the limit is made (checks each keystroke).\n", " The third accepts 10 digit phone numbers and formats", "them during entry. Only digits are accepted as input. ", "Left and Right arrow keys skip over format characters. ", "A 'backspace' is handled as a left arrow. A bell sounds ", "if illegal characters are attempted.\n", " The fourth is a password ", "field that accepts up to eight characters (silently ", "ignoring further ones), and displaying them as ", "asterisk characters."]) SeeDismissPanel(self) self._create_demo_panel() def _create_demo_panel(self): demoPanel = ttk.Frame(self) demoPanel.pack(side=TOP) # create entry panels integer = self._create_int_panel() constraint = self._create_constrained_panel() phone = self._create_phone_panel() pwd = self._create_pwd_panel() # position panels integer.grid(in_=demoPanel, row=0, column=0, padx='3m', pady='1m', sticky=EW) constraint.grid(in_=demoPanel, row=0, column=1, padx='3m', pady='1m', sticky=EW) phone.grid(in_=demoPanel, row=1, column=0, padx='3m', pady='1m', sticky=EW) pwd.grid(in_=demoPanel, row=1, column=1, padx='3m', pady='1m', sticky=EW) # configure resize constraints demoPanel.columnconfigure((0,1), uniform=True) # ===================================================================== # Integer Entry # ===================================================================== def _create_int_panel(self): # the entry is validated when focus is lost # if it does not contain a valid integer or # an empty string, it is cleared, given back # the focus and a beep is heard lf = ttk.Labelframe(text='Integer Entry') e = ttk.Entry(lf, validate='focusout') # register the validation/invalid methods # %W - entry widget name # %P - entry string e['validatecommand'] = (self.register(self._is_valid_int), '%P') e['invalidcommand'] = (self.register(self._invalid_int), '%W') e.pack(fill=X, expand=Y, padx='1m', pady='1m') e.focus_set() return lf def _is_valid_int(self, txt): # txt - value in %P if not txt: # accept empty string return True try: int(txt) return True # accept integer except ValueError: # not an integer return False def _invalid_int(self, widgetName): # called automatically when the # validation command returns 'False' # get entry widget widget = self.nametowidget(widgetName) # clear entry widget.delete(0, END) # return focus to integer entry widget.focus_set() widget.bell() # ===================================================================== # Constrained Entry # ===================================================================== def _create_constrained_panel(self): # the length of the entry is constrained to 10 chars lf = ttk.Labelframe(text='Constrained Entry') var = StringVar() e = ttk.Entry(lf, validate='key', textvariable= var, invalidcommand='bell', validatecommand = lambda var=var: len(var.get()) < 10) e.pack(fill=X, expand=Y, padx='1m', pady='1m') return lf # ===================================================================== # Phone Number Entry # ===================================================================== def _create_phone_panel(self): # phone number entry field # accepts a 10 digit phone number # and formats it as it's entered lf = ttk.Labelframe(text='Phone Entry') e = ttk.Entry(lf) # capture edits 'before' the entry # text is updated e.bind('<KeyPress>', self._format_phonenum) e.pack(fill=X, expand=Y, padx='1m', pady='1m') return lf def _format_phonenum(self, event): # formats the entry text as it's being # entered; format is '1-(ddd)-ddd-dddd' if event.keysym == 'Tab': # allow tab to next field return widget = event.widget # get the entry widget entry = widget.get() # get the text content idx = widget.index(INSERT) # current cursor position if event.keysym == 'Left': # skip format chars if idx == 3: return 'break' # block edit elif idx == 8: widget.icursor(6) elif idx == 12: widget.icursor(11) elif event.keysym == 'Right': # skip format chars if idx == 5: widget.icursor(7) elif idx == 10: widget.icursor(11) elif event.keysym == 'BackSpace': # convert a backspace to a 'Left' event widget.event_generate('<Left>') return 'break' else: # block if char not a digit # or phone number will be to long if not event.char.isdigit() or idx > 15: widget.bell() return 'break' # block edit if idx == widget.index(END): # allow adding a new digit, # inserting format characters # where necessary if idx == 0: # insert format chars widget.insert(idx, '1-(' + event.char) return 'break' if idx == 6: # insert format chars widget.insert(idx,')-' + event.char) return 'break' if idx == 7 or idx == 11: # insert format char widget.insert(idx,'-' + event.char) return 'break' else: # replacing a digit if idx in [0,1,2,6,7,11]: # disallow if overwriting # format chars widget.bell() return 'break' else: # ok to replace widget.delete(idx) # ===================================================================== # Password Entry # ===================================================================== def _create_pwd_panel(self): # masks entered characters with an asterisk # constrains the length of the entry to 8 chars lf = ttk.Labelframe(text='Password Entry') var = StringVar() e = ttk.Entry(lf, validate='key', show='*', textvariable = var, validatecommand = lambda v=var: len(v.get()) < 8) e.pack(fill=X, expand=Y, padx='1m', pady='1m') return lf if __name__ == '__main__': EntryValidationDemo().mainloop()
Thursday, August 2, 2012
Tkinter Entry with Validation Demo
This code is based on the Tcl entry3.tcl demo. It includes four example entry fields that use valid, validatecommand, and invalidcommand and/or bind to capture and check user input.
Labels:
Tkinter Demos,
Tkinter Entry