2012-06-12 6 views
14

Ho una GUI PySide (Qt) che genera più thread. A volte i thread devono aggiornare la GUI. Ho risolto questo nel modo seguente:PySide: modo più semplice di aggiornare la GUI da un altro thread

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() 

    # ... 

C'è un modo più semplice? Creare i segnali, i gestori e collegarli richiede poche righe di codice.

+0

Perché non stai usando 'QThread'? – Avaris

+0

Se è più semplice con un 'QThread', vorrei prendere in considerazione l'utilizzo di uno. Il problema è che il codice esistente tende spesso a usare 'threading.Thread'. – Petter

+1

È meglio, poiché 'QThread' supporta i segnali. Non avrai bisogno della tua classe 'Signaller'. Ma in fondo, la tua strada è la via. Avete bisogno di segnali e slot per comunicare tra thread e GUI. – Avaris

risposta

17

Ho iniziato a scrivere codice con PySide di recente e avevo bisogno di un equivalente del comportamento GLib.idle_add di PyGObject. Ho basato il codice sulla tua risposta (https://stackoverflow.com/a/11005204/1524507) ma questo usa gli eventi invece di usare noi stessi una coda.

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)) 

Che viene utilizzato allo stesso modo nel link di risposta sopra.

+0

Penso che questo sembra buono. – Petter

+0

Questo è fantastico. E anche la minuscola soluzione alternativa per avvolgere 'registerEventType' di nuovo in' QEvent.Type' per farlo funzionare in PySide ha aperto gli occhi. Grazie, userà il codice. – Trilarion

6

Questo è quello che ho finora. Ho scritto il seguente codice da qualche parte in un modulo helper:

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) 

Poi i miei thread possono facilmente eseguire codice per aggiornare la GUI nel thread principale. Non è necessario creare e collegare i segnali per ogni operazione.

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) 

Penso che qualcosa del genere sia abbastanza bello.