Evgenii Legotckoi
Evgenii Legotckoi15 сентября 2015 г. 21:44

Qt/C++ - Урок 024. Сигналы и слоты в Qt5

Сигналы и слоты используются для коммуникации между объектами в Qt. Механизм сигналов и слотов является центральной функцией в Qt, и вероятно это то, что отличает Qt по своему функционалу от других фреймворков. Сигналы и слоты стали возможны благодаря мета-объектной системе Qt.

Введение

При программировании GUI, когда изменяется один из виджетов, мы зачастую хотим, чтобы другие виджеты были об этом уведомлены. В общем случае, мы хотим, чтобы объекты могли взаимодействовать друг с другом. Например, если пользователь нажал кнопку Закрыть , мы вероятно захотим, чтобы объект window вызвал функцию close().

Другие инструменты разработки обеспечивают подобный функционал использованием callback. callback являет указателем на функцию, и если Вы хотите выполнить функцию, которая уведомит Вас о каком-либо событии, то Вы передаёте указатель на другую функцию, то есть callback. Работающая функция вызывает callback тогда, когда это уместно. В то время как существуют фреймворки, которые успешно используют callback функции, тем не менее callback являются неинтуитивным способом, который может вызывать проблемы с обеспечением корректности возвращаемых аргументов.


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

В Qt применяется альтернативная техника, то есть используются слоты и сигналы. Сигнал выполняется тогда, когда происходит определенное событие. Виджеты Qt имеют множество предопределённых сигналов, но мы можем всегда отнаследоваться отвиджета и определить собственные сигналы для них. Слотом является функция, которая вызывается в ответ на определенный сигнал. Виджеты Qt также имеют множество предопределенных слотов, но наследование от виджетов и добавление собственных слотов является обычной практикой, так что Вы можете обрабатывать те сигналы, которые Вам интересны.

]

сигналы и слоты в Qt Сигналы и слоты являются типо-безопасным механизмом. Сигнатура сигнала должа совпадать с сигнатурой принимающего слота (Хотя фактически, слот может иметь сигнатуру короче, чем сигнал, но слот принимает сигнал, поскольку игнорирует лишние аргументы). Поскольку сигнатуры совместимы, то компилятор может помочь определить несоответствия при использовании синтаксиса основанного на указателях. Тогда как при синтаксисе, основанном на макросах SIGNAL и SLOT возможно определить несоответствие типов только в runtime процессе. Сигналы и слоты слабо связаны: класс, который вызывает сигнал знает только слот, который принимает сигнал. Механизм сигналов и слотов в Qt обеспечивается, если Вы подключили сигнал к слоту, который будет вызываться с параметрами сигнала в нужным момент. Сигналы и слоты могут иметь несколько аргументов и типов. И они являются полностью типо-безопасными.

Все классы, которые отнаследованы от QObject или его подклассов (таких как QWidget) могут содержать сигналы и слоты. Сигналы вызываются объектами, которые изменяют своё состояние, что может быть интересно другим объектам. Это всё, что объект делает для коммуникации. И объект не беспокоится о том, кто принимает сигналы, которые он испускает. Это является честной инкапсуляцией информации, и обеспечивает то, что объект может использоваться, как программный компонент.

Слоты могут быть использованы для приема сигналов, но они являются также и обычными функциями. Просто как объект не знает что приняло его сигнал, так и слот не знает, какой сигнал подключен к нему. Это обеспечивает реальную независимость компонентов, которые могут быть созданы с Qt.

Вы можете подключить как множество сигналов к одному слоту, так и сигнал может быть подключен к множеству слотов. И даже возможно подключить сигнал непосредственно к другому сигналу. (Это вызовет второй сигнал, когда был вызван первый)

Вместе, сигналы и слоты создают мощный механизм компонентного программирования.

Сигналы

Сигналы выпускаются объектом, когда его внутреннее состояние изменилось в определенном направлении, которое может быть интересно другим объектам. Сигналы являются публично доступными функциями и могут быть вызваны где угодно, но рекомендуется их вызывать только в классе, где они были определены, а также в его подклассах.

