PyQt proper use of emit() and pyqtSignal()
Asked Answered
G

3

44

I am reading through some documentation on PyQt5 to come up with a simple signal-slot mechanism. I have come to a halt due to a design consideration.

Consider the following code:

import sys
from PyQt5.QtCore import (Qt, pyqtSignal)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
    QVBoxLayout, QApplication)


class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()

    def printLabel(self, str):
        print(str)

    def logLabel(self, str):
        '''log to a file'''
        pass

    def initUI(self):

        lcd = QLCDNumber(self)
        sld = QSlider(Qt.Horizontal, self)

        vbox = QVBoxLayout()
        vbox.addWidget(lcd)
        vbox.addWidget(sld)

        self.setLayout(vbox)

        #redundant connections
        sld.valueChanged.connect(lcd.display)
        sld.valueChanged.connect(self.printLabel)
        sld.valueChanged.connect(self.logLabel)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')
        self.show()


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

To track the changes made to the slider, I simply print and log the changes made. What I do not like about the code is that I am required to call the sld.valueChanged slot thrice to send the same information to 3 different slots.

Is it possible to create my own pyqtSignal that sends an integer to a single slot function. And in turn have the slot function emit the changes that need to be made?

  • Maybe I don't fully understand the purpose of emit() because there are no good examples of it's purpose in the PyQt Signal-Slot docs. All we're given is an example of how to implement an emit with no parameters.

What I would like to do is create a function that handles the emit function. Consider the following:

import sys
from PyQt5.QtCore import (Qt, pyqtSignal)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
    QVBoxLayout, QApplication)


class Example(QWidget):

    def __init__(self):
        super().__init__()

        #create signal
        self.val_Changed = pyqtSignal(int, name='valChanged')

        self.initUI()

    def initUI(self):

        lcd = QLCDNumber(self)
        sld = QSlider(Qt.Horizontal, self)

        vbox = QVBoxLayout()
        vbox.addWidget(lcd)
        vbox.addWidget(sld)

        self.setLayout(vbox)

        sld.val_Changed.connect(self.handle_LCD)
        self.val_Changed.emit()

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')
        self.show()

    def handle_LCD(self, text):
        '''log'''
        print(text)
        '''connect val_Changed to lcd.display'''

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

There are obviously some serious design flaws here. I cannot wrap my head around the order of function calls. And I am not implementing pyqtSignal correctly. I do however believe that correctly stating the following 3 points will help me produce a proper app:

  1. For a predefined signal: send the signal to the slot function. Slot can be reimplemented to use the signal values.
  2. Produce pyqtSignal object with some parameters. It is not yet clear what the purpose of these parameters are and how they differ from 'emit' parameters.
  3. emit can be reimplemented to send specific signal values to the slot function. It is also not yet clear why I would need to send different values from previously existing signal methods.

Feel free to completely alter the code for what I am trying to do because I have not yet figured out if its in the realm of good style.

Geri answered 5/4, 2016 at 18:51 Comment(0)
T
63

You can define your own slot (any python callable) and connect that to the signal, then call the other slots from that one slot.

