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.