Когда сигнал вызван, слот подключенный к нему обычно выполняется незамедлительно, просто как нормальная функция. Это возможно потому, что механизм сигналов и слотов является независимым от каких-либо циклов в GUI. Выполнение кода следует вызывать директивой emit , которая вызовет все слоты. В тех ситуациях, когда используются очереди подключений, код будет запускать сигнал, а слоты будут выполнены несколько позже.

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

Сигналы автоматически генерируются в moc и не должны быть определены в .cpp файле, а также они никогда не возвращают результат.

Примечание: По нашему опыту сигналы и слоты являются более используемыми, если они не используются специальных типов. Если QScrollBar::valueChanged () является специальным типом, таким как гипотетический QScrollBar::Range, он может подключаться только к слоту разработанному специально для QScrollBar . Подключение различных виджетов вместе может быть невозможным.

Слоты

Слот вызывается тогда, когда сигнал подключенный к нему был вызван. Слоты являются нормальной С++ функцией и может быть вызвана; они особенны только тем, что к ним подключаются сигналы.

Также слоты могут выполняться как обычные функции, они подчиняются обычным правилам С++, когда вызываются непосредственно. Однако, как слоты, они могут быть вызваны другими компонентами, несмотря на их уровень доступа, через сигнал-слотовое подключение. Это означает, что сигнал испускается из одного из классов и может быть передан в приватный слот, который будет вызван из этого несвязанного класса.

Вы можете также определить слоты как виртуальные, которые мы находим довольно полезными в практике.

По сравнению с callback сигналы и слоты немного медленнее из-за той гибкости, что они предоставляют, хотя различия в реальном приложении незначительны. В основном, вызов сигнала, который подключается к нескольким слотам, приблизительно в десять раз медленнее, чем вызов не виртуальной функции. Это накладные расходы, из-за которых требуется находить объект соединения путём перебора всех слотов и сигналов и сравнения сигнатур для безопасного вызова функции-слота. В то время как десять невиртуальных функций вызываются меньшими накладными расходами, чем несколько операций new и delete. Как только вы выполняете строку, вектор или список операция за сценой, требующей операций new и delete, накладные расходы сигналов и слотов очень малы по сравнению с полной стоимостью вызова функции. Это верно, когда вы делаете системный вызов в слот или косвенно вызываете более, чем десять функций. Простота и гибкость механизма сигналов и слотов является неважными накладными расходами, которые ваши пользователи не заметят.

Заметьте, что другие библиотеки определяют переменные, называемые сигналы и слоты и могут вызывать ошибки и предупреждения, когда компилируется приложение, основанное на Qt. Решение этих проблем применение директивы #undef для препроцессора.

Подключение сигнала к слоту

До пятой версии Qt подключение сигнала к слоту записывалось посредством макросов, тогда как в пятой версии стала применяться запись, основанная на указателях.

Запись с макросами:

connect(button, SIGNAL(clicked()), this, SLOT(slotButton()));

Запись на основе указателей:

connect(button, &QPushButton::clicked, this, &MainWindow::slotButton);

Преимущество второго варианта заключается в том, что имеется возможность определить несоответствие сигнатур и неверное наименование слота или сигнала ещё на стадии компиляции проекта, а не в процессе тестирования приложения.

Пример использования сигналов и слотов

Для примера использования сигналов и слотов был создан проект, у которого в главном окне содержится три кнопки, к каждой из которых подключен слот, а уже эти слоты передают сигнал в один единый слот с номером нажатой кнопки.

Структура проекта

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

mainwindow.h

Итак, действо следующее: три кнопки - три слота, один сигнал на все три кнопки, который подаётся в слотах кнопок и передаёт номер кнопки в один общий слот, который выдаёт сообщение с номером кнопки.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPushButton>
#include <QMessageBox>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

signals:
    void signalFromButton(int buttonID);    // Сигнал для передачи номер нажатой кнопки

private:
    Ui::MainWindow *ui;

private slots:
    void slotButton1();     // Слоты-обработчики нажатий кнопок
    void slotButton2();
    void slotButton3();

    // Слоты вызывающий сообщение с номеро нажатой кнопки
    void slotMessage(int buttonID);
};

#endif // MAINWINDOW_H

