Hi! I was currently searching for the possibility to communicate between tkinter and a python background thread. I hoped for something like java's invokeLater mechanism.
I found a lot of ideas, using a Queue for the communication and then poll in Tkinter on the Queue, raising the question which interval should be used. Then there was mentioned to use the after_idle() method to pass something into the Tk loop, but the method is not thread_safe as it seems. And then there was the idea of binding events to methods and to raise the event so that method will be called. It seems, that this is still a valid question, so I would like to share my solution and open it for discussion and hope you would review it and show me problems if there are any. First I want something like the invokeLater call from java, so I created to classes which will be "listener", one for the tk loop and one as a background thread: class BackgroundListener (threading.Thread) : def __init__(self): threading.Thread.__init__(self) self.queue = Queue.Queue() self.cancel = threading.Event() def invokeLater(self, delegate): self.queue.put(delegate) def interrupt(self) : self.cancel.set() def run(self): while not self.cancel.isSet() : try : delegate = self.queue.get(timeout=0.5) if not self.cancel.isSet() : # don't call if already finished ! delegate() except Queue.Empty : pass class TkListener : def __init__(self, tk) : self.queue = Queue.Queue() self.tk = tk self.event = "<<%s>>" % uuid.uuid1() tk.bind(self.event, self.invoke) def invokeLater(self, delegate) : self.queue.put(delegate) self.tk.event_generate(self.event, when='tail') def invoke(self, event) : try : while True : delegate = self.queue.get(block=False) delegate() except Queue.Empty : pass So, what happens here? The Background thread will store the delegate object (see below) into a Queue and the background thread is reading from this Queue, as Queue is threadsafe everything is fine. (I misuse the Event to create something like the interrupt() call from java) The TkListener will get a reference to the tk object and binds a event to it which calls a the invoke method. Therefore it is similar to the other listener. The invokeLater call adds the delegate to the Queue, then raise the event. The tkloop will then handle the event by calling the invoke method, where all waiting delegates are removed from the queue and called. So whats with the delegate class? Its part of a dynamic proxy implementation which can be used for any other purpose as long as the listener you provide has an invokeLater method. And that's what they look like: class Delegate : def __init__(self, real, name, args, kwargs) : self.real = real self.name = name self.args = args self.kwargs = kwargs def __call__(self) : method = getattr(self.real, self.name) apply(method, self.args, self.kwargs) class Delegator : def __init__(self, listener, real, name) : self.listener = listener self.real = real self.name = name def __call__(self, *args, **kwargs) : delegate = Delegate(self.real, self.name, args, kwargs) self.listener.invokeLater(delegate) class Proxy : def __init__(self, listener, real) : self.listener = listener self.real = real self.cache = {} def __getattr__(self, name) : try : delegator = self.cache[name] except KeyError : delegator = Delegator(self.listener, self.real, name) self.cache[name] = delegator return delegator The Proxy class will hold the listener and the real object. When you call a method on the proxy a delegator object for the method name is generated and cached. Then the delegator is returned and the caller call will be handled in the delegator. The delegator then creates a delegate object holding all the informations and the attributes of the call and the delegate object will be hand over to the listener via the invokeLater method. As you have seen above the listener will then get the delegate object from the Queue and call it, which leads to a call to the real object in the context of the listener. And at last, so that you have something to play around with, some test classes showing different ways on how to use the proxy object, even for callbacks over thread boundaries: import Tkinter import Queue import uuid import threading import thread class MyTkinterObject: def __init__(self, tk): frame = Tkinter.Frame(tk) self.quitButton = Tkinter.Button(frame, text="QUIT", fg="red", command=frame.quit) self.quitButton.pack(side=Tkinter.LEFT) self.helloButton = Tkinter.Button(frame, text="Hello") self.helloButton.pack(side=Tkinter.LEFT) self.hello2Button = Tkinter.Button(frame, text="Hello2", command=self.trigger2) self.hello2Button.pack(side=Tkinter.LEFT) frame.pack() def register(self, myBackgroundObject) : self.helloButton.bind("<Button-1>", myBackgroundObject.trigger) def trigger(self, callback) : print "%s - Front hello" % thread.get_ident() callback() def trigger2(self) : print "%s - Front hello" % thread.get_ident() class MyBackgroundObject : def __init__(self, listener): self.proxy = Proxy(listener, self) def register(self, myTkinterObject) : self.myTkinterObject = myTkinterObject def trigger(self, event) : print "%s - Back hello - %s" % (thread.get_ident(), event) self.myTkinterObject.trigger(self.proxy.callback) def callback(self) : print "%s - Back bye" % thread.get_ident() def main() : root = Tkinter.Tk() tkListener = TkListener(root) backgroundListener = BackgroundListener() myTkinterObject = MyTkinterObject(root) myBackgroundObject = MyBackgroundObject(backgroundListener) myTkinterObject.register(myBackgroundObject.proxy) myBackgroundObject.register(Proxy(tkListener, myTkinterObject)) backgroundListener.start() root.mainloop() backgroundListener.interrupt() if __name__ == "__main__": main() I would like to hear your comments! thanks, AO _______________________________________________ Tkinter-discuss mailing list Tkinter-discuss@python.org http://mail.python.org/mailman/listinfo/tkinter-discuss