dliv
dliv

Reputation: 685

PyQt Signals and Slots: "new style" emit?

I'm trying to wrap my head around how to correctly use threads and signals with PyQt5 and Python3, but somehow don't manage to understand how this all works. I found a sample code here and am now trying to make it work in PyQt5.

Here is the gui file 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_())

And here is the main script 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_())

From the initial code example, I had to change "old style"

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

to "new style"

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

However, now I'm getting the following AttributeError: 'GenericThread' object has no attribute 'itemSelectionChanged'. I guess this line from test_slotting.py is still "old style":

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

But what would be the new style version of it? Any help would be much appreciated...

Upvotes: 1

Views: 1200

Answers (2)

dliv
dliv

Reputation: 685

The updated code

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

Upvotes: 0

rbaleksandar
rbaleksandar

Reputation: 9701

The error is quite expected. Your GenericThread has no signal named itemSelectionChanged() hence you cannot connect a non-existing signal to a slot. This signal belongs to your QListWidget and not to your GenericThread.

I would suggest to read more on how QThread works before you decide to create your custom one. Especially if that thread of yours works with slots and signals where you actually do the "heavy processing", you will get screwed due to the nature of QThread - only what's inside run() actually runs inside a separate thread. The rest (slots, signals, class members etc.) belong to the thread where you have spawned your own QThread from - in your case this is the main thread.

I have uploaded an example of working with PyQt and threads here where you can see how things are usually done. I am also using the new style there.


Short explanation about the new style:

Let's say you have a QWidget-derived class with a QPushButton in it called button and a slot do_work() that you want to trigger whenever the button is clicked. In order to establish this connection you will have to do the following:

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

In this case self is the receiver hence we use self.do_work to say that the slot do_work belongs to self and it's where the signal clicked, emitted by button, has to land.

PS: I have noticed that you try to use decorators but do that only partially. If you want to use these (I strongly recommend to do that), you need to add a slot decorator to your fill_List2() function.

Upvotes: 3

Related Questions