【问题标题】:Tkinter Spinbox range validationTkinter Spinbox 范围验证
【发布时间】:2015-09-18 10:53:35
【问题描述】:

我正在尝试利用 Bryan Oakley 这个出色的答案,但无济于事 (https://stackoverflow.com/a/4140988/5060127)...

我想使用相同的方法来验证 Spinbox 值。我已经为 spinboxes 定义了 from_ 和 to 值,但是用户仍然可以在其中输入大多数任何内容......应该验证用户只能输入 from_-to 范围内的值,并且只能输入整数。

这是显示我已经走了多远的代码......

try:
    from Tkinter import *
except ImportError:
    from tkinter import *

class GUI:
    def __init__(self):
        # root window of the whole program
        self.root = Tk()
        self.root.title('ImageSound')

        # registering validation command
        vldt_ifnum_cmd = (self.root.register(self.ValidateIfNum),'%s', '%S')

        # creating a spinbox
        harm_count = Spinbox(self.root, from_=1, to=128, width=5, justify='right', validate='all', validatecommand=vldt_ifnum_cmd)
        harm_count.delete(0,'end')
        harm_count.insert(0,8)
        harm_count.pack(padx=10, pady=10)

    def ValidateIfNum(self, s, S):
        # disallow anything but numbers
        valid = S.isdigit()
        if not valid:
            self.root.bell()
        return valid

if __name__ == '__main__':
    mainwindow = GUI()
    mainloop()

【问题讨论】:

  • 但是您的 ValidateIfNum 方法应用该范围验证。
  • 问题是Spinbox小部件没有提供validatevalidatecommand参数。它们被接受,但(我的猜测是)它们被忽略了。
  • 但我设置了 validatecommand 正是 Bryan 的布局方式 - 使用 .register... 查看链接。在我继续进行范围验证之前,我想弄清楚为什么 .isdigit 没有做它应该做的事情......当我将 valid = (S.lower() == S)valid = S.isdigit() 交换时,Bryan 的例子确实有效......所以为什么在这里不行吗?

标签: python validation input tkinter


【解决方案1】:

我想我找到了问题所在。验证器函数最初使用S='' 调用,您的条件S.isdigit() 返回False,并且不再调用函数。但是在我将条件更新为valid = S == '' or S.isdigit() 后,它开始按预期工作。

当然,您可能需要一些更复杂的条件(例如检查值是否在范围内),但看起来空字符串必须通过(至少是初始)验证。

【讨论】:

  • 哇,就是这么简单!谢谢!是的,你是对的,isdigit 在一个空字符串上返回 False,这似乎就是它的绊脚石。现在可以了。谢谢!让我们看看我是否能够以某种方式进行范围验证...如果输入了超出范围的数字,它应该恢复为最后一个有效数字。
  • 很高兴能帮上忙,请考虑将此标记为已解决。范围检查可能更棘手,因为在验证器中,您只能获得字段的旧值,而新值可能取决于插入符号的位置、选定的文本等。
  • 嗯,%s 替换包括整个文本条目,所以我很确定我将不得不在比较中以某种方式使用它,以及 .delete/.insert 来覆盖超出范围的用户输入...如果我再次卡在某个地方,我将编辑我的原始帖子,所以如果可以的话,请密切关注这个:) 同时将您的答案标记为已解决。它真的有帮助 - 我昨天整天都在试图解决这个问题(我对 Python 和 tkinter 很陌生)!
  • 是的,%S 可以是空字符串。引用:“正在插入/删除的文本字符串,如果有的话。否则它是一个空字符串。”来源:wiki.tcl-lang.org/page/tkinter.Spinbox
【解决方案2】:

我做到了!仅整数输入和将小部件的 from_ 和 to 值考虑在内的范围检查都有效!它可能看起来有点hacky,但它正在工作!这是任何有兴趣的人的代码:

try:
    from Tkinter import *
except ImportError:
    from tkinter import *

class GUI:
    def __init__(self):
        # root window of the whole program
        self.root = Tk()
        self.root.title('ImageSound')

        # registering validation command
        vldt_ifnum_cmd = (self.root.register(self.ValidateIfNum),'%P', '%S', '%W')

        # creating a spinbox
        harm_count = Spinbox(self.root, from_=1, to=128, width=5, justify='right', validate='all', validatecommand=vldt_ifnum_cmd)
        harm_count.insert(0,8)
        harm_count.delete(1,'end')
        harm_count.pack(padx=10, pady=10)

    def ValidateIfNum(self, user_input, new_value, widget_name):
        # disallow anything but numbers in the input
        valid = new_value == '' or new_value.isdigit()
        # now that we've ensured the input is only integers, range checking!
        if valid:
            # get minimum and maximum values of the widget to be validated
            minval = int(self.root.nametowidget(widget_name).config('from')[4])
            maxval = int(self.root.nametowidget(widget_name).config('to')[4])
            # check if it's in range
            if int(user_input) not in range (minval, maxval):
                valid = False
        if not valid:
            self.root.bell()
        return valid