mainwindow.cpp

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

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    /* Объявляем и инициализируем кнопки
     * */
    QPushButton *but_1 = new QPushButton(this);
    QPushButton *but_2 = new QPushButton(this);
    QPushButton *but_3 = new QPushButton(this);

    /* Устанавливаем номера кнопок
     * */
    but_1->setText("1");
    but_2->setText("2");
    but_3->setText("3");

    /* Добавляем кнопки на слой с вертикальной ориентацией
     * */
    ui->verticalLayout->addWidget(but_1);
    ui->verticalLayout->addWidget(but_2);
    ui->verticalLayout->addWidget(but_3);

    /* Подключаем к кнопкам индивидуальные слоты
     * */
    connect(but_1, SIGNAL(clicked()), this, SLOT(slotButton1()));
    connect(but_2, SIGNAL(clicked()), this, SLOT(slotButton2()));
    connect(but_3, SIGNAL(clicked()), this, SLOT(slotButton3()));

    /* Подключаем сигнал с передачей номера кнопки к слоту вывода сообщения
     * */
    connect(this, &MainWindow::signalFromButton, this, &MainWindow::slotMessage);
}

MainWindow::~MainWindow()
{
    delete ui;
}

/* Слоты для обработки нажатия кнопок
 * */
void MainWindow::slotButton1()
{
    emit signalFromButton(1);
}

void MainWindow::slotButton2()
{
    emit signalFromButton(2);
}

void MainWindow::slotButton3()
{
    emit signalFromButton(3);
}

/* Слоты вывода сообщения
 * */
void MainWindow::slotMessage(int buttonID)
{
    QMessageBox::information(this,
                             "Уведомление о нажатой кнопке",
                             "Нажата кнопка под номером " + QString::number(buttonID));
}

Видеоурок

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

ИВ
  • 29 апреля 2020 г. 20:04

Простите , а если я например хочу сделать по кнопке клик не левой кнопкой ,а правой или вообще засекать перемещение мыши на кнопке, то как будет выглядить строка

connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::slotButton1);

Я так понял что сиглалы виджетов заранее предопределены в библиотеке и в данном случае &QPushButton::clicked это событие из стандартной библиотеки Qt. В справке сказано, что всего есть четыре сигнала clicked, pressed, released,toggled. Может я не ту справку читаю, от того и странные вопросы.

Evgenii Legotckoi
  • 29 апреля 2020 г. 20:09

Правильно всё читаете. Так и есть.

Если хотите отслеживать правую кнопку мыши, то вам следует наследоваться от QPushButton и переопределять методы

virtual void mouseDoubleClickEvent(QMouseEvent *event)
virtual void mouseMoveEvent(QMouseEvent *event)
virtual void mousePressEvent(QMouseEvent *event)
virtual void mouseReleaseEvent(QMouseEvent *event)

И переопределять поведение кнопки в случае правой, или левой кнопки мыши.

ИВ
  • 29 апреля 2020 г. 22:39

Извините, последний вопрос.
Написал маленький "проект", хочу стобы сцена ловила события мыши. Особенно когда мышь "отпустили". По сути это нужно для манипулирования с картинками на сцене.
Согласно справке за события на сцене отвечает QGraphicsSceneMouseEvent, однако попытка подружить слот и сигнал со сценой провалились, вот такой код привязки

connect (scene, &MainWindow::mousePressEvent, this, &MainWindow::Slot_mousePressEvent);

не работает, компилятор ругается на объект scene, если поставить this то все конечно компилируется, но это же и не должно работать как надо
Вот весь код проекта.
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtCore>
#include <QtGui>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void mousePressEvent(QGraphicsSceneMouseEvent *e);
    void mouseMoveEvent(QGraphicsSceneMouseEvent *e);
    void mouseReleasEvent(QGraphicsSceneMouseEvent *e);
private:
    Ui::MainWindow *ui;
    QGraphicsScene *scene;
    QGraphicsEllipseItem *ellips;
