Home > Mobile >  python - PyQt5 Emits Signal only in the class that the emit statement is in (Intended behaviour?)
python - PyQt5 Emits Signal only in the class that the emit statement is in (Intended behaviour?)

Time:06-10

I've been racking my brain over this issue for a week now. Whenever I emit a signal it seems to stay local to the class where it is called in.

My objective is to emit a signal in one class and receive it in multiple. Basically im trying to a emit a signal to another class to carry over data.

My problem might stem from using QStackedWidget, every single stackoverflow thread or youtube video I have seen about signals/slots has not used QStackedWidget.

Below is my code, it showcases that even though I emit to the same signal, the slot is only activated when the signal is in the class where the slot is.

from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5 import QtWidgets
from PyQt5.QtCore import pyqtSignal, QObject
import sys


# Class that holds the signal
class communication(QObject):
    GotFilePath = pyqtSignal(str)


# Class for first page
class DataLoadedPage(QWidget):

    # Initilize
    def __init__(self):
        super(DataLoadedPage, self).__init__()
        # import signal class
        self.communication = communication()

        self.initUI()

    def initUI(self):
        # Label
        self.label = QtWidgets.QLabel(self)
        self.label.setText("First page")
        self.label.move(50,50)
        
        # Button
        self.b1 = QtWidgets.QPushButton(self)
        self.b1.setText('Next screen')


        # Connect to signal from signal class and print which page its on
        self.communication.GotFilePath.connect(self.somethingrandom)

        # Connect button to emitter and change page
        self.b1.clicked.connect(self.mouseclick)



    def somethingrandom(self, emitted):
      print('First') # <------------- Difference

    def mouseclick(self):
        self.communication.GotFilePath.emit("Emitted")
        widget.setCurrentIndex(widget.currentIndex() 1) # Go to second page



# Class for second page
class DataLoadedPage2(QWidget):

    # Initilize
    def __init__(self):
        super(DataLoadedPage2, self).__init__()

        # import signal class
        self.communication = communication()

        self.initUI()

    def initUI(self):
        # Label
        self.label = QtWidgets.QLabel(self)
        self.label.setText("Second page")
        self.label.move(50,50)

        # Button
        self.b1 = QtWidgets.QPushButton(self)
        self.b1.setText('Previous screen')



        # Connect to signal from signal class and print which page its on
        self.communication.GotFilePath.connect(self.somethingrandom)

        # Connect button to emitter and change page
        self.b1.clicked.connect(self.mouseclick)



    def somethingrandom(self, Emitted):
      print('second') # <--------------- Difference

    def mouseclick(self):
        self.communication.GotFilePath.emit("Emitted")
        widget.setCurrentIndex(widget.currentIndex()-1) # Go to first page



# define widget
app = QApplication(sys.argv)
widget = QtWidgets.QStackedWidget()

# Define pages
frontpage = DataLoadedPage()
secondpage = DataLoadedPage2()

# Add pages to widget
widget.addWidget(frontpage)
widget.addWidget(secondpage)

# Launch application
widget.show()
sys.exit(app.exec_())

Feel free to try out the code, it does function

CodePudding user response:

While signals are created as class attributes, they are actually bound to an instance of a QObject.

In your code you are creating two different instances of communication, and since you connected that instance signal to the instance of the class in which you created it, you will only get that signal.

If you want a "broadcast" signal, then you should create a single instance of the class for that signal and use it in any sender or receiver instance that will use it:

import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QPushButton, 
    QStackedWidget, QVBoxLayout)
from PyQt5.QtCore import QObject, pyqtSignal


class Communication(QObject):
    gotFilePath = pyqtSignal(str)


class DataLoadedPage(QWidget):
    def __init__(self, communication):
        super().__init__()

        self.communication = communication
        self.communication.gotFilePath.connect(self.somethingrandom)

        self.initUI()

    def initUI(self):
        self.label = QLabel('First page')
        self.b1 = QPushButton('Next screen')

        layout = QVBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.b1)

        self.b1.clicked.connect(self.mouseclick)

    def somethingrandom(self, emitted):
        print('First')

    def mouseclick(self):
        self.communication.gotFilePath.emit('Emitted')
        widget.setCurrentIndex(widget.currentIndex()   1)


class DataLoadedPage2(QWidget):
    def __init__(self, communication):
        super().__init__()

        self.communication = communication
        self.communication.gotFilePath.connect(self.somethingrandom)

        self.initUI()

    def initUI(self):
        self.label = QLabel('Second page')
        self.b1 = QPushButton('Previous screen')

        layout = QVBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.b1)

        self.b1.clicked.connect(self.mouseclick)

    def somethingrandom(self, Emitted):
        print('Second')

    def mouseclick(self):
        self.communication.gotFilePath.emit('Emitted')
        widget.setCurrentIndex(widget.currentIndex() - 1)


app = QApplication(sys.argv)
widget = QStackedWidget()

communication = Communication()
frontpage = DataLoadedPage(communication)
secondpage = DataLoadedPage2(communication)

widget.addWidget(frontpage)
widget.addWidget(secondpage)

widget.show()
sys.exit(app.exec_())

With the code above, the communication object is created outside of any class, and used as a parameter in the __init__ of each page, so that an instance attribute can be created for its reference, and finally connected to each instance method. Any time the signal is emitted, each connected ("subscribed") function will be called.

Note that this behavior is rarely needed, and you might actually need to use signals to communicate between instances.

For this, there is actually no need for a separate class for the signal, as you can add to each page class (since QWidget inherits from QObject).

In this case, since the connections are bidirectional, they have to be done after both instances are created. You either create a function in each class that actually connects the signal, or you do that externally.

class DataLoadedPage(QWidget):
    gotFilePath = pyqtSignal(str)
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.label = QLabel('First page')
        self.b1 = QPushButton('Next screen')

        layout = QVBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.b1)

        self.b1.clicked.connect(self.mouseclick)

    def somethingrandom(self, emitted):
        print('First')

    def mouseclick(self):
        self.gotFilePath.emit('Emitted')
        widget.setCurrentIndex(widget.currentIndex()   1)


class DataLoadedPage2(QWidget):
    gotFilePath = pyqtSignal(str)
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.label = QLabel('Second page')
        self.b1 = QPushButton('Previous screen')

        layout = QVBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.b1)

        self.b1.clicked.connect(self.mouseclick)

    def somethingrandom(self, Emitted):
        print('Second')

    def mouseclick(self):
        self.gotFilePath.emit('Emitted')
        widget.setCurrentIndex(widget.currentIndex() - 1)


app = QApplication(sys.argv)
widget = QStackedWidget()

frontpage = DataLoadedPage()
secondpage = DataLoadedPage2()

frontpage.gotFilePath.connect(secondpage.somethingrandom)
secondpage.gotFilePath.connect(frontpage.somethingrandom)

widget.addWidget(frontpage)
widget.addWidget(secondpage)

widget.show()
sys.exit(app.exec_())

With the code above, "Second" will be shown when the button of the first page is clicked, and vice versa.

Some further notes: 1. I took the liberty to fix some naming, style and syntax to better follow the official Style Guide for Python Code which is important for code readability, and also unified the import style (generally, you should either import individual classes or the submodule containing them, not both); 2. you should always use layout managers, as shown above; 3. you're probably following a youtube tutorial ("Code first with Hala") that is known to provide a lot of terrible suggestions and bad practices, I strongly suggest you to ignore it.

  • Related