Z T
Z T

Reputation: 570

Global signaling in Pyside6?

I would like to emit a signal in my main window and somewhere deep down in one of my components I would like to receive that signal. I have read in some thread here that can be solved with a common signal class, however I cannot make it work.

I created a signal Manager class with a test signal in it:

class Manager(QObject):
  test_signal = Signal(str)
  def __init__(self):
    QObject.__init__(self)

In my main window I do this:

class MainWindow(QMainWindow):
  def __init__(self):
    super(MainWindow, self).__init__()
    self.signal_manager = SignalManager.Manager()
  def some_button_clicked(self):
    self.signal_manager.test_signal.emit('test')

Somewhere deep in one of the components:

class MyTreeWidget(QTreeWidget):
  def __init__(self, parent=None):
    QTreeWidget.__init__(self, parent)
    self.signal_manager = SignalManager.Manager()
    self.signal_manager.test_signal.connect(self.do_something)
  def do_something(self, param):
    # Do something...

So when I click my button and some_button_clicked emits the event do_something() should happen but it won't. Wondering what could be the problem here?

Upvotes: 0

Views: 1463

Answers (1)

musicamante
musicamante

Reputation: 48241

Your solution wouldn't work, because you're always creating a new instance of the manager.

Whenever I need to rely on some "global" objects in a Qt program, I usually create a subclass of the QApplication and create attributes for them, which obviously also works for "global signals".

Then you can emit the signals or connect to them by accessing the application instance():

class MyApp(QApplication):
    globalSignal = pyqtSignal(str)


class TargetLabel(QLabel):
    def __init__(self):
        super().__init__()
        QApplication.instance().globalSignal.connect(self.setText)


app = MyApp([])
window = QWidget()
layout = QVBoxLayout(window)
lineEdit = QLineEdit()
layout.addWidget(lineEdit)
layout.addWidget(TargetLabel())

lineEdit.textChanged.connect(app.globalSignal)

window.show()
app.exec()

An alternative could be to use a global QObject subclass in a separate module, using a reference that is global to the module corresponding to a unique instance (one of the rare cases for which using global actually makes sense).

A possible "hacky" implementation would be to create a function named as the signal manager, and replace its global reference with an instance of the QObject, like the following example:

globalsignal.py

from PyQt5 import QtCore

def GlobalSignalProxy():
    global GlobalSignalProxy
    if not isinstance(GlobalSignalProxy, QtCore.QObject):
        class GlobalSignalProxy(QtCore.QObject):
            signal = QtCore.pyqtSignal(str)
        # replace the function with the instance
        GlobalSignalProxy = GlobalSignalProxy()
    return GlobalSignalProxy

targetlabel.py

from PyQt5 import QtWidgets
from globalsignal import GlobalSignalProxy

class TargetLabel(QtWidgets.QLabel):
    def __init__(self):
        super().__init__()
        GlobalSignalProxy().signal.connect(self.setText)

main.py

from PyQt5 import QtWidgets
from globalsignal import GlobalSignalProxy
from targetlabel import TargetLabel

app = QtWidgets.QApplication([])
window = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(window)
lineEdit = QtWidgets.QLineEdit()
layout.addWidget(lineEdit)
lineEdit.textChanged.connect(GlobalSignalProxy().signal)
layout.addWidget(TargetLabel())
window.show()
app.exec()

The above could obviously be achieved with a singleton class too.

In any case, I wouldn't suggest this practice, as I believe that the QApplication subclass would be better for many reasons, including the fact that it's more appropriate for the OOP pattern.

Upvotes: 1

Related Questions