PySide:另一个线程更新GUI的更简单方法

15

我有一个使用 PySide (Qt) 实现的 GUI,它会产生多个线程。这些线程有时需要更新 GUI。我是通过以下方式解决了这个问题:

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

    # ...

有更简单的方法吗?创建信号、处理程序并连接它们需要几行代码。


你为什么不使用QThread呢? - Avaris
如果使用QThread更容易,我会考虑使用它。问题在于现有的代码往往使用threading.Thread - Petter
1
最好使用QThread,因为它支持信号。这样你就不需要自己的Signaller类了。但基本上,你的方法也是可以的。你需要使用信号和槽来在线程和GUI之间进行通信。 - Avaris
2个回答

21

我最近开始使用PySide编码,需要一个等价于PyGObject的GLib.idle_add行为的工具。我基于你的答案编写了代码(https://dev59.com/V2gu5IYBdhLWcg3w6bGo#11005204),但这个代码使用事件而不是使用队列。

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))
与上面答案链接中使用方式相同。

这太棒了。即使是关于在QEvent.Type中再次包装registerEventType的微小解决方法也让我眼前一亮。谢谢,我会使用这段代码。 - Trilarion
@chfoo:非常感谢您!您是否同意让这个答案中的代码在开源许可条款下使用?BSD或MIT将是理想的选择,因为它们与Numpy、Pandas、Scipy等兼容。 - naitsirhc
@naitsirhc 我想这样做,但代码是基于其他答案的,所以我不确定是否可以这样做。此外,我不是律师,但代码片段非常小,我认为在源代码注释中包含指向此答案的链接作为来源是足够的做法。 - chfoo
感谢@chfoo。不幸的是,回答的默认许可证是CC BY-SA 3.0(请参见每个页面右下角)。如果没有在OSI批准的开源许可证下明确发布,则使用条款(即使是短片段)存在歧义。您的答案仍然非常有用,可以指向Qt API的正确部分! :D - naitsirhc
这太棒了!真的帮助我理解使用Pyside6进行事件处理和多线程编程的过程。 - Nicholas Stommel

7

目前为止,这就是我所拥有的。我在一个帮助模块中写下了如下代码:

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)

我的线程可以很容易地运行代码以更新主线程中的GUI界面。不需要为每个操作创建和连接信号。

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)

我认为这种方式非常不错。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接