Qt
Segnali e slot
Ricerca…
introduzione
Osservazioni
La documentazione ufficiale su questo argomento può essere trovata qui .
Un piccolo esempio
Segnali e slot sono utilizzati per la comunicazione tra oggetti. Il meccanismo di segnali e slot è una caratteristica centrale di Qt e probabilmente la parte che differisce maggiormente dalle funzionalità fornite da altri framework.
L'esempio minimo richiede una classe con un segnale, uno slot e una connessione:
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
Il main
imposta un nuovo valore. Possiamo controllare come viene chiamato lo slot, stampando il valore.
#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();
}
Infine, il nostro file di progetto:
SOURCES = \
main.cpp
HEADERS = \
counter.h
La nuova sintassi per la connessione Qt5
La sintassi di connect
convenzionale che utilizza le macro SIGNAL
e SLOT
funziona interamente in fase di runtime, che presenta due inconvenienti: presenta alcuni sovraccarichi di runtime (che si verificano anche in overhead di dimensioni binarie) e non esiste un controllo di correttezza in fase di compilazione. La nuova sintassi affronta entrambi i problemi. Prima di controllare la sintassi in un esempio, faremmo meglio a sapere cosa succede in particolare.
Diciamo che stiamo costruendo una casa e vogliamo collegare i cavi. Questo è esattamente ciò che fa la funzione di connessione. Segnali e slot sono quelli che necessitano di questa connessione. Il punto è che se si effettua una connessione, è necessario fare attenzione alle ulteriori connessioni di sovrapposizione. Ogni volta che si collega un segnale a uno slot, si sta tentando di dire al compilatore che ogni volta che viene emesso il segnale, basta richiamare la funzione dello slot. Questo è esattamente ciò che accade.
Ecco un esempio di 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();
}
Suggerimento: la vecchia sintassi (macro SIGNAL
/ SLOT
) richiede che il metacompiler Qt (MOC) venga eseguito per qualsiasi classe che abbia uno slot o un segnale. Dal punto di vista della codifica ciò significa che tali classi devono avere la macro Q_OBJECT
(che indica la necessità di eseguire MOC su questa classe).
La nuova sintassi, d'altra parte, richiede ancora MOC per far funzionare i segnali, ma non per gli slot. Se una classe ha solo slot e nessun segnale, non ha bisogno della macro Q_OBJECT
e quindi potrebbe non invocare il MOC, che non solo riduce la dimensione binaria finale ma riduce anche il tempo di compilazione (nessuna chiamata MOC e nessuna successiva chiamata del compilatore per il generato *_moc.cpp
file *_moc.cpp
).
Collegamento di segnali / slot sovraccarichi
Pur essendo migliore sotto molti aspetti, la nuova sintassi della connessione in Qt5 presenta una grande debolezza: connessione di segnali e slot sovraccaricati. Per consentire al compilatore di risolvere i sovraccarichi, è necessario utilizzare static_cast
s per i puntatori di funzioni dei membri o (a partire da Qt 5.7) qOverload
e amici:
#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
}
Connessione slot segnale multi finestra
Un semplice esempio multiwindow che utilizza segnali e slot.
Esiste una classe MainWindow che controlla la vista della finestra principale. Una seconda finestra controllata dalla classe del sito web.
Le due classi sono collegate in modo che quando si fa clic su un pulsante nella finestra del sito Web, qualcosa accade nella finestra principale (un'etichetta di testo viene modificata).
Ho fatto un semplice esempio che è anche su 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();
}
Composizione del progetto:
SOURCES += main.cpp\
mainwindow.cpp \
website.cpp
HEADERS += mainwindow.h \
website.h
FORMS += mainwindow.ui \
website.ui
Considera l'Uis da comporre:
- Finestra principale: un'etichetta chiamata "testo" e un pulsante chiamato "openButton"
- Finestra del sito Web: un pulsante chiamato "changeButton"
Quindi i punti chiave sono le connessioni tra segnali e slot e la gestione dei puntatori o riferimenti di Windows.