Like most events in Tk, your <Key>
handler is fired before the event is processed by the built-in bindings, rather than after. This allows you to, for example, prevent the normal processing from happening, or change what it does.
But this means that you can't access the new value (whether via a StringVar
, or just by calling entry.get()
), because it hasn't been updated yet.
If you're using Text
, there's a virtual event <<Modified>>
that gets fired after the "modified" flag changes. Assuming you weren't using that flag for another purpose (e.g., in a text editor, you might want to use it to mean "enable the Save button"), you can use it to do exactly what you want:
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)
Usually, when you want something like this, you want an Entry
rather than a Text
. Which provides a much nicer way to do this: validation. As with everything beyond the basics in Tkinter, there's no way you're going to figure this out without reading the Tcl/Tk docs (which is why the Tkinter docs link to them). And really, even the Tk docs don't describe validation very well. But here's how it works:
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'))
The validatecommand
can take a list of 0 or more arguments to pass to the function. The %P
argument gets the new value the entry will have if you allow it. See VALIDATION
in the Entry manpage for more details.
If you want the entry to be rejected (e.g., if you want to actually block someone from entering more than 140 characters), just return False
instead of True
.
By the way, it's worth looking over the Tk wiki and searching for Tkinter recipes on ActiveState. It's a good bet someone's got wrappers around Text
and Entry
that hide all the extra stuff you have to do to make these solutions (or others) work so you just have to write the appropriate count
method. There might even be a Text
wrapper that adds Entry
-style validation.
There are a few other ways you could do this, but they all have downsides.
Add a trace
to hook all writes to a StringVar
attached to your widget. This will get fired by any writes to the variable. I guarantee that you will get the infinite-recursive-loop problem the first time you try to use it for validation, and then you'll run into other more subtle problems in the future. The usual solution is to create a sentinel flag, which you check every time you come into the handler to make sure you're not doing it recursively, and then set while you're doing anything that can trigger a recursive event. (That wasn't necessary for the edit_modified
example above because we could just ignore anyone setting the flag to False
, and we only set it to False
, so there's no danger of infinite recursion.)
You can get the new char (or multi-char string) out of the <Key>
virtual event. But then, what do you do with it? You need to know where it's going to be added, which character(s) it's going to be overwriting, etc. If you don't do all the work to simulate Entry
—or, worse, Text
—editing yourself, this is no better than just doing len(entry.get()) + 1
.
StringVar
isn't one event behind. The problem is that the default handler for the<Key>
event—the one that updates the variable—runs after your handler, instead of before. You can see this is you just check the variable from any other event after your<Key>
event. – RedonText
widget has an implicit newline at the end. So, when you do thatself.post_tweet.get(1.0, END))
, don't you also get one character too many? – Redon