【问题标题】:Python Tkinter Spinbox Validate FailPython Tkinter Spinbox 验证失败
【发布时间】:2019-06-12 13:47:32
【问题描述】:

我在验证旋转框输入时遇到问题。我有一个似乎可以工作的解决方法;但是,这很尴尬。假设这不是错误,是否有正确的方法来做到这一点?我在 Windows 10 上使用 Anaconda Python 3.6 (tk 8.6)。

问题在于,当旋转框条目中的值介于 tofrom 之间时,如果从验证函数返回 False,则 validate 设置为 None。这仅在单击向上或向下按钮时发生,而不是在直接编辑文本时发生。

import tkinter as tk

class SpinboxGui:

    def __init__(self):
        self.root = tk.Tk()
        vcmd = (self.root.register(self.validate_spin), '%W', '%P')
        self.spin = tk.Spinbox(self.root, from_=0, to=50000)
        self.spin.config(validate="key", validatecommand=vcmd)
        self.spin.pack()

    def validate_spin(self, name, nv):
        try:
            print(nv)
            n = int(nv)
        except:
            return False
        if n <= 15000:
            return True
        return False

if __name__ == "__main__":
    SpinboxGui()
    tk.mainloop()

要重现,请突出显示 0 并输入 149999。然后向上单击几次。请注意,验证命令停止被调用。输出是:

01
014
0149
01499
014999
0149999
15000
15001

现在,根据docs,同时使用textVariablevalidateCommand 是危险的;事实上,我拥有crashed Python/Tkinter 的方式不止一种。但是,在这种情况下,您是否使用textVariable 并不重要;问题是一样的。

一种可能的解决方案是在验证函数中编辑tofrom 选项。即使这可行,对我来说也有些问题,因为我正在将旋转框值同步到嵌入式 Matplotlib 图。我需要计算 tofrom 并为每个 Matplotlib Artist 和 spinbox 转换单位。

由于您无法在验证功能中编辑textVariable,因此我想出的是以下内容。也许有人可以对此进行改进。

def __init__(self):
    # http://stackoverflow.com/a/4140988/675216
    vcmd= (self.root.register(self.validate_spin), '%W', '%P')
    # Rest of code left out
    self.spin.config(validate="key", validatecommand=vcmd)
    self.spin.bind("<<ResetValidate>>", self.on_reset_validate)

def on_reset_validate(self, event):
    # Turn validate back on and set textVariable
    self.spin.config(validate="key")

def validate_spin(self, name, nv):
    # Do validation ...
    if not valid:
        self.spin.event_generate("<<ResetValidate>>", when="tail")
    return valid

【问题讨论】:

  • 此语句为假:"问题是如果您从验证函数返回 False,则 validate 设置为 None..." - 不会设置验证函数到None 只是因为你返回False。事实上,唯一有效的返回值是TrueFalse。请阅读并遵循此处的建议:How to create a Minimal, Complete, and Verifiable example
  • 重现问题需要花费大量时间和精力。不是我懒得做;我认为这可能是旋转框的预期行为。我不想花时间去寻找一个不存在的问题。但现在我知道了,就在这里。

标签: python tkinter


【解决方案1】:

在与 spinbox 中的验证机制苦苦挣扎后,我放弃了它。也许它按预期的方式工作,但我认为它只被调用一次是违反直觉的。我的应用程序使用 spinbox 来更新 matplotlib 图,并且我需要数据是指定范围内的整数。我需要代码来捕获非整数条目以及超出范围的整数。我想出的解决方案是使用键绑定而不是验证机制来达到预期的结果。这是代码的相关部分:

class IntSpinbox(ttk.Frame):

    def __init__(self, parent, **kwargs):
        ttk.Frame.__init__(self,
                   parent,
                   borderwidth=kwargs.get('frameborderwidth', 2),
                   relief=kwargs.get('framerelief', tk.GROOVE))
        self.valuestr = tk.StringVar()
        self.valuestr2 = tk.StringVar()
        self.minvalue = kwargs.get('minvalue', 0)
        self.maxvalue = kwargs.get('maxvalue', 99)
        self.initval = kwargs.get('initvalue', self.minvalue)
        self.valuestr.set(str(self.initval))
        self.valuestr2.set(str(self.initval))
        self.label = ttk.Label(self,
                   text=kwargs.get('labeltext', 'No label'),
                   anchor='w',
                   width=kwargs.get('labelwidth', 20))
        self.spinbox = tk.Spinbox(self,
                   from_=self.minvalue,
                   to=self.maxvalue,
                   increment=1,
                   textvariable=self.valuestr)
        self.spinbox.bind('<Return>', self.updateSpinbox)
        self.spinbox.bind('<FocusOut>', self.updateSpinbox)
        self.spinbox.bind('<FocusIn>', self.storeSpinbox)
        self.spinbox.bind('<Button-1>', self.storeSpinbox)
        self.spinbox.bind('<Button-2>', self.storeSpinbox)

        self.label.pack(side=tk.TOP, fill=tk.X, expand=True, padx=5)
        self.spinbox.pack(side=tk.BOTTOM, fill=tk.X, expand=True, padx=2, pady=5)
        self.onChange = kwargs.get('onchange', self.doNothing)

    def storeSpinbox(self, event):
        tmpstr = self.valuestr.get()
        try:
            tmpval = int(tmpstr)
        except:
            tmpval = -1000
        if tmpval < self.minvalue:
            tmpval = self.minvalue
        elif tmpval > self.maxvalue:
            tmpval  = self.maxvalue
        self.valuestr2.set(str(tmpval))        

    def updateSpinbox(self, event):
        tmpstr = self.valuestr.get()
        try:
            tmpval = int(tmpstr)
        except:
            tmpstr = self.valuestr2.get()
            self.valuestr.set(tmpstr)
            return
        if tmpval < self.minvalue:
            tmpval = self.minvalue
        elif tmpval > self.maxvalue:
            tmpval  = self.maxvalue
        tmpstr = str(tmpval)
        self.valuestr.set(tmpstr)
        self.valuestr2.set(tmpstr)
        self.onChange()

    def doNothing(self):
        pass

    def getValue(self):
        tmpstr = self.valuestr.get()
        return(int(tmpstr))

    def setValue(self, value):
        self.valuestr.set(str(value))

【讨论】:

  • 可能有很多方法可以绕过它。问题是它的行为是否符合预期,或者我们只是做错了什么。
【解决方案2】:

我可能迟到了,但我会把它留在这里以防有人需要它。我在类似情况下所做的是在回调中做我需要的一切,以编写我链接到微调框的变量。比如:

import Tkinter as tk

root = tk.Tk()
my_var = tk.IntVar() # or whatever you need
spin = tk.Spinbox(root, from_=0, to=100, textvariable=my_var)
spin.pack()

def do_whatever_I_need(*args):
    # here I can access the Spinbox value using spin.get()
    # I can do whatever check I 

my_var.trace('w', whatever) #'w' for "after writing"

trace 方法创建的回调调用带有两个参数的给定函数:回调模式(在本例中为'w')和变量名(这是我从未使用过的一些内部 tkinter 标识符)。这就是为什么do_wahtever_I_need 的签名是*args

【讨论】:

    猜你喜欢
    • 2015-09-18
    • 1970-01-01
    • 2021-11-26
    • 2020-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多