在 tkinter 中以交互方式验证 Entry 小部件内容

2024-11-25 08:50:00
admin
原创
159
摘要:问题描述:用于以交互方式验证 tkinter 小部件中的内容的推荐技术是什么Entry?validate=True我已经阅读了有关使用和的帖子validatecommand=command,并且看起来这些功能受到这样的事实限制:如果命令validatecommand更新Entry小部件的值,它们就会被清除。鉴...

问题描述:

用于以交互方式验证 tkinter 小部件中的内容的推荐技术是什么Entry

validate=True我已经阅读了有关使用和的帖子validatecommand=command,并且看起来这些功能受到这样的事实限制:如果命令validatecommand更新Entry小部件的值,它们就会被清除。

鉴于这种行为,我们是否应该绑定KeyPressCutPaste事件并通过这些事件监视/更新Entry小部件的值?(以及我可能错过的其他相关事件?)

或者我们应该完全忘记交互式验证而只对FocusOut事件进行验证?


解决方案 1:

正确的答案是使用validatecommand小部件的属性。不幸的是,此功能在 Tkinter 世界中严重缺乏文档记录,但在 Tk 世界中却有相当充分的文档记录。尽管文档记录不完善,但它拥有进行验证所需的一切,而无需借助绑定或跟踪变量,也无需在验证过程中修改小部件。

诀窍在于,您可以让 Tkinter 将特殊值传递给您的验证命令。这些值为您提供了判断数据是否有效所需的所有信息:编辑前的值、编辑后的值(如果编辑有效)以及其他一些信息。不过,要使用这些信息,您需要做一些小技巧才能将这些信息传递给您的验证命令。

注意:验证命令必须返回TrueFalse。任何其他内容都将导致小部件的验证被关闭。

