交互式验证tkinter Entry
小部件中的内容的推荐技术是什么?
我已阅读有关使用validate=True
和validatecommand=command
的帖子,看来这些功能受限于validatecommand
命令更新{Entry
时它们会被清除的事实1}}小部件的价值。
鉴于此行为,我们是否应该绑定KeyPress
,Cut
和Paste
事件并通过这些事件监视/更新我们的Entry
窗口小部件的值? (以及我可能错过的其他相关事件?)
或者我们是否应该完全忘记交互式验证并仅在FocusOut
事件上验证?
答案 0 :(得分:158)
正确答案是,使用窗口小部件的validatecommand
属性。不幸的是,这个特征在Tkinter世界中严重缺乏记录,尽管在Tk世界中有足够的记录。尽管它没有很好地记录,但它具有进行验证所需的一切,无需借助绑定或跟踪变量,或在验证过程中修改小部件。
诀窍是要知道你可以让Tkinter将特殊值传递给validate命令。这些值为您提供了确定数据是否有效所需的所有信息:编辑前的值,编辑后编辑后的值以及其他几个信息。但是,要使用这些,您需要做一些小巫术来将此信息传递给您的验证命令。
注意:验证命令返回True
或False
非常重要。其他任何内容都会导致小部件的验证被关闭。
这是一个只允许小写(并打印所有那些时髦的值)的例子:
import tkinter as tk # python 3.x
# import Tkinter as tk # python 2.x
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# valid percent substitutions (from the Tk entry man page)
# note: you only have to register the ones you need; this
# example registers them all for illustrative purposes
#
# %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
vcmd = (self.register(self.onValidate),
'%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
self.entry = tk.Entry(self, validate="key", validatecommand=vcmd)
self.text = tk.Text(self, height=10, width=40)
self.entry.pack(side="top", fill="x")
self.text.pack(side="bottom", fill="both", expand=True)
def onValidate(self, d, i, P, s, S, v, V, W):
self.text.delete("1.0", "end")
self.text.insert("end","OnValidate:\n")
self.text.insert("end","d='%s'\n" % d)
self.text.insert("end","i='%s'\n" % i)
self.text.insert("end","P='%s'\n" % P)
self.text.insert("end","s='%s'\n" % s)
self.text.insert("end","S='%s'\n" % S)
self.text.insert("end","v='%s'\n" % v)
self.text.insert("end","V='%s'\n" % V)
self.text.insert("end","W='%s'\n" % W)
# Disallow anything but lowercase letters
if S == S.lower():
return True
else:
self.bell()
return False
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
有关在致电register
方法时幕后发生的事情的详情,请参阅Input validation tkinter
答案 1 :(得分:8)
使用Tkinter.StringVar
跟踪Entry小部件的值。您可以通过在StringVar
上设置trace
来验证from Tkinter import *
root = Tk()
sv = StringVar()
def validate_float(var):
new_value = var.get()
try:
new_value == '' or float(new_value)
validate.old_value = new_value
except:
var.set(validate.old_value)
validate.old_value = ''
# trace wants a callback with nearly useless parameters, fixing with lambda.
sv.trace('w', lambda nm, idx, mode, var=sv: validate_float(var))
ent = Entry(root, textvariable=sv)
ent.pack()
root.mainloop()
的值。
这是一个简短的工作程序,只接受Entry小部件中的有效浮点数。
{{1}}
答案 2 :(得分:6)
在研究和试验Bryan的代码之后,我制作了一个最小版本的输入验证。以下代码将显示一个Entry框,只接受数字。
from tkinter import *
root = Tk()
def testVal(inStr,acttyp):
if acttyp == '1': #insert
if not inStr.isdigit():
return False
return True
entry = Entry(root, validate="key")
entry['validatecommand'] = (entry.register(testVal),'%P','%d')
entry.pack()
root.mainloop()
也许我应该补充一点,我还在学习Python,我很乐意接受任何和所有意见/建议。
答案 3 :(得分:2)
在研究Bryan Oakley's answer时,有人告诉我,可以开发出更为通用的解决方案。以下示例介绍了模式枚举,类型字典和用于验证目的的设置函数。有关示例用法和简单性的演示,请参见第48行。
#! /usr/bin/env python3
# https://stackoverflow.com/questions/4140437
import enum
import inspect
import tkinter
from tkinter.constants import *
Mode = enum.Enum('Mode', 'none key focus focusin focusout all')
CAST = dict(d=int, i=int, P=str, s=str, S=str,
v=Mode.__getitem__, V=Mode.__getitem__, W=str)
def on_validate(widget, mode, validator):
# http://www.tcl.tk/man/tcl/TkCmd/ttk_entry.htm#M39
if mode not in Mode:
raise ValueError('mode not recognized')
parameters = inspect.signature(validator).parameters
if not set(parameters).issubset(CAST):
raise ValueError('validator arguments not recognized')
casts = tuple(map(CAST.__getitem__, parameters))
widget.configure(validate=mode.name, validatecommand=[widget.register(
lambda *args: bool(validator(*(cast(arg) for cast, arg in zip(
casts, args)))))]+['%' + parameter for parameter in parameters])
class Example(tkinter.Frame):
@classmethod
def main(cls):
tkinter.NoDefaultRoot()
root = tkinter.Tk()
root.title('Validation Example')
cls(root).grid(sticky=NSEW)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
def __init__(self, master, **kw):
super().__init__(master, **kw)
self.entry = tkinter.Entry(self)
self.text = tkinter.Text(self, height=15, width=50,
wrap=WORD, state=DISABLED)
self.entry.grid(row=0, column=0, sticky=NSEW)
self.text.grid(row=1, column=0, sticky=NSEW)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
on_validate(self.entry, Mode.key, self.validator)
def validator(self, d, i, P, s, S, v, V, W):
self.text['state'] = NORMAL
self.text.delete(1.0, END)
self.text.insert(END, 'd = {!r}\ni = {!r}\nP = {!r}\ns = {!r}\n'
'S = {!r}\nv = {!r}\nV = {!r}\nW = {!r}'
.format(d, i, P, s, S, v, V, W))
self.text['state'] = DISABLED
return not S.isupper()
if __name__ == '__main__':
Example.main()
答案 4 :(得分:2)
Bryan的答案是正确的,但是没有人提到tkinter小部件的'invalidcommand'属性。
一个很好的解释在这里: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
在链接断开的情况下复制/粘贴文本
Entry小部件还支持invalidcommand选项,该选项指定当validatecommand返回False时将调用的回调函数。此命令可以通过使用小部件的关联text变量上的.set()方法来修改小部件中的文本。设置此选项的作用与设置validate命令的作用相同。您必须使用.register()方法来包装Python函数;此方法以字符串形式返回包装函数的名称。然后,您将传递该字符串作为invalidcommand选项的值,或者作为包含替换代码的元组的第一个元素。
注意: 我只有一件事我不知道该怎么做:如果您向条目添加验证,并且用户选择了文本的一部分并键入了新值,则无法捕获原始值并重置条目。这是一个例子
答案 5 :(得分:1)
如果您只想设置数字和最大字符数,此代码会有所帮助。
from tkinter import *
root = Tk()
def validate(P):
if len(P) == 0 or len(P) <= 10 and P.isdigit(): # 10 characters
return True
else:
return False
ent = Entry(root, validate="key", validatecommand=(root.register(validate), '%P'))
ent.pack()
root.mainloop()
答案 6 :(得分:0)
这是一种验证输入值的简单方法,该方法允许用户仅输入数字:
import tkinter # imports Tkinter module
root = tkinter.Tk() # creates a root window to place an entry with validation there
def only_numeric_input(P):
# checks if entry's value is an integer or empty and returns an appropriate boolean
if P.isdigit() or P == "":
return True
return False
my_entry = tkinter.Entry(root) # creates an entry
my_entry.grid(row=0, column=0) # shows it in the root window using grid geometry manager
callback = root.register(only_numeric_input) # registers a Tcl to Python callback
my_entry.configure(validate="key", validatecommand=(callback, "%P")) # enables validation
root.mainloop() # enters to Tkinter main event loop
PS:此示例对于创建诸如calc之类的应用程序非常有用。
答案 7 :(得分:0)
响应orionrobert's problem,即通过选择替换文本而不是单独删除或插入文本来进行简单验证:
对所选文本的替换被处理为删除,然后插入。这可能会导致问题,例如,当删除操作将光标向左移动,而替换操作将光标向右移动时。幸运的是,这两个过程彼此立即执行。 因此,我们可以区分删除本身与直接由于删除而导致插入的删除,因为后者在删除和插入之间没有空闲时间。
这是通过使用replaceFlag和Widget.after_idle()
来利用的。
after_idle()
在事件队列的末尾执行lambda函数:
class ValidatedEntry(Entry):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tclValidate = (self.register(self.validate), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
# attach the registered validation function to this spinbox
self.config(validate = "all", validatecommand = self.tclValidate)
def validate(self, type, index, result, prior, indelText, currentValidationMode, reason, widgetName):
if typeOfAction == "0":
# set a flag that can be checked by the insertion validation for being part of the substitution
self.substitutionFlag = True
# store desired data
self.priorBeforeDeletion = prior
self.indexBeforeDeletion = index
# reset the flag after idle
self.after_idle(lambda: setattr(self, "substitutionFlag", False))
# normal deletion validation
pass
elif typeOfAction == "1":
# if this is a substitution, everything is shifted left by a deletion, so undo this by using the previous prior
if self.substitutionFlag:
# restore desired data to what it was during validation of the deletion
prior = self.priorBeforeDeletion
index = self.indexBeforeDeletion
# optional (often not required) additional behavior upon substitution
pass
else:
# normal insertion validation
pass
return True
当然,替换后,在验证删除部分的同时,仍然不知道是否会插入。
幸运的是,具有:
.set()
,
.icursor()
,
.index(SEL_FIRST)
,
.index(SEL_LAST)
,
.index(INSERT)
,
我们可以回顾性地实现最理想的行为(因为新的replaceFlag与插入的组合是一个新的唯一且最终的事件。
答案 8 :(得分:0)
这是一种验证输入值的简单方法,它允许用户仅输入数字:
import tkinter # imports Tkinter module
root = tkinter.Tk() # creates a root window to place an entry with validation there
def only_numeric_input(P):
# checks if entry's value is an integer or empty and returns an appropriate boolean
if P.isdigit() or P == "": # if a digit was entered or nothing was entered
return True
return False
my_entry = tkinter.Entry(root) # creates an entry
my_entry.grid(row=0, column=0) # shows it in the root window using grid geometry manager
callback = root.register(only_numeric_input) # registers a Tcl to Python callback
my_entry.configure(validate="key", validatecommand=(callback, "%P")) # enables validation
root.mainloop() # enters to Tkinter main event loop
PS:此示例对于创建诸如calc之类的应用程序非常有用。