每次在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))
一个简单的解决方案是在类绑定之后添加一个新的bindtag。这样,类绑定将在绑定之前触发。看到这个问题的答案如何绑定自事件在Tkinter文本小部件后,它将由文本小部件绑定?举个例子。这个答案使用条目小部件而不是文本小部件,但是这两个小部件之间的bindtags概念是相同的。只是要确保在适当的地方使用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()
像Tk中的大多数事件一样,您的<Key>
处理程序在事件被内置绑定处理之前(而不是之后)触发。例如,这允许您阻止正常处理的发生,或更改其所做的工作。
但是这意味着你不能访问新值(无论是通过StringVar
,还是仅仅通过调用entry.get()
),因为它还没有更新。
如果你使用的是Text
,有一个虚拟事件<<Modified>>
在"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)
通常,当你想要这样的东西时,你想要一个
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上搜索Tkinter食谱是值得的。很有可能有人在Text
和Entry
周围进行了包装,隐藏了您为使这些解决方案(或其他解决方案)工作而必须做的所有额外工作,因此您只需要编写适当的count
方法。甚至可能是一个Text
包装器,它添加了Entry
样式的验证。
还有其他一些方法可以做到这一点,但它们都有缺点。
添加一个trace
,将所有写操作挂接到您的小部件上的StringVar
。这将被触发,任何写入变量。我保证,当您第一次尝试使用它进行验证时,您将遇到无限递归循环问题,然后您将在将来遇到其他更微妙的问题。通常的解决方案是创建一个哨兵标志,每次进入处理程序时检查它,以确保没有递归地执行它,然后在执行任何可能触发递归事件的操作时设置它。(对于上面的edit_modified
示例,这是不必要的,因为我们可以忽略任何将标志设置为False
的人,我们只将其设置为False
,因此没有无限递归的危险。)
您可以从<Key>
虚拟事件中获得新的字符(或多字符字符串)。但是,你用它做什么呢?你需要知道它将被添加到哪里,它将覆盖哪个字符,等等。如果您自己不做所有的工作来模拟Entry
——或者更糟的是,模拟Text
——编辑,那么这并不比只做len(entry.get()) + 1
好。