在Tkinter的Entry小部件中限制输入值

18

我需要限制 Entry 组件中的输入值仅为数字。我的实现方法是:

import numpy as np
from Tkinter import *;
import tkMessageBox;

class window2:

    def __init__(self,master1):

        self.panel2=Frame(master1)
        self.panel2.grid()

        self.button2=Button(self.panel2,text="Quit",command=self.panel2.quit)
        self.button2.grid()

        self.text1=Entry(self.panel2)
        self.text1.grid()
        self.text1.bind('<KeyPress>', self.keybind1)
        self.text1.focus()

    def keybind1 (self,event):
        if event.int in np.linspace(0,9,10):
            print event.int


root1=Tk()
window2(root1)
root1.mainloop()

我一直收到错误消息,指出Event实例没有'int'属性。我该怎么办?


1
我试图遵循http://stackoverflow.com/questions/3478890/how-to-insert-only-some-specified-characters-in-a-tkinter-entry-widget - sigma.z.1980
1
我在那个问题中看不到int的提及... - user395760
10个回答

30

我意识到这个回答来得相当晚,但我觉得我可以给出一个简单的回答...一旦你理解了它的工作原理,它就非常简单。

使用附带在Entry小部件中的验证功能。

假设self是一个小部件:

vcmd = (self.register(self.callback))

w = Entry(self, validate='all', validatecommand=(vcmd, '%P')) 
w.pack()

def callback(self, P):
    if str.isdigit(P) or P == "":
        return True
    else:
        return False

不需要包含所有的替换代码:('%d','%i','%P','%s','%S','%v','%V','%W'),只需要必要的部分。

Entry小部件返回一个字符串,因此您需要以某种方式提取任何数字,以将它们与其他字符分开。最简单的方法是使用str.isdigit()。这是内置在Python库中的一个方便的小工具,无需额外导入它,它将识别从Entry小部件返回的字符串中找到的任何数字(数字)。

if语句的or P == ""部分允许您删除整个输入内容。如果没有它,由于'%P'返回一个空值并导致回调返回False,您将无法删除最后一个(输入框中的第一个)数字。我不会在这里详细解释原因。

validate ='all'允许回调评估P的值,例如focusinfocusout或更改小部件中的任何key键时,因此您不会留下任何漏洞使杂散的字符被错误输入。

总之,为了简化事情。如果回调返回True,它将允许输入数据。如果回调返回“False”,它将基本上“忽略”键盘输入。

查看这两个参考文献。它们解释了每个替换代码的含义以及如何实现它们。

http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html http://stupidpythonideas.blogspot.ca/2013/12/tkinter-validation.html

编辑:这只处理文本框中允许的内容。但是,在回调内部,您可以将任何值P添加到任何您想要的变量中。


我更喜欢这个,代码行数较少,因此在我看来更易读,谢谢。 - Ritooon
1
请注意,在 Python 3.8 及以上版本中,P == '' 的检查实际上需要改为 str(P) == '' - KBriggs

29

使用validatecommand来限制在tk.Entry中的有效用户输入为可以解释为浮点数的字符串:

import tkinter as tk

