6

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

但这StringVar()总是背后的一个事件。据我所知,这是因为事件是在字符输入到 Text 小部件之前处理的。这意味着如果我在该字段中有 3 个字符并且我输入了第 4StringVar个字符,则会更新但仍然是 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))
4

2 回答 2

4

一个简单的解决方案是在类绑定之后添加一个新的绑定标签。这样类绑定将在您的绑定之前触发。请参阅这个问题的答案How to bind self events in Tkinter Text widget after it will bind by Text 小部件?例如。该答案使用条目小部件而不是文本小部件,但绑定标签的概念在这两个小部件之间是相同的。请务必使用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()
于 2013-04-02T20:52:07.090 回答
3

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

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


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

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)

通常,当你想要这样的东西时,你想要的是Entrya 而不是 a 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'))

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

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


顺便说一句,值得查看Tk wiki并在ActiveState上搜索 Tkinter 食谱。这是一个很好的赌注,有人有包装器,Text并且Entry隐藏了使这些解决方案(或其他)工作所需的所有额外内容,因此您只需要编写适当的count方法即可。甚至可能有一个添加-style 验证的Text包装器。Entry

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

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

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

于 2013-04-02T19:13:18.123 回答