Python 在 tkinter 中交互式验证 Entry 小部件内容
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4140437/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Interactively validating Entry widget content in tkinter
提问by Malcolm
What is the recommended technique for interactively validating content in a tkinter Entrywidget?
在 tkinterEntry小部件中交互式验证内容的推荐技术是什么?
I've read the posts about using validate=Trueand validatecommand=command, and it appears that these features are limited by the fact that they get cleared if the validatecommandcommand updates the Entrywidget's value.
我已经阅读了有关使用validate=Trueand的帖子validatecommand=command,似乎这些功能受到以下事实的限制:如果validatecommand命令更新Entry小部件的值,它们将被清除。
Given this behavior, should we bind on the KeyPress, Cut, and Pasteevents and monitor/update our Entrywidget's value through these events? (And other related events that I might have missed?)
鉴于这种行为,我们应该绑定的KeyPress,Cut以及Paste事件和监视/更新我们的Entry小部件的价值,通过这件事情?(以及我可能错过的其他相关事件?)
Or should we forget interactive validation altogether and only validate on FocusOutevents?
还是我们应该完全忘记交互式验证而只对FocusOut事件进行验证?
采纳答案by Bryan Oakley
The correct answer is, use the validatecommandattribute of the widget. Unfortunately this feature is severely under-documented in the Tkinter world, though it is quite sufficiently documented in the Tk world. Even though it's not documented well, it has everything you need to do validation without resorting to bindings or tracing variables, or modifying the widget from within the validation procedure.
正确答案是,使用validatecommand小部件的属性。不幸的是,这个特性在 Tkinter 世界中的记录严重不足,尽管它在 Tk 世界中有足够的记录。即使没有很好的文档记录,它也拥有您进行验证所需的一切,而无需借助绑定或跟踪变量,或在验证过程中修改小部件。
The trick is to know that you can have Tkinter pass in special values to your validate command. These values give you all the information you need to know to decide on whether the data is valid or not: the value prior to the edit, the value after the edit if the edit is valid, and several other bits of information. To use these, though, you need to do a little voodoo to get this information passed to your validate command.
诀窍是要知道您可以让 Tkinter 将特殊值传递给您的验证命令。这些值为您提供了决定数据是否有效所需的所有信息:编辑前的值、编辑后的值(如果编辑有效)以及其他一些信息。但是,要使用这些,您需要做一些伏都教来将此信息传递给您的验证命令。
Note: it's important that the validation command returns either Trueor False. Anything else will cause the validation to be turned off for the widget.
注意:验证命令返回True或很重要False。其他任何事情都会导致小部件的验证被关闭。
Here's an example that only allows lowercase (and prints all those funky values):
这是一个只允许小写的示例(并打印所有这些时髦的值):
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()
For more information about what happens under the hood when you call the registermethod, see Input validation tkinter
有关调用该register方法时发生的事情的更多信息,请参阅输入验证 tkinter
回答by Steven Rumbalski
Use a Tkinter.StringVarto track the value of the Entry widget. You can validate the value of the StringVarby setting a traceon it.
使用 aTkinter.StringVar跟踪 Entry 小部件的值。您可以通过在StringVar其trace上设置 a来验证 的值。
Here's a short working program that accepts only valid floats in the Entry widget.
这是一个简短的工作程序,它只接受 Entry 小部件中的有效浮点数。
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()
回答by user1683793
After studying and experimenting with Bryan's code, I produced a minimal version of input validation. The following code will put up an Entry box and only accept numeric digits.
在对 Bryan 的代码进行研究和试验后,我生成了输入验证的最小版本。下面的代码将放置一个输入框并且只接受数字。
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()
Perhaps I should add that I am still learning Python and I will gladly accept any and all comments/suggestions.
也许我应该补充一点,我仍在学习 Python,我很乐意接受任何和所有评论/建议。
回答by Noctis Skytower
While studying Bryan Oakley's answer, something told me that a far more general solution could be developed. The following example introduces a mode enumeration, a type dictionary, and a setup function for validation purposes. See line 48 for example usage and a demonstration of its simplicity.
在研究Bryan Oakley 的回答时,有人告诉我可以开发一个更通用的解决方案。以下示例引入了模式枚举、类型字典和用于验证目的的设置函数。有关示例用法及其简单性的演示,请参见第 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()
回答by orionrobert
Bryan's answer is correct, however no one mentioned the 'invalidcommand' attribute of the tkinter widget.
布莱恩的回答是正确的,但是没有人提到 tkinter 小部件的“无效命令”属性。
A good explanation is here: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
一个很好的解释在这里:http: //infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
Text copy/pasted in case of broken link
在链接断开的情况下复制/粘贴文本
The Entry widget also supports an invalidcommand option that specifies a callback function that is called whenever the validatecommand returns False. This command may modify the text in the widget by using the .set() method on the widget's associated textvariable. Setting up this option works the same as setting up the validatecommand. You must use the .register() method to wrap your Python function; this method returns the name of the wrapped function as a string. Then you will pass as the value of the invalidcommand option either that string, or as the first element of a tuple containing substitution codes.
Entry 小部件还支持一个 invalidcommand 选项,该选项指定每当 validatecommand 返回 False 时调用的回调函数。此命令可以通过使用小部件关联的文本变量上的 .set() 方法来修改小部件中的文本。设置此选项的工作方式与设置验证命令相同。您必须使用 .register() 方法来包装您的 Python 函数;此方法以字符串形式返回包装函数的名称。然后,您将作为 invalidcommand 选项的值传递该字符串,或者作为包含替换代码的元组的第一个元素。
Note: There is only one thing that I cannot figure out how to do: If you add validation to an entry, and the user selects a portion of the text and types a new value, there is no way to capture the original value and reset the entry. Here's an example
注意:只有一件事我不知道该怎么做:如果您向条目添加验证,并且用户选择文本的一部分并键入新值,则无法捕获原始值并重置入口。这是一个例子
- Entry is designed to only accept integers by implementing 'validatecommand'
- User enters 1234567
- User selects '345' and presses 'j'. This is registered as two actions: deletion of '345', and insertion of 'j'. Tkinter ignores the deletion and acts only on the insertion of 'j'. 'validatecommand' returns False, and the values passed to the 'invalidcommand' function are as follows: %d=1, %i=2, %P=12j67, %s=1267, %S=j
- If the code does not implement an 'invalidcommand' function, the 'validatecommand' function will reject the 'j' and the result will be 1267. If the code does implement an 'invalidcommand' function, there is no way to recover the original 1234567.
- 条目旨在通过实现“validatecommand”仅接受整数
- 用户输入 1234567
- 用户选择“345”并按“j”。这被注册为两个动作:删除“345”和插入“j”。Tkinter 忽略删除并仅对 'j' 的插入起作用。'validatecommand' 返回 False,传递给 'invalidcommand' 函数的值如下: %d=1, %i=2, %P=12j67, %s=1267, %S=j
- 如果代码没有实现'invalidcommand'函数,'validatecommand'函数会拒绝'j',结果为1267。如果代码没有实现'invalidcommand'函数,就没有办法恢复原来的1234567 .
回答by Mohammad Omar
import tkinter
tk=tkinter.Tk()
def only_numeric_input(e):
#this is allowing all numeric input
if e.isdigit():
return True
#this will allow backspace to work
elif e=="":
return True
else:
return False
#this will make the entry widget on root window
e1=tkinter.Entry(tk)
#arranging entry widget on screen
e1.grid(row=0,column=0)
c=tk.register(only_numeric_input)
e1.configure(validate="key",validatecommand=(c,'%P'))
tk.mainloop()
#very usefull for making app like calci
回答by Stendert
Responding to orionrobert's problemof dealing with simple validation upon substitutions of text through selection, instead of separate deletions or insertions:
响应orionrobert 的问题,即在通过选择替换文本时处理简单验证,而不是单独的删除或插入:
A substitution of selected text is processed as a deletion followed by an insertion. This may lead to problems, for example, when the deletion should move the cursor to the left, while a substitution should move the cursor to the right. Fortunately, these two processes are executed immediatelyafter one another. Hence, we can differentiate between a deletion by itself and a deletion directly followed by an insertion due to a substitution because the latter has does not change the idle flag between deletion and insertion.
所选文本的替换被处理为删除后插入。这可能会导致问题,例如,何时删除应将光标移至左侧,而替换应将光标移至右侧。幸运的是,这两个进程是立即依次执行的。因此,我们可以区分删除本身和由于替换直接插入的删除,因为后者没有改变删除和插入之间的空闲标志。
This is exploited using a substitutionFlag and a Widget.after_idle().
after_idle()executes the lambda-function at the end of the event queue:
这是使用substitutionFlag和a来利用的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
Of course, after a substitution, while validating the deletion part, one still won't know whether an insert will follow.
Luckily however, with:
.set(),
.icursor(),
.index(SEL_FIRST),
.index(SEL_LAST),
.index(INSERT),
we can achieve most desired behavior retrospectively (since the combination of our new substitutionFlag with an insertion is a new unique and final event.
当然,替换后,在验证删除部分时,仍然不知道是否会出现插入。然而幸运的是,使用:
.set(),
.icursor(),
.index(SEL_FIRST),
.index(SEL_LAST),
.index(INSERT),我们可以追溯实现最想要的行为(因为我们的新替换标志与插入的组合是一个新的独特和最终事件。
回答by Demian Wolf
Here is a simple way to validate the entry value, which allows user to enter digits only:
这是验证输入值的简单方法,它只允许用户输入数字:
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: This example can be very useful for creating an app like calc.
PS:这个例子对于创建像calc这样的应用程序非常有用。

