views:

67

answers:

1
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.

+2  A: 

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

Bindings are fired in the order specified by a widget's 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 current toplevel widget it is fired next
  4. if there is a binding on "all" it will fire next.

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

In the default case, your binding on 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. Once 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-1, or trim off one newline from the text.

Bryan Oakley
Thanks for taking the time to type that up Bryan, explains alot.
dave