【问题标题】:Tkinter: set StringVar after <Key> event, including the key pressedTkinter:在 <Key> 事件之后设置 StringVar,包括按下的键
【发布时间】:2013-04-02 18:36:53
【问题描述】:

每次将字符输入Text 小部件时,我都想获取该小部件的内容并从某个数字中减去其长度(基本上是“您还剩 x 个字符”的交易)。

StringVar() 总是落后一个事件。据我所知,这是因为事件是在字符输入到 Text 小部件之前处理的。这意味着如果我在字段中有 3 个字符并且我输入了第 4 个字符,StringVar 会更新但仍然是 3 个字符长,然后当我输入第 5 个字符时它会更新为 4 个字符。

有没有办法让两者保持一致?

这里有一些代码。我删除了不相关的部分。

def __init__(self, master):
    self.char_count = StringVar()
    self.char_count.set("140 chars left")

    self.post_tweet = Text(self.master)
    self.post_tweet.bind("<Key>", self.count)
    self.post_tweet.grid(...)

    self.char_count = Label(self.master, textvariable=self.foo)
    self.char_count.grid(...)

def count(self):
    self.x = len(self.post_tweet.get(1.0, END))
    self.char_count.set(str(140 - self.x))

【问题讨论】:

  • 你能贴一些代码吗?很难在没有看到任何东西的情况下理解您想要什么以及您尝试过什么。
  • 不,StringVar 不是一个事件。问题是&lt;Key&gt; 事件的默认处理程序(更新变量的处理程序)您的处理程序之后运行,而不是之前。您可以看到这是您在 &lt;Key&gt; 事件之后从任何其他事件中检查变量。
  • 我想你可能还有另一个问题。 IIRC,默认的 Text 小部件末尾有一个隐式换行符。所以,当你这样做self.post_tweet.get(1.0, END))时,你不会也得到一个字符太多吗?

标签: python tkinter text-widget


【解决方案1】:

一个简单的解决方案是在类绑定之后添加一个新的绑定标签。这样类绑定将在您的绑定之前触发。有关示例,请参阅问题 How to bind self events in Tkinter Text widget after it will binded by Text widget?this answer。该答案使用条目小部件而不是文本小部件,但绑定标签的概念在这两个小部件之间是相同的。请务必在适当的情况下使用Text 而不是Entry

另一个解决方案是在 KeyRelease 上进行绑定,因为默认绑定发生在 KeyPress 上。

下面是一个例子,展示了如何使用 bindtags 来做到这一点:

import Tkinter as tk

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

        self.post_tweet = tk.Text(self)
        bindtags = list(self.post_tweet.bindtags())
        bindtags.insert(2, "custom") # index 1 is where most default bindings live
        self.post_tweet.bindtags(tuple(bindtags))

        self.post_tweet.bind_class("custom", "<Key>", self.count)
        self.post_tweet.grid()

        self.char_count = tk.Label(self)
        self.char_count.grid()

    def count(self, event):
        current = len(self.post_tweet.get("1.0", "end-1c"))
        remaining = 140-current
        self.char_count.configure(text="%s characters remaining" % remaining)

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

