Tkinter Spinbox范围验证

时间:2015-07-01 10:22:02

标签: python validation input tkinter

我试图利用布莱恩·奥克利的这个出色的答案,但无济于事(https://stackoverflow.com/a/4140988/5060127)......

我想使用相同的方法来验证Spinbox值。我已经定义了from_和to spinbox的值,但是用户仍然可以在其中键入大部分内容......应该验证只有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()

3 个答案:

答案 0 :(得分:2)

我想我发现了这个问题。最初使用S=''调用Validator函数,并且条件S.isdigit()返回False,并且不再调用函数。但在我将条件更新为valid = S == '' or S.isdigit()后,它开始按预期工作。

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

答案 1 :(得分:1)

我做到了!将小部件的from_和值考虑在内的整数输入和范围检查都正常工作!它可能看起来有点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()

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

答案 2 :(得分:0)

我想出了一种适用于任何Entry小部件的解决方案,也适用于SpinBox的解决方案。它使用validate命令确保仅输入正确的值。空白条目会暂时生效,但是在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()