PyQt 信号和插槽:“新风格”发射?

Posted

技术标签:

【中文标题】PyQt 信号和插槽:“新风格”发射?【英文标题】:PyQt Signals and Slots: "new style" emit? 【发布时间】:2017-02-24 11:25:44 【问题描述】:

我正在努力思考如何正确使用PyQt5Python3 的线程和信号,但不知何故无法理解这一切是如何工作的。我在这里找到了一个示例代码,现在正试图让它在 PyQt5 中工作。

这里是gui文件ui.py

from PyQt5 import QtCore, QtWidgets

class Ui_Win(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(416, 292)
        self.centralWidget = QtWidgets.QWidget(MainWindow)
        self.centralWidget.setObjectName("centralWidget")
        MainWindow.setCentralWidget(self.centralWidget)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_Win()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

这里是主脚本test_slotting.py

from ui import Ui_Win
from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import pyqtSlot
import time

class GenericThread(QtCore.QThread):
    def __init__(self, parent=None):
        QtCore.QThread.__init__(self, parent)

    def __del__(self):
        self.quit()
        self.wait()

    def run(self):
        #Do all your heavy processing here
        #I'll just wait for 2 seconds
        time.sleep(2)
        self.emit(QtCore.pyqtSignal('itemSelectionChanged()'))
        return


class MainUI(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self)
        self.ui = Ui_Win()
        self.ui.setupUi(self)
        self.ui.List1 = QtWidgets.QListWidget(self)
        self.ui.List2 = QtWidgets.QListWidget(self)

        hbox = QtWidgets.QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(self.ui.List1)
        hbox.addWidget(self.ui.List2)

        self.ui.centralWidget.setLayout(hbox)

        self.ui.List1.addItems(['alpha','beta','gamma','delta','epsilon'])
        self.ui.List2.addItems(['Item1','Item2'])

        self.ui.List1.itemSelectionChanged.connect(self.start_heavy_processing_thread)

    @pyqtSlot()
    def start_heavy_processing_thread(self):
        genericThread = GenericThread(self)
        # self.connect(genericThread, QtCore.SIGNAL("itemSelectionChanged()"), self.fill_List2 )
        genericThread.itemSelectionChanged.connect(self.fill_List2)
        genericThread.start()

    def fill_List2(self):
        self.ui.List2.clear()
        list1SelectedItem = str(self.ui.List1.currentItem().text())
        self.ui.List2.addItem(list1SelectedItem)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = MainUI()
    MainWindow.show()
    sys.exit(app.exec_())

从最初的代码示例,我不得不改变“旧样式”

self.connect(genericThread, QtCore.SIGNAL("itemSelectionChanged()"), self.fill_List2 )

到“新风格”

self.ui.List1.itemSelectionChanged.connect(self.start_heavy_processing_thread)

但是,现在我收到了以下AttributeError: 'GenericThread' object has no attribute 'itemSelectionChanged'。我猜test_slotting.py 的这条线仍然是“旧式”:

self.emit(QtCore.pyqtSignal('itemSelectionChanged()'))

但它的新风格版本是什么?任何帮助将不胜感激......

【问题讨论】:

【参考方案1】:

这个错误在意料之中。您的 GenericThread 没有名为 itemSelectionChanged() 的信号,因此您无法将不存在的信号连接到插槽。这个信号属于你的QListWidget,而不是你的GenericThread

我建议您在决定创建自定义的之前阅读更多关于QThread 的工作原理。特别是如果您的线程与您实际执行“繁重处理”的插槽和信号一起使用,由于QThread 的性质,您将被搞砸 - 只有run() 内部的内容实际上在单独的线程中运行。其余的(插槽、信号、类成员等)属于您从其中产生自己的 QThread 的线程 - 在您的情况下,这是主线程。

我上传了一个使用 PyQt 和线程 here 的示例,您可以在其中看到通常是如何完成的。我也在那里使用新样式。


关于新样式的简短说明:

假设您有一个QWidget 派生类,其中有一个名为QPushButtonbutton 和一个槽do_work(),您希望在单击按钮时触发它。为了建立此连接,您必须执行以下操作:

self.button.clicked.connect(self.do_work)
      |        |              |    |
      |        |              |    |
      |   signal of emitter   |  slot of receiver
      |                       |
  signal emitter            signal receiver

在这种情况下,self 是接收器,因此我们使用self.do_work 表示插槽do_work 属于self,它是由button 发出的信号clicked 必须降落的地方。

PS:我注意到您尝试使用装饰器,但只是部分使用。如果你想使用这些(我强烈建议这样做),你需要在你的 fill_List2() 函数中添加一个插槽装饰器。

【讨论】:

感谢您的详尽解释。在GenericThread 上使用itemSelectionChanged() 而不是QListWidget,这是一个愚蠢的错误,对不起。我仍然需要在QThread 上阅读大量内容,谢谢提示。感谢代码示例,也将更详细地检查它。我设法将某事修补在一起并使我的示例运行。但是,仍然有一些我不太了解的部分(代码中的 cmets,如果您愿意,请随时提供帮助 :))。再次感谢您的帮助! 哦,P.S.:插图真的超级有用! 很高兴能帮上忙。正如我在回答中所写 - 请查看我使用线程的 git 存储库。它应该足以让您更好地掌握这一切。【参考方案2】:

