2012-06-12 20 views
14

Tengo una interfaz gráfica de PySide (Qt) que genera múltiples hilos. Los hilos a veces necesitan actualizar la GUI. He resuelto este de la siguiente manera:PySide: forma más fácil de actualizar GUI desde otro hilo

class Signaller(QtCore.QObject) : 
    my_signal = QtCore.Signal(QListWidgetItem, QIcon) 
signaller = Signaller() 

class MyThread(threading.Thread): 
    def __init__(self): 
     super(IconThread, self).__init__() 
     # ... 

    def run(self) : 
     # ... 

     # Need to update the GUI 
     signaller.my_signal.emit(self.item, icon) 

# 
# MAIN WINDOW   
# 
class Main(QtGui.QMainWindow): 

    def __init__(self): 
     QtGui.QMainWindow.__init__(self) 

     # ... 

     # Connect signals 
     signaller.my_signal.connect(self.my_handler) 

    @QtCore.Slot(QListWidgetItem, QIcon) 
    def my_handler(self, item, icon): 
     item.setIcon(icon) 

    def do_something(self, address): 
     # ... 

     # Start new thread 
     my_thread = MyThread(newItem) 
     my_thread.start() 

    # ... 

¿Existe una manera más fácil? Crear las señales, manipuladores y conectarlos requiere unas pocas líneas de código.

+0

¿Por qué no estás usando 'QThread'? – Avaris

+0

Si es más fácil con un 'QThread', consideraría usar uno. El problema es que el código existente a menudo tiende a usar 'threading.Thread'. – Petter

+1

Es mejor, ya que 'QThread' admite señales. No necesitarás tu clase 'Signaller'. Pero básicamente, tu camino es el camino. Necesita señales y ranuras para comunicarse entre hilos y GUI. – Avaris

Respuesta

17

Empecé a codificar con PySide recientemente y necesitaba un equivalente del comportamiento de PyGObject GLib.idle_add. Basé el código de tu respuesta (https://stackoverflow.com/a/11005204/1524507) pero este usa eventos en lugar de usar una cola nosotros mismos.

from PySide import QtCore 


class InvokeEvent(QtCore.QEvent): 
    EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType()) 

    def __init__(self, fn, *args, **kwargs): 
     QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE) 
     self.fn = fn 
     self.args = args 
     self.kwargs = kwargs 


class Invoker(QtCore.QObject): 
    def event(self, event): 
     event.fn(*event.args, **event.kwargs) 

     return True 

_invoker = Invoker() 


def invoke_in_main_thread(fn, *args, **kwargs): 
    QtCore.QCoreApplication.postEvent(_invoker, 
     InvokeEvent(fn, *args, **kwargs)) 

Que se utiliza de la misma manera en el enlace de respuesta anterior.

+0

Creo que esto se ve bien. – Petter

+0

Esto es genial. E incluso la pequeña solución sobre envolver 'registerEventType' nuevamente en' QEvent.Type' para que funcione en PySide me abrió los ojos. Gracias, usaré el código. – Trilarion

6

Esto es lo que tengo hasta ahora. Escribí el siguiente código en algún lugar de un módulo de ayuda:

from Queue import Queue 
class Invoker(QObject): 
    def __init__(self): 
     super(Invoker, self).__init__() 
     self.queue = Queue() 

    def invoke(self, func, *args): 
     f = lambda: func(*args) 
     self.queue.put(f) 
     QMetaObject.invokeMethod(self, "handler", QtCore.Qt.QueuedConnection) 

    @Slot() 
    def handler(self): 
     f = self.queue.get() 
     f() 
invoker = Invoker() 

def invoke_in_main_thread(func, *args): 
    invoker.invoke(func,*args) 

Entonces mis hilos pueden correr muy fácilmente el código para actualizar la interfaz gráfica de usuario en el hilo principal. No es necesario crear y conectar señales para cada operación.

class MyThread(threading.Thread): 
    def __init__(self): 
     super(IconThread, self).__init__() 
     # ... 

    def run(self) : 
     # ... 

     # Need to update the GUI 
     invoke_in_main_thread(self.item.setIcon, icon) 

Creo que algo así es bastante agradable.

Cuestiones relacionadas