如何在失去焦点时确保我的ttk.Entry无效状态不被清除?

时间:2015-05-19 22:37:56

标签: python validation tkinter ttk

每当内容发生变化时,我想设置或清除invalid的{​​{1}}状态标志。我是通过将ttk.Entry与条目相关联,并在StringVar回调中调用trace()state(['valid'])来实现此目的的。标志由我的回调正确设置,但是,只要焦点离开文本条目,它就会被清除!我该如何避免或解决这个问题?

我想设置或清除标志,因为我可以根据状态标志更改视觉样式。我不想禁止用户输入任何无效的内容;我希望他们可以随意输入他们想要的东西,并立即查看它是否有效。我想特别使用state(['!invalid'])标志,而不是invalid标志,不仅因为alternate是更合乎逻辑的选择,还因为我已经在使用invalid标志为别的东西。

我没有使用此小部件的内置验证功能,因为根据the Tk docs,如果我在编辑文本时调用验证命令(alternate等于{{1} }}或-validate),

  

在每次编辑之前,该条目将被预先验证...如果预验证失败,则拒绝编辑。

就像我之前说过的那样,我不希望如此。我想要'keys'等于'all'应该做的事情:

  

验证仅在-validate小部件命令特别请求时发生。

很好,理论上我所要做的就是永远不要打电话给'none'。不幸的是,无论如何都要清除validate标志。我可以在Python的交互模式中重现这种意外和不需要的行为:

validate()

到目前为止,这么好。 (我在这个例子中使用的是Python 3,但是我在Python 2中获得了相同的结果。)现在我将焦点更改为输入框和输入框,并且:

invalid

>>> import tkinter as tk >>> from tkinter import ttk >>> win = tk.Tk() >>> entry = ttk.Entry(win) >>> entry.pack() >>> entry['validate'] 'none' >>> entry.state() () >>> entry.state(['invalid']) ('!invalid',) >>> entry.state() ('invalid',) >>> entry.state() () 而不是-validate'none'时,为什么会被清除?为了我的目的,我有什么办法可以使用'focus'州吗?

我在Linux上使用Tcl / Tk版本8.6看到了Python 3.4.2和2.7.9的相同行为。

3 个答案:

答案 0 :(得分:3)

将您自己的绑定添加到<FocusOut>,调用您的验证功能并重置状态。

这是一个完整的工作示例。如果条目小部件包含单词&#34;无效&#34;,则状态将更改为&#34;无效&#34;。然后,您可以单击窗口小部件以查看状态是否仍然无效:

try:
    import Tkinter as tk
    import ttk
except ImportError:
    import tkinter as tk
    from tkinter import ttk

class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        # give invalid entries a red background
        style = ttk.Style()
        style.map("TEntry",  background=[('invalid', "red")])

        self.entryVar = tk.StringVar()
        self.entry = ttk.Entry(self, textvariable=self.entryVar)

        # this label will show the current state, updated 
        # every second.
        self.label = tk.Label(self, anchor="w")
        self.after_idle(self.updateLabel)

        # layout the widgets
        self.entry.pack(side="top", fill="x")
        self.label.pack(side="bottom", fill="x")

        # add trace on the variable to do custom validation
        self.entryVar.trace("w", self.validate)

        # set up bindings to also do the validation when we gain
        # or lose focus
        self.entry.bind("<FocusIn>", self.validate)
        self.entry.bind("<FocusOut>", self.validate)

    def updateLabel(self):
        '''Display the current entry widget state'''
        state = str(self.entry.state())
        self.label.configure(text=state)
        self.after(1000, self.updateLabel)

    def validate(self, *args):
        '''Validate the widget contents'''
        value = self.entryVar.get()
        if "invalid" in value:
            self.entry.state(["invalid"])
        else:
            self.entry.state(["!invalid"])

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()

答案 1 :(得分:2)

为什么会发生

在Tk源文件generic/ttk/ttkEntry.c中,EntryRevalidate()始终在焦点事件上运行。这会调用EntryValidateChange(),当它注意到焦点事件的验证被“关闭”时,会返回指示当前值有效的结果EntryRevalidate(),清除invalid标志。

因此,正如当前实现的那样,invalid标志不会通过焦点事件持续存在。 “没有重新验证”的确意味着“而不是重新验证,清除invalid标志”。

解决方案

如果你无法击败他们,请加入他们。将validate='focus'validatecommand一起使用可以返回文本是否有效,就好像您一直希望在焦点事件上检查这一点一样。当事情真的发生变化时,继续设置invalid标志。例如:

class MyEntry(ttk.Entry):

    def __init__(self, master):
        self.var = tk.StringVar(master)
        super().__init__(master,
                         textvariable=self.var,
                         validate='focus',
                         validatecommand=self.is_valid)
        self.var.trace('w', self.revalidate)
        self.revalidate()

    def revalidate(self, *args):
        self.state(['!invalid' if self.is_valid()
                    else 'invalid'])

    def is_valid(self, *args):
        # ... return if current text is valid ...

另一个(类似的)解决方案

您可以简单地在焦点事件中保持状态标志:

class ValidationDisabledEntry(ttk.Entry):
    def __init__(self, *args, **kwargs):
        super().__init__(
            *args,
            validate='focus',
            validatecommand=lambda *a: 'invalid' not in self.state(),
            **kwargs)

然后随时设置或清除invalid标志,而不必担心焦点事件清除它。如上所述设置validatevalidatecommand应该(并且似乎)实现我认为validate='none'会让我的行为。

答案 2 :(得分:-2)

这是您的问题的解决方案。您可能希望根据需要调整它,但它可能会给您一个想法:

import tkinter as tk
from tkinter import ttk

class ValidatingEntry(ttk.Entry):

    COLOR_VALID = "#99ff99"
    COLOR_INVALID="#ff9999"

    def __init__(self, master, *args, **kwargs):
        self.stringVar = tk.StringVar()

        tk.Entry.__init__(self, master, *args, textvariable = self.stringVar, **kwargs)

        self.validatingFunction = None

        self.bind("<FocusOut>",self.validation)

    def validation(self, event):

        if self.validatingFunction != None:
            if self.validatingFunction():
                self['bg']=ValidatingEntry.COLOR_VALID
            else:
                self['bg']=ValidatingEntry.COLOR_INVALID
        else:
            print("no evaluation possible for the content of this entry")


if __name__ == "__main__":
    app = tk.Tk()

    entry = ValidatingEntry(app)
    entry.validatingFunction = lambda : 'foo' in entry.stringVar.get()
    entry.pack()

    entry2 = ValidatingEntry(app)
    entry2.validatingFunction = lambda : 'bar' in entry2.stringVar.get()
    entry2.pack()


    app.mainloop()

可能可以修改类以获取模式参数,并使用它来检查StringVar内容是否与模式匹配为例如regexp。但这与传统知识无关。

希望它有所帮助。 亚瑟。