class window2:
    def __init__(self, master1):
        self.panel2 = tk.Frame(master1)
        self.panel2.grid()
        self.button2 = tk.Button(self.panel2, text = "Quit", command = self.panel2.quit)
        self.button2.grid()
        vcmd = (master1.register(self.validate),
                '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
        self.text1 = tk.Entry(self.panel2, validate = 'key', validatecommand = vcmd)
        self.text1.grid()
        self.text1.focus()

    def validate(self, action, index, value_if_allowed,
                       prior_value, text, validation_type, trigger_type, widget_name):
        if value_if_allowed:
            try:
                float(value_if_allowed)
                return True
            except ValueError:
                return False
        else:
            return False

root1 = tk.Tk()
window2(root1)
root1.mainloop()

参考资料:

  • Tk man页面 解释了validatevalidatecommand选项。(感谢schlenk提供的链接)。
  • 我学习如何在Python中实现这一点在这里

Tk文档中关于验证的部分可能会有所帮助:http://www.tcl.tk/man/tcl8.5/TkCmd/entry.htm#M-validate - schlenk
感谢您提供的链接,schlenk。 - unutbu
1
如何处理删除输入字段中所有字符的情况?我尝试使用 if action == 0: return True,但它没有起作用。然而,在 except ValueError: 后检查 if value_if_allowed == '': return True,它确实允许我删除字段内容。奇怪。 - alkamid
为什么在这里使用退格键无法删除输入的所有数据。即使按一次退格键,一个值仍然存在。! - master_yoda
1
@sunny_old_days:感谢您的错误报告。当value_if_allowed为空字符串时,float(value_if_allowed)会引发ValueError。我已更改代码以避免此ValueError,从而允许空字符串。 - unutbu

11
答案几乎完美,只需稍作修改即可允许删除整个字符串。检查浮点数应该仅在插入文本时进行。
def validate_float(self, action, index, value_if_allowed,
    prior_value, text, validation_type, trigger_type, widget_name):
    # action=1 -> insert
    if(action=='1'):
        if text in '0123456789.-+':
            try:
                float(value_if_allowed)
                return True
            except ValueError:
                return False
        else:
            return False
    else:
        return True

感谢您对unutbu答案的修复!他的答案中留下了您提到的那个落单字符。 - nimig18

3

我对Python和Tkinker不太熟悉,但这样做对我来说效果最好:

    def keybind1 (self,event):
        v = event.char
        try:
            v = int(v)
        except ValueError:
            if v!="\x08" and v!="":
                return "break"
v = int(v) 触发了一个 ValueError 异常,除了数字键之外的任何键都会触发。但是,if v!="\x08 and v!="":" 语句仍然允许退格键 ("\x08") 和删除、箭头、home、end 等键(它们的 event.char"")正常工作 - 否则,break 命令将停止向 Entry 小部件中输入其他字符。

这是最好的解决方案。谢谢! - Jakub Bláha

1

我也不得不处理一个初始插入的情况。这是我最终得出的结果:

    def _checkNumberOnly(self, action, value_if_allowed):
        if action != '1':
           return True
        try:
            return value_if_allowed.isnumeric()
        except ValueError:
           return False

    vcmd = (self.register(self._checkNumberOnly), '%d', '%P')
        self.port = ttk.Entry(self, width=35, validate='key', validatecommand=vcmd)

因此,它适用于以下内容:
    self.port.insert(0, '6379')

我不确定是否需要使用catch,因为isnumeric()没有说明会引发异常。

0
def validate(entry_text):
    chars = '1234567890'
    if any((c not in chars) for c in pin.get()):
        lpin = int(len(pin.get()))-1 
        pin.set(pin.get()[:lpin])

pin.trace("w", lambda *args: validate(pin))

0

这里是一个完整且简化的工作代码,回答了OP的问题,其中Entry小部件仅接受整数,同时允许完全退格以进行重新输入。这是对Ernie Peters和unutbu的答案以及类似问题在Interactively validating Entry widget content in tkinter中解决方案的变体。

在enter_only_digits()中,从vcmd语句提供的参数值为%P,即当允许编辑时entry的值,以及%d,即所需的“插入”操作类型1。

import tkinter as tk

class window2:
    def __init__(self, master1):
        self.panel2 = tk.Frame(master1)

        self.button2 = tk.Button(self.panel2, text="Quit", command=self.panel2.quit)

        vcmd = (master1.register(self.enter_only_digits), '%P', '%d')
        self.text1 = tk.Entry(self.panel2, validate='key', validatecommand=vcmd)

        self.panel2.grid()
        self.button2.grid()
        self.text1.grid()
        self.text1.focus()


    def enter_only_digits(self, entry, action_type) -> bool:
        if action_type == '1' and not entry.isdigit():
            return False

        return True
    

root1 = tk.Tk()
window2(root1)
root1.mainloop()

0
这补充了capitalaslash的答案,但允许第一个字符为“-”。
import tkinter as tk

class window2:
    def __init__(self, master1):
        self.panel2 = tk.Frame(master1)
        self.panel2.grid()
        self.button2 = tk.Button(self.panel2, text = "Quit", command = self.panel2.quit)
        self.button2.grid()
        vcmd = (master1.register(self.validate),
                '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
        self.text1 = tk.Entry(self.panel2, validate = 'key', validatecommand = vcmd)
        self.text1.grid()
        self.text1.focus()

    def validate(self, action, index, value_if_allowed,
                       prior_value, text, validation_type, trigger_type, widget_name):
        # action=1 -> insert
        print(index)
        if action == '1':
            if text in '0123456789-.':

                try:
                    if text == '-' and index == '0':
                        print('returning')
                        return True
                    float(value_if_allowed)
                    return True
                except ValueError:
                    return False
            else:
                return False
        else:
            return True

root1 = tk.Tk()
window2(root1)
root1.mainloop()

0

""" 以下代码将限制ttk.Entry小部件仅接收'type'为“str”的值。 """

    import tkinter as tk
    from tkinter import ttk


    def is_type_int(*args):
      item = var.get()
      try:
        item_type = type(int(item))
        if item_type == type(int(1)):
          print(item)
          print(item_type)
      except:
        ent.delete(0, tk.END)


    root = tk.Tk()
    root.geometry("300x300")

    var = tk.StringVar()

    ent = ttk.Entry(root, textvariable=var)
    ent.pack(pady=20)

    var.trace("w", is_type_int)

    root.mainloop()

0

如果你正在处理使用逗号作为小数点的本地化:

locale.setlocale(locale.LC_ALL,'de_DE.UTF-8') # German

vcmd = (self.root.register(self.entry_numericonly), '%d', '%P')

self.my_float_entry = tk.Entry(self.root, ... , validate='key', validatecommand=vcmd)

def entry_numericonly(self, action, value_if_allowed):
    if(action == "1"):
        try:
            loc_float  = locale.atof(value_if_allowed)
            loc_float_format = locale.format("%f", loc_float)
            try:
                loc_same_length = loc_float_format[:len(value_if_allowed)]
                return value_if_allowed == loc_same_length
            except:
                return False                    
        except:
            return False
    else:
        return True

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接