Qt
Сигналы и слоты
Поиск…
Вступление
замечания
Официальную документацию по этой теме можно найти здесь .
Маленький пример
Сигналы и слоты используются для связи между объектами. Механизм сигналов и слотов является центральной особенностью Qt и, вероятно, той частью, которая больше всего отличается от функций, предоставляемых другими фреймами.
Минимальный пример требует класса с одним сигналом, одним слотом и одним соединением:
counter.h
#ifndef COUNTER_H
#define COUNTER_H
#include <QWidget>
#include <QDebug>
class Counter : public QWidget
{
/*
* All classes that contain signals or slots must mention Q_OBJECT
* at the top of their declaration.
* They must also derive (directly or indirectly) from QObject.
*/
Q_OBJECT
public:
Counter (QWidget *parent = 0): QWidget(parent)
{
m_value = 0;
/*
* The most important line: connect the signal to the slot.
*/
connect(this, &Counter::valueChanged, this, &Counter::printvalue);
}
void setValue(int value)
{
if (value != m_value) {
m_value = value;
/*
* The emit line emits the signal valueChanged() from
* the object, with the new value as argument.
*/
emit valueChanged(m_value);
}
}
public slots:
void printValue(int value)
{
qDebug() << "new value: " << value;
}
signals:
void valueChanged(int newValue);
private:
int m_value;
};
#endif
main
задает новое значение. Мы можем проверить, как вызывается слот, распечатывая значение.
#include <QtGui>
#include "counter.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Counter counter;
counter.setValue(10);
counter.show();
return app.exec();
}
Наконец, наш файл проекта:
SOURCES = \
main.cpp
HEADERS = \
counter.h
Новый синтаксис соединения Qt5
Обычный синтаксис connect
который использует макросы SIGNAL
и SLOT
полностью работает во время выполнения, что имеет два недостатка: у него есть некоторые служебные данные во время выполнения (в результате также возникают издержки в двоичном размере), и проверка правильности времени компиляции не выполняется. В новом синтаксисе рассматриваются обе проблемы. Прежде чем проверять синтаксис в примере, нам лучше знать, что происходит в частности.
Предположим, мы строим дом, и мы хотим подключить кабели. Это именно то, что делает функция connect. Сигналы и слоты - это те, которые нуждаются в этом соединении. Дело в том, что если вы используете одно соединение, вам нужно быть осторожным в отношении дальнейших перекрывающихся соединений. Всякий раз, когда вы подключаете сигнал к слоту, вы пытаетесь сообщить компилятору, что всякий раз, когда выдается сигнал, просто вызывайте функцию слота. Именно это и происходит.
Вот пример main.cpp :
#include <QApplication>
#include <QDebug>
#include <QTimer>
inline void onTick()
{
qDebug() << "onTick()";
}
struct OnTimerTickListener {
void onTimerTick()
{
qDebug() << "OnTimerTickListener::onTimerTick()";
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
OnTimerTickListener listenerObject;
QTimer timer;
// Connecting to a non-member function
QObject::connect(&timer, &QTimer::timeout, onTick);
// Connecting to an object member method
QObject::connect(&timer, &QTimer::timeout, &listenerObject, &OnTimerTickListener::onTimerTick);
// Connecting to a lambda
QObject::connect(&timer, &QTimer::timeout, [](){
qDebug() << "lambda-onTick";
});
return app.exec();
}
Подсказка: старый синтаксис (макросы SIGNAL
/ SLOT
) требует, чтобы метакомпилятор Qt (MOC) запускался для любого класса, имеющего либо слоты, либо сигналы. С точки зрения кодирования это означает, что для таких классов должен быть макрос Q_OBJECT
(что указывает на необходимость запуска MOC для этого класса).
С другой стороны, новый синтаксис по-прежнему требует MOC для работы сигналов, но не для слотов. Если у класса есть только слоты и нет сигналов, он не обязательно должен иметь макрос Q_OBJECT
и, следовательно, не может вызывать MOC, что не только уменьшает окончательный двоичный размер, но также сокращает время компиляции (без вызова MOC и последующего вызова компилятора для сгенерированного *_moc.cpp
файл).
Подключение перегруженных сигналов / слотов
Хотя во многих отношениях лучше, новый синтаксис соединения в Qt5 имеет одну большую слабость: подключение перегруженных сигналов и слотов. Чтобы позволить компилятору разрешить перегрузки, нам нужно использовать static_cast
s для указателей функций-членов или (начиная с Qt 5.7). qOverload
и friends:
#include <QObject>
class MyObject : public QObject
{
Q_OBJECT
public:
explicit MyObject(QObject *parent = nullptr) : QObject(parent) {}
public slots:
void slot(const QString &string) {}
void slot(const int integer) {}
signals:
void signal(const QString &string) {}
void signal(const int integer) {}
};
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
// using pointers to make connect calls just a little simpler
MyObject *a = new MyObject;
MyObject *b = new MyObject;
// COMPILE ERROR! the compiler does not know which overloads to pick :(
QObject::connect(a, &MyObject::signal, b, &MyObject::slot);
// this works, now the compiler knows which overload to pick, it is very ugly and hard to remember though...
QObject::connect(
a,
static_cast<void(MyObject::*)(int)>(&MyObject::signal),
b,
static_cast<void(MyObject::*)(int)>(&MyObject::slot));
// ...so starting in Qt 5.7 we can use qOverload and friends:
// this requires C++14 enabled:
QObject::connect(
a,
qOverload<int>(&MyObject::signal),
b,
qOverload<int>(&MyObject::slot));
// this is slightly longer, but works in C++11:
QObject::connect(
a,
QOverload<int>::of(&MyObject::signal),
b,
QOverload<int>::of(&MyObject::slot));
// there are also qConstOverload/qNonConstOverload and QConstOverload/QNonConstOverload, the names should be self-explanatory
}
Разъем для подключения нескольких оконных сигналов
Простой пример с несколькими окнами, использующий сигналы и слоты.
Существует класс MainWindow, который управляет представлением главного окна. Второе окно контролируется классом Веб-сайта.
Эти два класса подключены таким образом, что при нажатии кнопки на веб-сайте происходит что-то в MainWindow (текстовая метка изменяется).
Я сделал простой пример, который также находится на GitHub :
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "website.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void changeText();
private slots:
void on_openButton_clicked();
private:
Ui::MainWindow *ui;
//You want to keep a pointer to a new Website window
Website* webWindow;
};
#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);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::changeText()
{
ui->text->setText("New Text");
delete webWindow;
}
void MainWindow::on_openButton_clicked()
{
webWindow = new Website();
QObject::connect(webWindow, SIGNAL(buttonPressed()), this, SLOT(changeText()));
webWindow->show();
}
website.h
#ifndef WEBSITE_H
#define WEBSITE_H
#include <QDialog>
namespace Ui {
class Website;
}
class Website : public QDialog
{
Q_OBJECT
public:
explicit Website(QWidget *parent = 0);
~Website();
signals:
void buttonPressed();
private slots:
void on_changeButton_clicked();
private:
Ui::Website *ui;
};
#endif // WEBSITE_H
website.cpp
#include "website.h"
#include "ui_website.h"
Website::Website(QWidget *parent) :
QDialog(parent),
ui(new Ui::Website)
{
ui->setupUi(this);
}
Website::~Website()
{
delete ui;
}
void Website::on_changeButton_clicked()
{
emit buttonPressed();
}
Состав проекта:
SOURCES += main.cpp\
mainwindow.cpp \
website.cpp
HEADERS += mainwindow.h \
website.h
FORMS += mainwindow.ui \
website.ui
Рассмотрим Uis, который должен быть составлен:
- Главное окно: ярлык «текст» и кнопка «openButton»
- Окно веб-сайта: кнопка "changeButton"
Таким образом, ключевыми точками являются соединения между сигналами и слотами и управление указателями или ссылками на окна.