Обработка сигналов и событий

При взаимодействии пользователя с окном происходят события. В ответ на события система генерирует определенные сигналы. Сигналы — это своего рода извещения системы о том, что пользователь выполнил какое-либо действие или в самой системе возникло некоторое условие. Сигналы являются важнейшей составляющей приложения с графическим интерфейсом, поэтому необходимо знать, как назначить обработчик сигнала, как удалить обработчик, а также уметь правильно обработать событие. Какие сигналы генерирует тот или иной компонент мы будем рассматривать при изучении конкретного компонента.

Назначение обработчиков сигналов

Чтобы обработать какой-либо сигнал необходимо сопоставить ему метод класса, который будет вызван при наступлении события. Назначить обработчик позволяет статический метод connect() из класса QObject. Прототипы метода:

static QMetaObject::Connection connect(const QObject *sender,
   const char *signal, const QObject *receiver, const char *method,
   Qt::ConnectionType type = Qt::AutoConnection)
static QMetaObject::Connection connect(const QObject *sender,
   const QMetaMethod &signal, const QObject *receiver,
   const QMetaMethod &method,
   Qt::ConnectionType type = Qt::AutoConnection)
static QMetaObject::Connection connect(const QObject *sender,
   PointerToMemberFunction signal, const QObject *receiver,
   PointerToMemberFunction method,
   Qt::ConnectionType type = Qt::AutoConnection)
static QMetaObject::Connection connect(const QObject *sender,
   PointerToMemberFunction signal, Functor functor)
static QMetaObject::Connection connect(const QObject *sender,
   PointerToMemberFunction signal, const QObject *context,
   Functor functor, Qt::ConnectionType type = Qt::AutoConnection)

Кроме того, существует обычный (не статический) метод connect():

QMetaObject::Connection connect(const QObject *sender,
   const char *signal, const char *method,
   Qt::ConnectionType type = Qt::AutoConnection) const

Наиболее часто используются следующие форматы:

connect(<Объект1>, <Сигнал>, <Объект2>, <Слот>)
connect(<Объект1>, <Сигнал>, <Слот>)

При нажатии кнопки закроем окно:

QPushButton *button = new QPushButton("Закрыть окно", &window);
QObject::connect(button, SIGNAL(clicked()),
                 &window, SLOT(close()));

Метод позволяет назначить обработчик сигнала <Сигнал>, возникшего при изменении статуса объекта <Объект1>. В первом параметре передается указатель на объект <Объект1>. В параметре <Сигнал> указывается результат выполнения макроса SIGNAL(). Формат макроса:

SIGNAL(<Название сигнала>([Тип параметров]))

Каждый компонент имеет определенный набор сигналов, например, при щелчке на кнопке генерируется сигнал, имеющий следующий формат:

void clicked(bool checked = false)

Внутри круглых скобок могут быть указаны типы параметров, которые передаются в обработчик. Если параметров нет, то указываются только круглые скобки. Пример указания сигнала без параметров:

SIGNAL(clicked())

В этом случае обработчик не принимает никаких параметров. Указание сигнала с параметром выглядит следующим образом:

SIGNAL(clicked(bool))

В этом случае обработчик должен принимать один параметр, значение которого всегда будет равно false, так как это значение по умолчанию для сигнала clicked().

В параметре <Объект2> передается указатель на объект, метод которого должен быть вызван. В параметре <Сигнал> указывается результат выполнения макроса SLOT(). Формат макроса:

SLOT(<Название слота>([Тип параметров]))

Пример слота без параметров:

SLOT(close()))

Пример слота с одним параметром:

SLOT(on_btn1_clicked(bool)))

Пользовательские слоты должны объявляться внутри объявления класса в секции:

<Модификатор доступа> slots:

Пример объявления закрытого слота внутри класса:

private slots:
   void on_btn1_clicked();

Создадим окно с двумя кнопками. Для кнопок назначим обработчики нажатия разными способами. Содержимое файла widget.h приведено в листинге 4.1, файла widget.cpp — в листинге 4.2, а файла main.cpp — в листинге 4.3.

Листинг 4.1. Содержимое файла widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>

class Widget : public QWidget
{
   Q_OBJECT

public:
   Widget(QWidget *parent=nullptr);
   ~Widget();
private slots:
   void on_btn1_clicked();
   void on_btn2_clicked(bool);
private:
   QPushButton *btn1;
   QPushButton *btn2;
   QVBoxLayout *vbox;
};
#endif // WIDGET_H

Листинг 4.2. Содержимое файла widget.cpp

#include "widget.h"

Widget::Widget(QWidget *parent)
   : QWidget(parent)
{
   btn1 = new QPushButton("Кнопка 1");
   btn2 = new QPushButton("Кнопка 2");
   vbox = new QVBoxLayout();
   vbox->addWidget(btn1);
   vbox->addWidget(btn2);
   setLayout(vbox);
   connect(btn1, SIGNAL(clicked()),
           this, SLOT(on_btn1_clicked()));
   connect(btn2, SIGNAL(clicked(bool)),
           SLOT(on_btn2_clicked(bool)));
}