if __name__ == '__main__':
    mainwindow = GUI()
    mainloop()

我注意到不太有效的一件事是,如果您在旋转框中选择了整个文本,然后粘贴了错误的内容,例如文本。这完全破坏了验证。呃。

【讨论】:

  • 我遇到了同样的问题。问题是如果您试图覆盖选定的文本,它会被解释为两个独立的事件:删除然后插入。似乎没有任何方法可以知道删除事件之后是否会出现插入。真正需要的是一个新事件:覆盖选定的文本。
【解决方案3】:

我想出了一个适用于任何 Entry 小部件以及 SpinBox 的解决方案。它使用 validatecommand 来确保只输入正确的值。暂时验证一个空白条目,但在 FocusOut 上它会变回最后一个有效值。

intvalidate.py

import tkinter as tk


def int_validate(entry_widget, limits=(None, None)):
    """
    Validates an entry_widget so that only integers within a specified range may be entered

    :param entry_widget: The tkinter.Entry widget to validate
    :param limits: The limits of the integer. It is given as a (min, max) tuple

    :return:       None
    """
    num_str = entry_widget.get()
    current = None if (not _is_int(num_str)) else int(num_str)
    check = _NumberCheck(entry_widget, limits[0], limits[1], current=current)
    entry_widget.config(validate='all')
    entry_widget.config(validatecommand=check.vcmd)
    entry_widget.bind('<FocusOut>', lambda event: _validate(entry_widget, check))
    _validate(entry_widget, check)


def _is_int(num_str):
    """
    Returns whether or not a given string is an integer

    :param num_str: The string to test

    :return: Whether or not the string is an integer
    """
    try:
        int(num_str)
        return True
    except ValueError:
        return False


def _validate(entry, num_check):
    """
    Validates an entry so if there is invalid text in it it will be replaced by the last valid text

    :param entry: The entry widget
    :param num_check: The _NumberCheck instance that keeps track of the last valid number

    :return:    None
    """
    if not _is_int(entry.get()):
        entry.delete(0, tk.END)
        entry.insert(0, str(num_check.last_valid))


class _NumberCheck:
    """
    Class used for validating entry widgets, self.vcmd is provided as the validatecommand
    """

    def __init__(self, parent, min_, max_, current):
        self.parent = parent
        self.low = min_
        self.high = max_
        self.vcmd = parent.register(self.in_integer_range), '%d', '%P'

        if _NumberCheck.in_range(0, min_, max_):
            self.last_valid = 0
        else:
            self.last_valid = min_
        if current is not None:
            self.last_valid = current

    def in_integer_range(self, type_, after_text):
        """
        Validates an entry to make sure the correct text is being inputted
        :param type_:        0 for deletion, 1 for insertion, -1 for focus in
        :param after_text:   The text that the entry will display if validated
        :return:
        """

        if type_ == '-1':
            if _is_int(after_text):
                self.last_valid = int(after_text)

        # Delete Action, always okay, if valid number save it
        elif type_ == '0':
            try:
                num = int(after_text)
                self.last_valid = num
            except ValueError:
                pass
            return True

        # Insert Action, okay based on ranges, if valid save num
        elif type_ == '1':
            try:
                num = int(after_text)
            except ValueError:
                if self.can_be_negative() and after_text == '-':
                    return True
                return False
            if self.is_valid_range(num):
                self.last_valid = num
                return True
            return False
        return False

    def can_be_negative(self):
        """
        Tests whether this given entry widget can have a negative number

        :return: Whether or not the entry can have a negative number
        """
        return (self.low is None) or (self.low < 0)

    def is_valid_range(self, num):
        """
        Tests whether the given number is valid for this entry widgets range

        :param num: The number to range test

        :return: Whether or not the number is in range
        """
        return _NumberCheck.in_range(num, self.low, self.high)

    @staticmethod
    def in_range(num, low, high):
        """
        Tests whether or not a number is within a specified range inclusive

        :param num: The number to test if its in the range
        :param low: The minimum of the range
        :param high: The maximum of the range

        :return: Whether or not the number is in the range
        """
        if (low is not None) and (num < low):
            return False
        if (high is not None) and (num > high):
            return False
        return True

就是这样使用的

import tkinter as tk
from tkinter import ttk

from intvalidate import int_validate

if __name__ == '__main__':
    root = tk.Tk()
    var = tk.DoubleVar()
    widget = ttk.Spinbox(root, textvariable=var, justify=tk.CENTER, from_=0, to_=10)
    widget.pack(padx=10, pady=10)
    int_validate(widget, limits=(0, 10))
    root.mainloop()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-06
    • 1970-01-01
    • 2012-11-27
    相关资源
    最近更新 更多