更新后的代码

ui.py:

 from PyQt5 import QtWidgets


    class Ui_Win(object):
        def setupUi(self, MainWindow):
            MainWindow.setObjectName("MainWindow")
            MainWindow.resize(416, 292)
            self.centralWidget = QtWidgets.QWidget(MainWindow)
            self.centralWidget.setObjectName("centralWidget")
            MainWindow.setCentralWidget(self.centralWidget)

            self.List1 = QtWidgets.QListWidget()
            self.List2 = QtWidgets.QListWidget()

            self.hbox = QtWidgets.QHBoxLayout()
            self.hbox.addStretch(1)
            self.hbox.addWidget(self.List1)
            self.hbox.addWidget(self.List2)

            self.centralWidget.setLayout(self.hbox)


    if __name__ == "__main__":
        import sys
        app = QtWidgets.QApplication(sys.argv)
        MainWindow = QtWidgets.QMainWindow()
        ui = Ui_Win()
        ui.setupUi(MainWindow)
        MainWindow.show()
        sys.exit(app.exec_())

test_slotting.py:

from ui import Ui_Win
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject

class Trigger(QObject):  # --> or QtCore.QThread? What's the difference?
    trigger = pyqtSignal()

    def connect_emit(self, pressed_item, list_to_update):
        self.pressed_item = pressed_item  # --> this kind of looks ugly to me... hmmm...
        self.list_to_update = list_to_update  # --> this kind of looks ugly to me... hmmm...

        self.trigger.connect(self.run)
        self.trigger.emit()

    def run(self):
        self.list_to_update.clear()
        self.list_to_update.addItem(self.pressed_item)


class MainUI(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self)
        self.ui = Ui_Win()
        self.ui.setupUi(self)

        self.ui.List1.addItems(['alpha', 'beta', 'gamma', 'delta', 'epsilon'])
        self.ui.List2.addItems(['Item1', 'Item2'])

        self.ui.List1.itemSelectionChanged.connect(self.start_heavy_processing_thread)

    @pyqtSlot()  # --> what does this actually do? code also works without it...
    def start_heavy_processing_thread(self):
        genericThread = Trigger()
        myitem = [str(x.text()) for x in self.ui.List1.selectedItems()][0]
        mylist = self.ui.List2
        genericThread.connect_emit(myitem, mylist)


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = MainUI()
    MainWindow.show()
    sys.exit(app.exec_())

【讨论】:

以上是关于PyQt 信号和插槽:“新风格”发射?的主要内容,如果未能解决你的问题,请参考以下文章

PyQt5 线程、信号和插槽。连接错误

在循环中连接 PyQt4 中的插槽和信号

PyQt 和 QML:如何在一个插槽或函数中处理多个信号

PyQt4发射信号

网址未使用不同类中的信号和插槽 PyQt5 定义

PyQt5 数据在字节类型的信号发射期间丢失