private slots:
    void Slot_mousePressEvent();     // Слоты-обработчики нажатий кнопок
    void Slot_mouseMoveEvent();
    void Slot_mouseReleasEvent();
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QGraphicsItem>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    scene = new QGraphicsScene(this);  //вот они наши сцены
    ui->graphicsView->setScene(scene);
    QBrush redBrash (Qt::red);
    QPen blackpen(Qt::black);
    blackpen.setWidth(6);
    ellips = scene->addEllipse(10,10,100,100,blackpen,redBrash);
    ellips->setFlag(QGraphicsItem::ItemIsMovable);

    //вот тут ошибки
    connect (this, &MainWindow::mousePressEvent, this, &MainWindow::Slot_mousePressEvent);
    connect (this, &MainWindow::mouseMoveEvent, this, &MainWindow::Slot_mouseMoveEvent);
    connect (this, &MainWindow::mouseReleasEvent, this, &MainWindow::Slot_mouseReleasEvent);
}
MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
    emit Slot_mousePressEvent();
    Q_UNUSED(e);
}
void MainWindow::mouseMoveEvent(QGraphicsSceneMouseEvent *e)
{
    emit Slot_mouseMoveEvent();
    Q_UNUSED(e);
}
void MainWindow::mouseReleasEvent(QGraphicsSceneMouseEvent *e)
{
    emit Slot_mouseReleasEvent();
    Q_UNUSED(e);
}
void MainWindow::Slot_mousePressEvent()
{
    QMessageBox msgBox;
    msgBox.setText("Hello Here");
    msgBox.exec();
}
void MainWindow::Slot_mouseMoveEvent()
{
    QMessageBox msgBox;
    msgBox.setText("Hello Here");
    msgBox.exec();
}
void MainWindow::Slot_mouseReleasEvent()
{
    QMessageBox msgBox;
    msgBox.setText("Hello Here");
    msgBox.exec();
}

Как можно привязать слот к сцене или либому другому виджету ? Может быть у вас есть видеоурок на этой теме? Особенно интересует обработка событий при столкновении двух пиксельных рисунков на сцене.

Evgenii Legotckoi
  • 29 апреля 2020 г. 22:45

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

ИВ
  • 29 апреля 2020 г. 23:09

Да я уже нашел ваш проект, с рисованием мышью и понял что это действительно жуть.
Думаю надо отдохнуть, уже 12 учусь.

Дмитрий
  • 9 июня 2022 г. 1:24

Возник такой вопрос.
Разбираюсь с одной библиотекой. В ней применен паттерн Pimpl. И в коде есть вызовы метода connect(), но с помощью макросов:

Q_Q(QAmqpClient);
initSocket();
heartbeatTimer = new QTimer(q);
QObject::connect(heartbeatTimer, SIGNAL(timeout()), q, SLOT(_q_heartbeat()));
...

Q_Q(QAmqpClient); - это, как понял, получение q-указателя на основной класс.
В нем сигнал таймера, который находится в приватном классе коннектится к слоту, который находится в основном классе.
Как этот коннект записать без макросов?
В основном классе _q_heartbeat() объявлен как Q_PRIVATE_SLOT(d_func(), void _q_heartbeat()). Т.е., на сколько понял, вызывается приватный метод _q_heartbeat приватного класса. Причем он находится в секции public, а не public slots.

Попытки написать что-то типа

QObject::connect(heartbeatTimer, &QTimer::timeout, q, &QAmqpClient::_q_heartbeat);
или
QObject::connect(heartbeatTimer, &QTimer::timeout, q, &QAmqpClientPrivate::_q_heartbeat);

приводят к ошибке компиляции.
ошибка: '_q_heartbeat' is not a member of 'QAmqpClient'
или
ошибка: expected primary-expression before '/' token

Evgenii Legotckoi
  • 24 августа 2022 г. 17:38

Без макросов никак. Приватные методы через указатели не коннектятся извне, что правильно, а вот макросы болт кладут на private и protected модификаторы.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
m
  • moreSpb
  • 18 марта 2024 г. 22:32

C++ - Тест 003. Условия и циклы

  • Результат:85баллов,
  • Очки рейтинга6
в

C++ - Тест 003. Условия и циклы

  • Результат:50баллов,
  • Очки рейтинга-4
l

C++ - Тест 005. Структуры и Классы

  • Результат:91баллов,
  • Очки рейтинга8
Последние комментарии
k
kmssr9 февраля 2024 г. 2:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 9:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 18:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 16:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 5:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
P
Pisych27 февраля 2023 г. 12:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 января 2024 г. 19:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCT27 декабря 2023 г. 16:57
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
Дмитрий10 января 2024 г. 12:18
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii Legotckoi12 декабря 2023 г. 14:48
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

Следите за нами в социальных сетях