下面是一个仅允许小写的示例。它还打印了所有特殊值以供说明。它们不是全部必需的;您很少需要超过一两个。

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:
")
        self.text.insert("end","d='%s'
" % d)
        self.text.insert("end","i='%s'
" % i)
        self.text.insert("end","P='%s'
" % P)
        self.text.insert("end","s='%s'
" % s)
        self.text.insert("end","S='%s'
" % S)
        self.text.insert("end","v='%s'
" % v)
        self.text.insert("end","V='%s'
" % V)
        self.text.insert("end","W='%s'
" % 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方法时发生的情况的更多信息,请参阅为什么 tkinter 输入验证需要调用 register()?

有关规范文档,请参阅Tcl/Tk Entry 手册页的验证部分

解决方案 2:

在研究和试验 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()

也许我应该补充一点,我仍在学习 Python,我很乐意接受任何和所有的评论/建议。

解决方案 3:

使用Tkinter.StringVar来跟踪小部件的值。您可以通过在其上设置Entry来验证 的值。StringVar`trace`

这是一个简短的工作程序,它只接受Entry小部件中的有效浮点数。

try:
    from tkinter import *
except ImportError:
    from Tkinter import *  # Python 2


root = Tk()
sv = StringVar()

def validate_float(var):
    new_value = var.get()
    try:
        new_value == '' or float(new_value)
        validate_float.old_value = new_value
    except:
        var.set(validate_float.old_value)

validate_float.old_value = ''  # Define function attribute.

# 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()
ent.focus_set()

root.mainloop()

解决方案 4:

布莱恩的回答是正确的,但是没有人提到 tkinter 小部件的“invalidcommand”属性。

这里有一个很好的解释:
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html

链接失效时复制/粘贴文本

Entry 小部件还支持 invalidcommand 选项,该选项指定在validatecommand 返回 False 时调用的回调函数。此命令可以通过使用小部件关联的文本变量上的 .set() 方法修改小部件中的文本。设置此选项的工作方式与设置validatecommand 相同。您必须使用 .register() 方法来包装您的 Python 函数;此方法将包装函数的名称作为字符串返回。然后,您将传递该字符串作为 invalidcommand 选项的值,或者作为包含替换代码的元组的第一个元素。

注意:只有一件事我无法弄清楚该怎么做:如果您向条目添加验证,并且用户选择部分文本并输入新值,则无法捕获原始值并重置条目。以下是一个例子

  1. 通过实施“validatecommand”,Entry 被设计为仅接受整数

  2. 用户输入 1234567

  3. 用户选择 '345' 并按下 'j'。这被注册为两个操作:删除 '345' 和插入 'j'。Tkinter 忽略删除,只对插入 'j' 起作用。'validatecommand' 返回 False,传递给 'invalidcommand' 函数的值如下:%d=1、%i=2、%P=12j67、%s=1267、%S=j

  4. 如果代码没有实现“invalidcommand”函数,那么“validatecommand”函数将拒绝“j”,结果为 1267。如果代码实现了“invalidcommand”函数,则无法恢复原始的 1234567。

解决方案 5:

定义一个返回布尔值的函数,该布尔值指示输入是否有效。
将其注册为 Tcl 回调,并将回调名称作为 传递给小部件validatecommand

例如:

import tkinter as tk


def validator(P):
    """Validates the input.

    Args:
        P (int): the value the text would have after the change.

    Returns:
        bool: True if the input is digit-only or empty, and False otherwise.
    """

    return P.isdigit() or P == ""


root = tk.Tk()

entry = tk.Entry(root)
entry.configure(
    validate="key",
    validatecommand=(
        root.register(validator),
        "%P",
    ),
)
entry.grid()

root.mainloop()

参考。

解决方案 6:

在研究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}
i = {!r}
P = {!r}
s = {!r}
'
                              'S = {!r}
v = {!r}
V = {!r}
W = {!r}'
                         .format(d, i, P, s, S, v, V, W))
        self.text['state'] = DISABLED
        return not S.isupper()


if __name__ == '__main__':
    Example.main()

解决方案 7:

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

解决方案 8:

如果您想设置数字和最大字符数,此代码可以提供帮助。

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()

解决方案 9:

这是@Steven Rumbalski 的答案的改进版本,Entry通过跟踪对 a 的更改来验证小部件的值StringVar- 我已经通过就地编辑对其进行了调试和一定程度的改进。

下面的版本将所有内容放入StringVar 子类中,以更好地封装正在发生的事情,更重要的是允许它的多个独立实例同时存在而不会互相干扰 - 他的实现存在潜在问题,因为它利用函数属性而不是实例属性,这本质上与全局变量相同,并且可能在这种情况下导致问题。

try:
    from tkinter import *
except ImportError:
    from Tkinter import *  # Python 2


class ValidateFloatVar(StringVar):
    """StringVar subclass that only allows valid float values to be put in it."""

    def __init__(self, master=None, value=None, name=None):
        StringVar.__init__(self, master, value, name)
        self._old_value = self.get()
        self.trace('w', self._validate)

    def _validate(self, *_):
        new_value = self.get()
        try:
            new_value == '' or float(new_value)
            self._old_value = new_value
        except ValueError:
            StringVar.set(self, self._old_value)


root = Tk()
ent = Entry(root, textvariable=ValidateFloatVar(value=42.0))
ent.pack()
ent.focus_set()
ent.icursor(END)

root.mainloop()

解决方案 10:

回应orionrobert 的问题,即通过选择来处理文本替换的简单验证,而不是单独的删除或插入:

所选文本的替换被处理为删除后插入。这可能会导致问题,例如,当删除应该将光标移到左侧,而替换应该将光标移到右侧时。幸运的是,这两个过程是紧接着执行的。因此,我们可以区分删除本身和替换后直接插入的删除,因为后者不会改变删除和插入之间的空闲标志。

这是通过使用 replacementFlag 和 来利用的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)

解决方案 11:

entry_text1 = StringVar()
XYZ=Entry( root, width=10,textvariable=entry_text1 ,  
font=font3)
XYZ.grid(column=1,row=1,padx=10)

try:
    XYZ=float(entry_text1.get())
    return True
except ValueError:        
    XYZ.delete(0, END)       
    XYZ.insert(0,0)
    tkinter.messagebox.showinfo("not pass",  "please enter only 
    number with '.' or only number") 
    return False
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1579  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1355  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   8  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   9  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用