PyQT5: how to add custom signal and slot?

Viewed 59

i want to change slider position, when the self.factor is changed during set_parameter(). More precisely, how to update slider position as the value changes during computation?

from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QSlider, QShortcut
from PyQt5.QtGui import QKeySequence
class Params():
    def __init__(self):
        self.factor = 0

    def get_parameter(self):
            return self.factor
        
    def set_parameter(self, val):
            self.factor =val

class SliderWidget(QWidget):
    def __init__(self, param: Params):
        QWidget.__init__(self)
        self.params = param

        layout = QVBoxLayout()
        self.setLayout(layout)

        self.slider = QSlider()
        self.slider.valueChanged.connect(self.on_param_change)
        layout.addWidget(self.slider)

        self.slider.setMinimum(4)
        self.slider.setMaximum(8)
    
    def on_param_change(self):
        value = self.params.get_parameter()
        self.slider.setValue(value)
        

class Window(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.box = QVBoxLayout()
        self.params = Params()
        self.scene = SliderWidget(self.params)
        self.setCentralWidget(self.scene)
        self.shortcutUp = QShortcut(QKeySequence('Ctrl+W'), self)
        self.shortcutDw = QShortcut(QKeySequence('Ctrl+S'), self)

        self.shortcutUp.activated.connect(self.up)
        self.shortcutDw.activated.connect(self.down)

    def up(self):
        print('up')
        value = (self.params.get_parameter() + 1)
        self.params.set_parameter(value)

    def down(self):
        print('down')
        value = (self.params.get_parameter() -1)
        self.params.set_parameter(value)

import sys
if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Window()
    win.show()
    app.exec_()

2 Answers

You need to go through threading.

There is an exact procedure to achieve threading using PyQt5. If you are not following it correctly, the application will crash without any error messages.

You will need the QThread lib and create workers to overwrite run method of QThread.

Here some link describing this:

Here is the code with buttons:

from PyQt5.QtCore import QObject, QThread, pyqtSignal
class Worker(QObject):  # Step 1: Create a worker class
    finished = pyqtSignal()
    progress = pyqtSignal(int)

    def run(self):
        """Long-running task."""
        for i in range(5):
            sleep(1)
            self.progress.emit(i + 1)
        self.finished.emit()

class Window(QMainWindow):
    # Snip...
    def runLongTask(self):
        # Step 2: Create a QThread object
        self.thread = QThread()
        # Step 3: Create a worker object
        self.worker = Worker()
        # Step 4: Move worker to the thread
        self.worker.moveToThread(self.thread)
        # Step 5: Connect signals and slots
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.worker.progress.connect(self.reportProgress)
        # Step 6: Start the thread
        self.thread.start()

        # Final resets
        self.longRunningBtn.setEnabled(False)
        self.thread.finished.connect(
            lambda: self.longRunningBtn.setEnabled(True)
        )
        self.thread.finished.connect(
            lambda: self.stepLabel.setText("Long-Running Step: 0")
        )

Using PySide2 (which is basically the same as PyQt5), I created this minor example. It displays a single QSlider. If you press Ctrl+W or Ctrl+S, the QShortcut is activated and it respectively increases or decreases the QSlider by +1 or -1.

Here's the script:

from PySide2.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QSlider, QShortcut
from PySide2.QtGui import QKeySequence

class Scene(QWidget):
    def __init__(self):
        QWidget.__init__(self)

        layout = QVBoxLayout()
        self.setLayout(layout)

        self.slider = QSlider()
        layout.addWidget(self.slider)

        self.slider.setMinimum(4)
        self.slider.setMaximum(8)

class Window(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.scene = Scene()
        self.setCentralWidget(self.scene)
        self.shortcutUp = QShortcut(QKeySequence('Ctrl+W'), self)
        self.shortcutDw = QShortcut(QKeySequence('Ctrl+S'), self)

        self.shortcutUp.activated.connect(self.up)
        self.shortcutDw.activated.connect(self.down)

    def up(self):
        print('up')
        self.scene.slider.setValue(self.scene.slider.value()+1)

    def down(self):
        print('down')
        self.scene.slider.setValue(self.scene.slider.value()-1)

if __name__ == '__main__':
    app = QApplication()
    win = Window()
    win.show()
    app.exec_()

I used a QShortcut so I didn't have to create a menubar, and set a shortcut there. It makes the exmaple smaller but with the same behaviour.

Related