Тема 3 из 6

Сигналы, слоты и свои сигналы

Сигналы и слоты — сердце Qt. Виджеты сообщают о событиях сигналами, а ваш код на них отвечает.

Сигналы с данными

edit.textChanged.connect(self.on_text)          # on_text(self, text)
spin.valueChanged.connect(self.on_value)        # on_value(self, value)
checkbox.toggled.connect(self.on_toggle)        # on_toggle(self, checked)

Слот получает данные сигнала аргументами. Если они не нужны — слот может их не принимать.

textChanged срабатывает и при наборе с клавиатуры, и при программном setText. У QLineEdit есть ещё textEdited — только для изменений, сделанных человеком, и returnPressed — нажатие Enter.

lambda в connect

Чтобы передать слоту свои данные, используют lambda:

for key in "0123456789":
    button = QPushButton(key)
    button.clicked.connect(lambda _=False, k=key: self.press(k))

clicked передаёт аргумент checked, поэтому первый параметр _. А k=key фиксирует текущее значение: без этого все кнопки получили бы последнее значение key.

Блокировка сигналов

Если два виджета обновляют друг друга, возникает круг: поле A меняет B, B испускает сигнал и меняет A… Разрывают круг блокировкой:

edit.blockSignals(True)
edit.setText(text)          # сигнал не испускается
edit.blockSignals(False)

Не забывайте включить сигналы обратно — иначе виджет «замолчит» навсегда. Для надёжности есть QSignalBlocker(edit) — блокирует до конца блока with.

Свои сигналы

Свои классы тоже могут испускать сигналы — так данные программы сообщают окну об изменениях, ничего не зная о самом окне:

from PySide6.QtCore import QObject, Signal


class NoteStore(QObject):
    changed = Signal(int)            # атрибут КЛАССА; int — тип данных сигнала

    def __init__(self):
        super().__init__()           # обязательно для QObject
        self.notes = []

    def add(self, text):
        self.notes.append(text)
        self.changed.emit(len(self.notes))


store = NoteStore()
store.changed.connect(lambda n: label.setText(f"Заметок: {n}"))

Испускайте сигнал, только когда что-то действительно изменилось, — иначе окно будет перерисовываться зря, а подписчики получат ложные уведомления.

Итог

  • сигналы несут данные: textChanged(str), valueChanged(int);
  • lambda _=False, k=k: … — свои данные в слот;
  • blockSignals(True/False) разрывает круги обновлений;
  • свой сигнал: Signal(тип) в классе-наследнике QObject, emit(…).