python gui events out of order

from Tkinter import *
from tkMessageBox import *

class Gui:
 def __init__(self, root):
  self.container = Frame(root)
  self.container.grid()
  self.inputText = Text(self.container, width=50, height=8)
  self.outputText = Text(self.container, width=50, height=8, bg='#E0E0E0', state=DISABLED)
  self.inputText.grid(row=0, column=0)
  self.outputText.grid(row=0, column=1)

  self.inputText.bind("<Key>", self.translate)

 def translate(self, event):
  input  = self.inputText.get(0.0, END)
  output = self.outputText.get(0.0, END)

  self.outputText.config(state=NORMAL)
  self.outputText.delete(0.0, END)
  self.outputText.insert(INSERT, input)
  self.outputText.config(state=DISABLED)

  showinfo(message="Input: %s characters\nOutput: %s characters" % (len(input), len(input)))


root = Tk()   #toplevel object
app = Gui(root)  #call to the class where gui is defined
root.mainloop()  #enter event loop

Working on a gui in tkinter I'm a little confused as to the sequence the event handlers are run. If you run the above code you'll hopefully see...

1) Editing the text widget triggers the event handler but it seems to fire it off without registering the actual change, 2) Even when the text widget is cleared (ie, keep pressing BackSpace) it still seems to have a one character length string, 3) The output widget only receives its update when the NEXT event trigger is fired despite the fact the data came on the previous event.

Is this just how bindings work in tkinter or am i missing something here?

The behaviour i would like when updating the input widget is: 1) Show the change, 2) Enter event handler, 3) Update output widget, 4) Show message box.


Solution 1:

This is how bindings work (and that's a good thing), but your problem is easily solved.

Bindings are fired in the order specified by a widgets binding tags (also known as bind tags or bindtags). Unless you specify otherwise, the bindings happen in the following order:

  1. if there is a binding directly on the widget it will be fired before any other bindings.
  2. if there is a binding on the widget's class, it is fired next
  3. if there is a binding on the toplevel widget that contains the widget, it is fired next (note: the root window is considered a toplevel window in this context)
  4. if there is a binding on "all" it will fire next.

The sequence can be stopped at any point by one of the event handlers, but that's beside the point for this specific discussion.

In the default case, your binding on <Key> happens before the class binding, and it is the class binding where the text is actually inserted into the widget. That is why your binding always seems to be one character behind.

Normally this order of things is exactly right, since more specific bindings get a chance to override the default behavior. If it wasn't this way you'd always get the default behavior even if you didn't want it. One place where this is not always what you want is when you want to augment the default bindings rather than replace them.

You can swap the order of the bindtags so that the class binding happens first. Or, add an additional bindtag to your text widget and add that in the sequence after the class binding, and bind to that. Usually adding a bindtag is the better solution, but not always.

To change the bindtags you can do something like this:

self.inputText.bindtags(((str(self.inputText)), "Text", "post-insert", ".", "all"))

To bind to "post-insert", do it with the bind_class method:

self.inputText.bind_class("post-insert", "<Key>", self.translate)

It may seem odd, but bindtags are one of the most powerful binding mechanisms out there. They give you complete and total control over the order of bindings, which is much more difficult with any other toolkit.

By the way, don't forget that if you get all the characters to the end of the text widget there will always be an extra newline at the end. Either get to end-1c, or trim off one newline from the text.