void Widget::on_btn1_clicked()
{
   qDebug() << "Нажата кнопка 1";
}

void Widget::on_btn2_clicked(bool a)
{
   qDebug() << "Нажата кнопка 2" << a;
}

Widget::~Widget() {}

Листинг 4.3. Содержимое файла main.cpp

#include "widget.h"

int main(int argc, char *argv[])
{
   QApplication app(argc, argv);
   Widget window;
   window.setWindowTitle("Назначение обработчиков сигналов");
   window.resize(350, 120);
   window.show();
   return app.exec();
}

Назначение обработчика для первой кнопки выглядит так:

connect(btn1, SIGNAL(clicked()),
        this, SLOT(on_btn1_clicked()));

Здесь мы воспользовались статическим методом connect(), хоть и получили доступ к нему как к обычному методу класса. Класс QWidget наследует класс QObject, поэтому такая запись возможна. Иначе нужно написать так:

QObject::connect(btn1, SIGNAL(clicked()),
                 this, SLOT(on_btn1_clicked()));

Можно также назначить обработчик следующим образом:

QObject::connect(btn1, &QPushButton::clicked,
                 this, &Widget::on_btn1_clicked);

При назначения обработчика нажатия второй кнопки мы опустили передачу указателя this:

connect(btn2, SIGNAL(clicked(bool)),
        SLOT(on_btn2_clicked(bool)));

В качестве обработчика может выступать обычная функция или лямбда-выражение. Пример приведен в листинге 4.4.

Листинг 4.4. Содержимое файла main.cpp

#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>

void on_clicked() {
   qDebug() << "Нажата кнопка 1. on_clicked()";
}
void on_clicked2() {
   qDebug() << "Нажата кнопка 1. on_clicked2()";
}

int main(int argc, char *argv[])
{
   QApplication app(argc, argv);
   QWidget window;
   window.setWindowTitle("Назначение обработчиков сигналов");
   window.resize(350, 70);

   QPushButton *btn1 = new QPushButton("Кнопка 1");
   QPushButton *btn2 = new QPushButton("Кнопка 2");
   QVBoxLayout *vbox = new QVBoxLayout();
   vbox->addWidget(btn1);
   vbox->addWidget(btn2);
   window.setLayout(vbox);
   QObject::connect(btn1, &QPushButton::clicked,
                    on_clicked);
   QObject::connect(btn1, &QPushButton::clicked,
                    on_clicked2);
   QObject::connect(btn2, &QPushButton::clicked,
      [=]() {
         qDebug() << "Нажата кнопка 2";
      });
   window.show();
   return app.exec();
}

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

Можно также связать один сигнал с другим сигналом. При нажатии первой кнопки сгенерируем сигнал нажатия второй кнопки и обработаем его (листинг 4.5).

Листинг 4.5. Содержимое файла main.cpp

#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>

int main(int argc, char *argv[])
{
   QApplication app(argc, argv);
   QWidget window;
   window.setWindowTitle("Назначение обработчиков сигналов");
   window.resize(350, 100);

   QPushButton *btn1 = new QPushButton("Кнопка 1");
   QPushButton *btn2 = new QPushButton("Кнопка 2");
   QVBoxLayout *vbox = new QVBoxLayout();
   vbox->addWidget(btn1);
   vbox->addWidget(btn2);
   window.setLayout(vbox);
   QObject::connect(btn1, SIGNAL(clicked()),
                    btn2, SIGNAL(clicked()));
   QObject::connect(btn2, &QPushButton::clicked,
      [=]() {
         qDebug() << "Нажата кнопка 2";
      });
   window.show();
   return app.exec();
}

Необязательный параметр type определяет тип соединения между сигналом и обработчиком. На этот параметр следует обратить особое внимание при использовании нескольких потоков в приложении, так как изменять GUI-поток из другого потока нельзя. В параметре можно указать одну из следующих констант:

  • Qt::AutoConnection — значение по умолчанию. Если источник сигнала и обработчик находятся в одном потоке, то эквивалентно значению Qt::DirectConnection, а если в разных потоках — то Qt::QueuedConnection;
  • Qt::DirectConnection — обработчик вызывается сразу после генерации сигнала. Обработчик выполняется в потоке источника сигнала;
  • Qt::QueuedConnection — сигнал помещается в очередь обработки событий. Обработчик выполняется в потоке приемника сигнала;
  • Qt::BlockingQueuedConnection — аналогично значению Qt::QueuedConnection, но пока сигнал не обработан поток будет заблокирован. Обратите внимание на то, что источник сигнала и обработчик должны быть обязательно расположены в разных потоках;
  • Qt::UniqueConnection — обработчик можно назначить только если он не был назначен ранее;
  • Qt::SingleShotConnection — обработчик будет вызываться только один раз. Пример:
QObject::connect(btn1, SIGNAL(clicked()),
                 this, SLOT(on_btn1_clicked()),
                 Qt::SingleShotConnection);

Учебник C++ (Qt Creator и MinGW)
Учебник C++ (Qt Creator и MinGW) в формате PDF

Помощь сайту

ЮMoney (Yandex-деньги): 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов