Я пытаюсь создавать пользовательские события в PyQt. Один виджет будет излучать, а другой прослушивать события, но два виджета не должны быть связаны.
В JavaScript я бы добился этого, выполнив
// Component 1
document.addEventListener('Hello', () => console.info('Got it'))
// Component 2
document.dispatchEvent(new Event("Hello"))
Редактировать: я знаю о сигналах и слотах, но знаю только, как использовать их между родителем и дочерним элементом. Как бы мне этот механизм (или другой механизм) между произвольными несвязанными виджетами?
Чего вы на самом деле пытаетесь достичь, делая это? Какие конкретные проблемы вы пытаетесь решить? Отправка пользовательских событий возможна в qt, но такая функциональность требуется очень редко. Также трудно понять, как вы могли знать только, как использовать сигналы и слоты между родителем и потомком.
У меня есть виджет A, который содержит кнопку. Когда я нажимаю эту кнопку, я хочу, чтобы свет стал зеленым. Свет очень далеко от кнопки (иерархически). Я должен был бы пойти Кнопка -> Виджет A -> Родитель -> Родитель -> Дочерний -> Дочерний -> Дочерний -> Дочерний -> Свет.
Может быть, я ошибаюсь, пытаясь соотнести знания, которые у меня уже есть. В React я бы просто обновлял Redux Store из Button и подписывался на изменения состояния от света. Как я должен думать об этом в Qt?
Просто подключите сигнал нажатия кнопки к слоту, который меняет цвет света. Иерархия виджетов не должна иметь значения. Если это не так, вам, вероятно, нужно реструктурировать свои классы. Не могу сказать больше, не видя реального кода.
Для меня иерархия классов имитирует иерархию виджетов. Итак, для моего примера выше мне пришлось бы перепрыгнуть через 9 классов, чтобы перейти от дочернего к родительскому. Я не уверен, что вы имеете в виду, когда упоминаете, что иерархия виджетов не имеет значения?
Если вам приходится «прыгать через 9 классов», чтобы получить доступ к виджетам, это довольно очевидный запах кода. Похоже, вы пытались создать сложную DOM-подобную структуру с множеством небольших вложенных классов. Но многие (большинство?) приложений pyqt написаны с использованием одного центрального класса вместе с несколькими дочерними классами, которые доступны как атрибуты экземпляра, поэтому реальной иерархии нет вообще. Это позволяет очень легко связать элементы графического интерфейса с помощью сигналов, слотов и/или фильтров событий. Как говорит дзен: плоский лучше, чем вложенный.
В PyQt следующая инструкция:
document.addEventListener('Hello', () => console.info('Got it'))
эквивалентен
document.hello_signal.connect(lambda: print('Got it'))
Подобным образом:
document.dispatchEvent(new Event("Hello"))
эквивалентен
document.hello_signal.emit()
Но большая разница заключается в области действия объекта «документ», поскольку связь осуществляется между глобальным элементом. Но в PyQt этого элемента нет.
Один из способов эмулировать указанное вами поведение — создать глобальный объект:
глобальный объект.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, как мы можем передать параметры функции, используя этот подход?
Как насчет использования QApplication
в качестве глобального объекта? Вы можете получить к нему доступ в любом месте, перейдя QApplication.instance()
...
Я знаю о сигналах и слотах, но знаю только, как использовать их между родителем и потомком. Как бы я использовал их между произвольными, несвязанными виджетами?