【讨论】:

  • 好的,对我来说效果很好。大多。我发现默认的小部件名称是“.4456585”,而不是我命名的小部件实例。仍然一切正常,但是当我运行 len(self.text.get(1.0, END) 时,它比预期的大 1。所以“你好”将是 6 个字符而不是 5 个字符。我通过减去 1 解决了这个问题,一切都很好,但我很好奇为什么会发生这种情况。
  • @RoryByrne:文本小部件总是添加一个额外的换行符,以保证至少有一个换行符。这看起来很奇怪,但它确实让很多事情变得更简单。每当您从小部件中提取文本时,您都应该使用"end-1c"
  • 这很有意义。感谢所有的帮助。 @abarnert
【解决方案2】:

与 Tk 中的大多数事件一样,您的 &lt;Key&gt; 处理程序在 事件被内置绑定处理之前触发,而不是之后。例如,这允许您阻止正常处理的发生,或更改它的作用。

但这意味着您无法访问新值(无论是通过StringVar,还是仅通过调用entry.get()),因为它尚未更新。


如果您使用的是Text,则会在“修改”标志更改后触发一个虚拟事件&lt;&lt;Modified&gt;&gt;。假设您没有将该标志用于其他目的(例如,在文本编辑器中,您可能希望使用它来表示“启用保存按钮”),您可以使用它来做您想做的事情:

def count(self, event=None):
    if not self.post_tweet.edit_modified():
        return
    self.post_tweet.edit_modified(False)
    self.x = len(self.post_tweet.get(1.0, END))
    self.char_count.set(str(140 - self.x))

# ...

self.post_tweet.bind("<<Modified>>", self.count)

通常,当您想要这样的东西时,您想要Entry 而不是Text。这提供了一种更好的方法来做到这一点:验证。与 Tkinter 基础知识之外的所有内容一样,如果不阅读 Tcl/Tk 文档(这就是 Tkinter 文档链接到它们的原因),您将无法弄清楚这一点。实际上,即使是 Tk 文档也没有很好地描述验证。但它是这样工作的:

def count(self, new_text):
    self.x = len(new_text)
    self.char_count.set(str(140 - self.x))
    return True

# ...

self.vcmd = self.master.register(self.count)
self.post_tweet = Edit(self.master, validate='key',
                       validatecommand=(self.vcmd, '%P'))

validatecommand 可以将 0 个或多个参数的列表传递给函数。如果您允许,%P 参数将获取条目将具有的新值。有关详细信息,请参阅条目手册页中的 VALIDATION

如果您希望拒绝输入(例如,如果您想阻止某人输入超过 140 个字符),只需返回 False 而不是 True


顺便说一句,值得查看Tk wiki 并在ActiveState 上搜索 Tkinter 食谱。可以肯定的是,有人在 TextEntry 周围有包装器,它们隐藏了使这些解决方案(或其他)工作所需的所有额外内容,因此您只需编写适当的 count 方法。 可能甚至会有一个 Text 包装器添加 Entry 样式验证。

还有其他几种方法可以做到这一点,但它们都有缺点。

添加trace 以将所有写入挂钩到附加到您的小部件的StringVar。这将被 any 写入变量触发。我保证您第一次尝试使用它进行验证时会遇到无限递归循环问题,然后您将来会遇到其他更微妙的问题。通常的解决方案是创建一个哨兵标志,每次进入处理程序时都要检查它以确保您没有递归地执行它,然后在您执行任何可能触发递归事件的事情时设置它。 (对于上面的 edit_modified 示例,这不是必需的,因为我们可以忽略任何将标志设置为 False 的人,而我们只将其设置为 False,因此不存在无限递归的危险。)

您可以从&lt;Key&gt; 虚拟事件中获取新的字符(或多字符字符串)。但是,你用它做什么呢?你需要知道它将被添加到哪里,它将被覆盖哪些字符,等等。如果你不做所有的工作来模拟Entry——或者更糟糕的是,Text——编辑自己,这并不比只做len(entry.get()) + 1更好。

【讨论】:

  • 虽然我不太了解验证是如何工作的,但它在此处可以解决问题如果我使用 Entry 小部件。问题是我正在使用文本小部件(我不能使用条目小部件),所以我得到“TclError:未知选项'-validatecommand'”。有没有办法让验证与 Text 小部件一起工作?我可以尝试查看您的其他建议,但我不想太过分了。
  • 此解决方案不适用于文本小部件,因为它不支持验证选项。
  • 抱歉,没有看到您的代码,我以为您正在寻找一个非常短的字符串,这对于Entry 来说是一个明显的例子。对于输入推文,是的,您需要Text,并且那里没有验证。 Tk wiki 可能有办法将Entry-validation 应用于Text,但除此之外……让我考虑一下并写一些适当的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-25
  • 1970-01-01
  • 2015-01-28
  • 1970-01-01
  • 2011-02-25
  • 1970-01-01
  • 2019-09-27
相关资源
最近更新 更多