class Example(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def printLabel(self, str):
        print(str)

    def logLabel(self, str):
        '''log to a file'''
        pass

    @QtCore.pyqtSlot(int)
    def on_sld_valueChanged(self, value):
        self.lcd.display(value)
        self.printLabel(value)
        self.logLabel(value)

    def initUI(self):

        self.lcd = QLCDNumber(self)
        self.sld = QSlider(Qt.Horizontal, self)

        vbox = QVBoxLayout()
        vbox.addWidget(self.lcd)
        vbox.addWidget(self.sld)

        self.setLayout(vbox)
        self.sld.valueChanged.connect(self.on_sld_valueChanged)


        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')

Also, if you want to define your own signals, they have to be defined as class variables

class Example(QWidget):
    my_signal = pyqtSignal(int)

The arguments to pyqtSignal define the types of objects that will be emit'd on that signal, so in this case, you could do

self.my_signal.emit(1)

emit can be reimplemented to send specific signal values to the slot function. It is also not yet clear why I would need to send different values from previously existing signal methods.

You generally shouldn't be emitting the built in signals. You should only need to emit signals that you define. When defining a signal, you can define different signatures with different types, and slots can choose which signature they want to connect to. For instance, you could do this

my_signal = pyqtSignal([int], [str])

This will define a signal with two different signatures, and a slot could connect to either one

@pyqtSlot(int)
def on_my_signal_int(self, value):
    assert isinstance(value, int)

@pyqtSlot(str)
def on_my_signal_str(self, value):
    assert isinstance(value, str)

In practice, I rarely overload signal signatures. I would normally just create two separate signals with different signatures rather than overloading the same signal. But it exists and is supported in PyQt because Qt has signals that are overloaded this way (eg. QComboBox.currentIndexChanged)

Tensible answered 5/4, 2016 at 19:25 Comment(8)
It seems that commenting out @pyqtSlot(int) does not effect the output. Why is this the case?Geri
It seems that the true purpose of the decorator is too allow for overloading purposes.Geri
It's not just for overloading. This answer explains it pretty well. If you're ever sending signals across threads, it requires the slot decorator, too.Tensible
A simple side note: because my_signal is an attribute of the class and not associated with any QWidget. What is good Qt style for creating something like self.lcd.my_signal.connect(on_my_signal).Geri
If you want to add a custom signal to the LCD widget, you'll need to subclass it and add it as a class attribute. You also shouldn't emit the signal from anywhere but within the class, so it doesn't really make sense to add signals to non-subclassed widgetsTensible
AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'emit'Huntley
@lone_coder You may want to post your code as a new question if you're having problems.Tensible
@Brendan Abel Can you answer this question? #65738642 It's similiar to this one, but I still don't know how to deal with my problemLatrell
V
0

the accepted answer, was tricky to me to undestand because of the use of the QSlider built in signal valueChanged() I didn't know about https://doc.qt.io/qtforpython/PySide6/QtWidgets/QSlider.html, so I added my own signal that is emitted when self.sld.valueChanged.connect(self.on_sld_valueChanged) is called, to have an example of how to create my own pyqtSignal. the Post title: "PyQt proper use of emit() and pyqtSignal()" was misleading to me , I am trying to understand signals and slots too, so I wanted to add emit() to the code. I know its not the way to go forward but just to figure out how it works:

import sys

from PyQt5 import QtCore

from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
    QVBoxLayout, QApplication)

class Example(QWidget):

    #create signal
    val_Changed = pyqtSignal(int, str, name='valChanged')
    
    def __init__(self):
        super().__init__()
        self.initUI()
        
        
        self.valChanged.connect(self.mine)
        
        self.show()

    def printLabel(self, str):
        print(str)

    def logLabel(self, str):
        '''log to a file'''
        pass


    @QtCore.pyqtSlot(int, str)
    def mine(self, value, string):
        self.lcd.display(value)
        self.printLabel((str(value)+' '+ string))
        self.logLabel(value)
        
        
    def on_sld_valueChanged(self, value):
        
        self.val_Changed.emit(value, 'using-slider')


    def initUI(self):

        self.lcd = QLCDNumber(self)
        self.sld = QSlider(Qt.Horizontal, self)

        vbox = QVBoxLayout()
        vbox.addWidget(self.lcd)
        vbox.addWidget(self.sld)

        self.setLayout(vbox)
        self.sld.valueChanged.connect(self.on_sld_valueChanged)


        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')
        
if __name__ ==  '__main__' :

    app = QApplication( sys.argv ) 
    ex = Example() 
    
    sys.exit(app.exec_( ))

I'll wait to somebody that could show me how to reimplement QSlider valueChanged() signal

Varney answered 10/6, 2022 at 9:38 Comment(0)
I
0

Example should be simple:

from PyQt6.QtCore import QObject,pyqtSignal,Qt,pyqtSlot
class Test(QObject): # All Qt widgets inherit QObject.
    signal=pyqtSignal(int)
    @pyqtSlot(int)
    def slot2(self,i):
        print('slot2',i)
@pyqtSlot(int)
def slot1(i):
    print('slot1',i)
test=Test()
test2=Test()
test.signal.connect(slot1)
test.signal.emit(1)
test.signal.connect(test.slot2,type=Qt.ConnectionType.AutoConnection)
test.signal.connect(test2.slot2)
test.signal.emit(2)
test.signal.disconnect(slot1)
test.signal.disconnect(test2.slot2)
test.signal.emit(3)

result:

slot1 1
slot1 2
slot2 2
slot2 2
slot2 3

https://doc.qt.io/qtforpython-6/PySide6/QtCore/Qt.html#PySide6.QtCore.Qt.ConnectionType

Ithaman answered 21/4, 2024 at 8:55 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.