4

I am trying to emit custom events in PyQt. One widget would emit and another would listen to events, but the two widgets would not need to be related.

In JavaScript, I would achieve this by doing

// Component 1
document.addEventListener('Hello', () => console.log('Got it'))

// Component 2
document.dispatchEvent(new Event("Hello"))

Edit: I know about signals and slots, but only know how to use them between parent and child. How would I this mechanism (or other mechanism) between arbitrary unrelated widgets?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Andrei Cioara
  • 3,404
  • 5
  • 34
  • 62
  • I know about signals and slots, but only know how to use them between parent and child. How would I use them between arbitrary, unrelated widgets? – Andrei Cioara Apr 06 '19 at 21:35
  • 1
    What are you actually trying to achieve by doing this? What specific problem(s) are you trying to solve? Sending custom events is possible in qt, but it is very rare that such functionality is needed. It's also difficult to understand how you could only know how to use signals and slots between parent and child. – ekhumoro Apr 06 '19 at 22:08
  • I have a Widget A which contains a button. When I press that button, I want a light to turn green. The light is very far away from the button (hierarchically). I would have to go Button -> Widget A -> Parent -> Parent -> Child -> Child -> Child -> Child -> Light. – Andrei Cioara Apr 06 '19 at 22:15
  • Maybe I am mistaking by trying to relate to knowledge I already have. In React, I'd just update the Redux Store from Button and subscribe to the state changes from light. How should I think about it in Qt? – Andrei Cioara Apr 06 '19 at 22:16
  • Simply connect the button's clicked signal to a slot which changes the colour of the light. The hierarchy of the widgets should be irrelevant. If it's not, you probably need to restructure your classes. Can't really say any more than that without seeing some actual code. – ekhumoro Apr 06 '19 at 23:44
  • For me the hierarchy of classes mimics the hierarchy of the widgets. So for my example above, I'd have to jump through 9 classes to get from child to parent. I am not sure what you mean when you mention that the hierarchy of widgets should be irrelevant? – Andrei Cioara Apr 07 '19 at 00:09
  • If you are having to "jump through 9 classes" to access widgets, that is a pretty obvious code smell. It sounds like you've tried to create a complicated DOM-like structure with lots of small, nested classes. But many (most?) pyqt applications are written using a single central class along with a few subsidiary classes which are made accessible as instance attributes - so there's no real hierarchy at all. This makes it very easy to link up the gui elements using signals and slots and/or event-filters. As the [Zen](https://www.python.org/dev/peps/pep-0020/) says: flat is better than nested. – ekhumoro Apr 07 '19 at 04:57

1 Answers1

5

In PyQt the following instruction:

document.addEventListener('Hello', () => console.log('Got it'))

is equivalent

document.hello_signal.connect(lambda: print('Got it'))

In a similar way:

document.dispatchEvent(new Event("Hello"))

is equivalent

document.hello_signal.emit()

But the big difference is the scope of the "document" object, since the connection is between a global element. But in PyQt that element does not exist.

One way to emulate the behavior that you point out is by creating a global object:

globalobject.py

from PyQt5 import QtCore
import functools

@functools.lru_cache()
class GlobalObject(QtCore.QObject):
    def __init__(self):
        super().__init__()
        self._events = {}

    def addEventListener(self, name, func):
        if name not in self._events:
            self._events[name] = [func]
        else:
            self._events[name].append(func)

    def dispatchEvent(self, name):
        functions = self._events.get(name, [])
        for func in functions:
            QtCore.QTimer.singleShot(0, func)

main.py

from PyQt5 import QtCore, QtWidgets
from globalobject import GlobalObject


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        button = QtWidgets.QPushButton(text="Press me", clicked=self.on_clicked)
        self.setCentralWidget(button)

    @QtCore.pyqtSlot()
    def on_clicked(self):
        GlobalObject().dispatchEvent("hello")


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        GlobalObject().addEventListener("hello", self.foo)
        self._label = QtWidgets.QLabel()
        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(self._label)

    @QtCore.pyqtSlot()
    def foo(self):
        self._label.setText("foo")


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w1 = MainWindow()
    w2 = Widget()
    w1.show()
    w2.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241