This class forms a bridge between the main Qt event loop and python-based threads. This was a thorny problem to figure out, so I'm posting for others to benefit. Basically, you need to invoke all Qt GUI calls and (ActiveX calls on Windows) from the main Qt thread. But if you're using Python threads, you need to manage the interaction yourself.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
from qt import * class BaseQObject(QObject): MAIN_THREAD_ID = 0 def __init__(self): QObject.__init__(self) self.installEventFilter(self) self.event = None def eventFilter(self,obj,event): # FIXME: This is a workaround for an unexplained bug # The events were getting posted through postEVentWithCallback() # But the event() method wasn't getting called. But the eventFilter() # method is getting called. if event.type()==QEvent.User: cb = event.__dict__.get('callback') if cb: self._doEvent(event) return False return QObject.eventFilter(self,obj,event) def _doEvent(self,event): cb = event.__dict__.get('callback') if not cb: return data = event.__dict__.get('data') if data or type(data)==type(False): cb(data) else: cb() del event def event(self, event): if event.type()==QEvent.User: self._doEvent(event) return True return QObject.event(self, event) def postEventWithCallback(self, callback, data=None): # if we're in main thread, just fire off callback if get_ident()==BaseQObject.MAIN_THREAD_ID: if data or type(data)==type(False): callback(data) else: callback() # send callback to main thread else: event = QEvent(QEvent.User) event.callback = callback if data or type(data)==type(False): event.data = data qApp.postEvent(self, event) class ThreadLock: def __init__(self): if sys.platform == 'win32': from qt import QMutex self.mutex = QMutex(True) elif sys.platform == 'darwin': from Foundation import NSRecursiveLock self.mutex = NSRecursiveLock.alloc().init() def lock(self): self.mutex.lock() def unlock(self): self.mutex.unlock() import thread class Foo(BaseQObject): def doMainFoo(self): print 'doMainFoo(): thread id = '%thread.get_ident() def doFoo(self): print 'doFoo(): thread id = '%thread.get_ident() self.postEventWithCallback(self.doMainFoo) if __name__=="__main__": foofoo = Foo() import threading threading.Timer(1.0, foofoo.doFoo) from qt import QApplication app = QApplication() app.exec_loop()
Some of the code may be superfluous. For instance, you shouldn't really need qApp.sendPostedEvent(), but I did. Also eventFilter() isn't the place to intercept events, but its the only thing that worked.
The code is above is just a starter. Its not very mature, but provides an example of dealing with the potentially thorny issues of python threads and Qt event loop.
After working with the code more, I realized the extra call to sendPostedEvents() is actually a bug. Also, added a shortcut where if the call was coming from the main thread, the callback would be invoked directly. Assign the value of MAIN_THREAD_ID using the thread.get_ident() function, this should be done when the app starts up (from the main thread, of course).
Also added is the ThreadLock wrapper class that you'll need for thread locking within your python threads. An example of using this with Mac OS X Foundation kit locks is provided as an illustration.
An example of how to use the class is provided as well. The example runs a python threading.timer, which is triggered in a python thread. The timer calls a method, which calls another one from within the main Qt event loop.
Why not add a usage example? A usage example would make the recipe much easier to quickly comprehend. Thanks :)
Example Provided. I added a __main__ call to show how this is used in my situation.
Updated version. Here's a slightly shorter version that supports passing arbitrary positional arguments to the callback.
How about customEvent()? Try using QCustomEvent (or a subclass) and a customEvent() handler. Using QEvent and event() together with a user-defined event ID may be confusing Qt3, requiring you to meddle with eventFilter().
A much simpler, shorter version using a Queue and customEvent. This version has a slight semantic difference: all pending callbacks are called when an event is posted. This only has an effect if multiple events are posted in short succession before the main thread has time to process events. If this "feature" becomes problematic, the wile loop in customEvent() can be removed so a single event is processed for each event that is posted.
event.__dict__.get('callback')should be written as
getattr(event, 'callback', None)