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

Dan*_*etz 5 python validation tkinter ttk

每当内容改变时,我想设置或清除a的invalid状态标志ttk.Entry.我这样做是通过连接StringVar到条目,在trace()回调中,调用state(['valid'])state(['!invalid']).标志由我的回调正确设置,但是,只要焦点离开文本条目,它就会被清除!我该如何避免或解决这个问题?

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

我没有使用这个小部件的内置验证功能,因为根据Tk文档,如果我在编辑文本时调用验证命令(-validate等于'keys''all'),

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

就像我之前说过的那样,我不希望这样.我想要的-validate平等'none'是应该做的:

验证仅在validatewidget命令特别请求时发生.

很棒,所以理论上我所要做的就是永远不要打电话validate().不幸的是,invalid无论如何,国旗正在被清除.我可以在Python的交互模式中重现这种意外和不需要的行为:

>>> 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',)
Run Code Online (Sandbox Code Playgroud)

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

>>> entry.state()
()
Run Code Online (Sandbox Code Playgroud)

为什么越来越被清除,当下-validate'none'不是'focus'还是'all'invalid为了我的目的,我有什么办法可以使用州吗?

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

Dan*_*etz 3

为什么会发生这种情况

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

因此,正如目前所实现的那样,该invalid标志不会通过焦点事件持续存在。“不重新验证”实际上意味着“不进行重新验证,而是清除标志invalid”。

一个办法

如果你无法击败他们,那就加入他们。validate='focus'validatecommand返回文本是否有效的 a 一起使用,就好像您一直想在焦点事件上检查这一点一样。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 ...
Run Code Online (Sandbox Code Playgroud)

另一个(类似)解决方案

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

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

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