2012-06-12 7 views
14

Ich habe eine PySide (Qt) GUI, die mehrere Threads hervorbringt. Die Threads müssen manchmal die GUI aktualisieren. Ich habe das auf folgende Weise gelöst:PySide: Einfachere Möglichkeit, GUI von einem anderen Thread zu aktualisieren

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

    # ... 

Gibt es einen einfacheren Weg? Die Erstellung der Signale, Handler und deren Verbindung erfordert ein paar Zeilen Code.

+0

Warum verwenden Sie 'QThread' nicht? – Avaris

+0

Wenn es mit einem 'QThread' einfacher ist, würde ich einen verwenden. Das Problem besteht darin, dass der vorhandene Code häufig dazu tendiert, 'threading.Thread' zu verwenden. – Petter

+1

Es ist besser, da 'QThread' Signale unterstützt. Sie brauchen Ihre 'Signaller' Klasse nicht. Aber grundsätzlich ist dein Weg der richtige Weg. Sie benötigen Signale und Slots, um zwischen Threads und GUI zu kommunizieren. – Avaris

Antwort

17

Ich begann vor kurzem mit PySide zu kodieren und ich brauchte ein Äquivalent von PyGObjects GLib.idle_add Verhalten. Ich habe den Code von Ihrer Antwort (https://stackoverflow.com/a/11005204/1524507) abgeleitet, aber dieser verwendet Ereignisse, anstatt selbst eine Warteschlange zu verwenden.

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

Welche wird in der obigen Antwort Link auf die gleiche Weise verwendet.

+0

Ich denke, das sieht gut aus. – Petter

+0

Das ist großartig. Und selbst die winzige Umgehungsmöglichkeit, 'RegisterEventType' erneut in' QEvent.Type' einzupacken, um es in PySide funktionieren zu lassen, öffnete meine Augen. Danke, werde den Code verwenden. – Trilarion

6

Dies ist, was ich bisher habe. Ich schrieb den folgenden Code irgendwo in einem Helfer-Modul:

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) 

Dann können meine Fäden sehr leicht Code ausführen, um die GUI in dem Haupt-Thread zu aktualisieren. Es ist nicht notwendig, Signale für jede Operation zu erstellen und zu verbinden.

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) 

Ich denke, so etwas ist ziemlich nett.