Слоты и сигналы
Contents
Слоты и сигналы#
Наряду с событиями Qt
поддерживает механизм слотов и сигналов, которые позволяют общаться Qt
виджетам (и не только) общаться между собой.
Грубо говоря, принцип механизма слотов и сигналов можно объяснить на аналогии с тем, как работает искусственное освещение в помещениях. Результатом нажатия на выключатель (сигнал) Вы получаете включение (или выключение) лампы (слот).
В качестве другого примера непосредственно связанного с разработкой графического интерфейса можно привести нажатие на кнопку (например, QPushButton
): нажатие на кнопку (click
) — сигнал, а слот — то, что происходит при нажатии на кнопку.
У любого объекта QObject
(или производного) могут быть сигналы. Сигналы излучаются, когда такой объект претерпевает какие-то изменения, которые могут быть интересны другим объектам. Излучение сигналов — единственное, что объект делает, для того чтобы общаться. Излучая сигнал, объект не знает, принимает ли кто-то этот сигнал или нет.
Все встроенные виджеты Qt
предоставляют набор подходящих сигналов, но можно создавать и пользовательские. В качестве слота подходит любая python
функция (Callable
).
Нажатие на кнопку. Сигнал clicked
#
В качестве самого простого примера создадим кнопку QPushButton и сделаем так, чтобы при нажатии на неё в консоли выводилось сообщение.
Если бы мы пошли путем обработки событий, то нам бы пришлось наследовать от класса QPushButton
и перегрузить в нем обработчик событий. При этом обработать все это адекватно может быть не так уж и легко: пользователь можно нажать на мышку, увести курсор за пределы кнопки, и отпустить — считать ли это за нажатие на кнопку?
Использование сигналов значительно упрощает обработку такого сценария. У QPushButton
(а вообще говоря, у его базового класса QPushButton) есть сигнал clicked, который соответствует полноценному нажатию. При этом даже необязательно нажатие на кнопку должно осуществится, как результат взаимодействия пользователя с мышкой: программа сама может нажать на кнопку (метод click) или пользователь может нажать на пробел на клавиатуре, когда кнопка активна, сигнал clicked
излучится и в этих случаях тоже.
Чтобы при нажатии на кнопку в консоли появлялось сообщение, необходимо соединить этот сигнал с соответствующим слотом, которым в данном случае будет являться функция, вызывающая в себе print
. Соединение производится методом сигнала connect
, которому в качестве аргумента передаётся слот.
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
def on_button_click():
print("Button was pressed")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
button = QPushButton("Press me!", parent=self)
button.clicked.connect(on_button_click)
self.setCentralWidget(button)
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()
Рассмотрим детальнее этот пример.
button = QPushButton("Press me!", parent=self)
button.clicked.connect(on_button_click)
Первая из этих инструкций создаёт кнопку, а вторая из них соединяет сигнал clicked
с функцией on_button_click
:
def on_button_click():
print("Button was pressed")
В итоге, при нажатии на кнопку излучается сигнал clicked
, что приводит к вызову функции on_button_click
.
Несколько сигналов и слотов#
Дополнительным преимуществом сигналов и слотов перед событиями является то, что не только один и тот же сигнал можно соединять с произвольным количеством слотов, но и один и тот же слот может быть соединен с произвольным количеством сигналов.
В качестве примера создадим окно с тремя кнопками и двумя текстовыми виджетами, подсчитывающими нажатия на кнопки следующим образом: нажатие на левую кнопку увеличивает только левый счетчик, нажатие на правую — только правый счетчик, а нажатие на центральную кнопку — сразу обоих.
Так как нам нужно два текстовых виджета, которые умеют считать, то реализуем их в виде класса. Расширим класс QLabel
, чтобы он хранил в себе счетчик, а также его увеличение методом increment
.
class CountingLabel(QLabel):
def __init__(self, text):
super().__init__(f"{text}\n{0:3d}")
self.text = text
self.setAlignment(Qt.AlignCenter)
self.counter = 0
def increment(self):
self.counter += 1
self.setText(f"{self.text}\n{self.counter:3d}")
Теперь необходимо создать окно с тремя кнопками, двумя такими текстовыми виджетами и соединить сигналы кнопок со слотами increment
счетчиков по следующей схеме.
left_button.clicked.connect(left_label.increment)
middle_button.clicked.connect(left_label.increment)
middle_button.clicked.connect(right_label.increment)
right_button.clicked.connect(right_label.increment)
Т.е. сигнал средней кнопки соединяется и со слотом левой кнопки и со слотом правой кнопки.
Следующий код воспроизводит и желаемый функционал и внешний вид на картинке выше.
import sys
from PySide6.QtGui import Qt
from PySide6.QtWidgets import *
class CountingLabel(QLabel):
def __init__(self, text):
super().__init__(f"{text}\n{0:3d}")
self.text = text
self.setAlignment(Qt.AlignCenter)
self.counter = 0
def increment(self):
self.counter += 1
self.setText(f"{self.text}\n{self.counter:3d}")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# top row
left_label = CountingLabel("Left")
right_label = CountingLabel("Right")
# bottom row
left_button = QPushButton("Increment left")
middle_button = QPushButton("Increment both")
right_button = QPushButton("Increment right")
# layout
main_layout = QVBoxLayout()
top_layout = QHBoxLayout()
top_layout.addWidget(left_label)
top_layout.addWidget(right_label)
main_layout.addLayout(top_layout)
bottom_layout = QHBoxLayout()
bottom_layout.addWidget(left_button)
bottom_layout.addWidget(middle_button)
bottom_layout.addWidget(right_button)
main_layout.addLayout(bottom_layout)
# connections
left_button.clicked.connect(left_label.increment)
middle_button.clicked.connect(left_label.increment)
middle_button.clicked.connect(right_label.increment)
right_button.clicked.connect(right_label.increment)
widget = QWidget()
widget.setLayout(main_layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()
Встроенные слоты#
У многих Qt
виджетов есть предусмотренные разработчиками слоты. Например, упомянутый уже click
у QPushButton
. Более естественный пример — слот clear у текстового виджета QLineEdit: QLineEdit
позволяет вводить строку текста, а метод clear
очищает её.
Следующий код создаёт текстовое поле и кнопку, нажатие на которое очищает текстовое поле.
import sys
from PySide6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# widgets
line_edit = QLineEdit()
clear_button = QPushButton("Clear")
clear_button.clicked.connect(line_edit.clear)
# layout
layout = QHBoxLayout()
layout.addWidget(line_edit)
layout.addWidget(clear_button)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()