Signal-slot järjestelmä on Qt:n ydinominaisuus, jolla oliot voivat kommunikoida keskenään ilman tiukkaa kytkentää. Se korvaa perinteiset callback-funktiot ja tekee koodista modulaarisemman ja helpommin ylläpidettävän.
QObject::connect(lähettäjä, &LähettäjäLuokka::signaali,
vastaanottaja, &VastaanottajaLuokka::slotti);
Esimerkki QPushButton:n kanssa:
QPushButton *button = new QPushButton("Klikkaa", this);
connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
// Jos olet luonut buttonin UI-editorissa nimellä "btnKlikkaa" // voit kytkeä sen slottiin näin: connect(ui->btnKlikkaa, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
Kun nappia klikataan, clicked-signaali laukeaa ja onButtonClicked()-slotti suoritetaan.
Connect-funktion syntaksi riippuu siitä, onko oliot luotu staattisesti vai dynaamisesti:
SenderClass sender;
ReceiverClass receiver;
// Huomaa &-merkki osoittimen saamiseksi
QObject::connect(&sender, &SenderClass::valueChanged,
&receiver, &ReceiverClass::updateValue);
SenderClass *sender = new SenderClass();
ReceiverClass *receiver = new ReceiverClass();
// Ei &-merkkiä, koska ne ovat jo osoittimia
QObject::connect(sender, &SenderClass::valueChanged,
receiver, &ReceiverClass::updateValue);
Tehdään sovellus, jossa MainWindow avaa uuden ikkunan (DataForm), ja DataForm lähettää käyttäjän syöttämän tekstin takaisin MainWindow:lle signaalilla.
Avaa dataform.h ja lisää signals-osio:
class DataForm : public QWidget
{
Q_OBJECT
public:
explicit DataForm(QWidget *parent = nullptr);
~DataForm();
signals:
void dataSent(QString text); // Oma signaali joka lähettää tekstin
private slots:
void on_btnSend_clicked();
private:
Ui::DataForm *ui;
};
Huom! Signaalit määritellään signals:-osion alle. Ne ovat aina public ja niille ei kirjoiteta toteutusta!
Avaa dataform.cpp ja lisää btnSend:n clicked-slottiin:
void DataForm::on_btnSend_clicked()
{
QString text = ui->lineEditName->text();
if (text.isEmpty()) {
qWarning() << "Tyhjä teksti!";
return;
}
emit dataSent(text); // Lähetetään signaali tekstin kanssa
this->close(); // Suljetaan ikkuna
}
emit-avainsanalla lähetetään signaali. Kaikki slotit jotka on kytketty tähän signaaliin suoritetaan.
Avaa mainwindow.h ja lisää:
#include "dataform.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_btnOpenForm_clicked();
void onDataReceived(QString text); // Slotti joka vastaanottaa datan
private:
Ui::MainWindow *ui;
};
Avaa mainwindow.cpp ja toteuta:
void MainWindow::on_btnOpenForm_clicked()
{
DataForm *form = new DataForm(this);
form->setAttribute(Qt::WA_DeleteOnClose); // Tuhoaa olion ikkunan sulkeutuessa
// Kytketään DataForm:n signaali MainWindow:n slottiin
connect(form, &DataForm::dataSent, this, &MainWindow::onDataReceived);
form->show();
}
void MainWindow::onDataReceived(QString text)
{
ui->labelResult->setText("Vastaanotettiin: " + text);
qDebug() << "Saatu data:" << text;
}
Signaalit voivat lähettää dataa tai olla ilman parametreja. Kumpi on parempi?
// dataform.h
signals:
void saveClicked(); // Ei dataa
// dataform.cpp
emit saveClicked();
// mainwindow.cpp
void MainWindow::onSaveClicked()
{
// Pitää erikseen hakea data
QString text = dataForm->getText();
qDebug() << text;
}
// dataform.h
signals:
void saveClicked(QString text); // Lähettää datan
// dataform.cpp
emit saveClicked(ui->lineEdit->text());
// mainwindow.cpp
void MainWindow::onSaveClicked(QString text)
{
// Data tulee suoraan parametrina
qDebug() << text;
}
Modernissa Qt:ssa voit käyttää lambda-funktioita slottien sijaan yksinkertaisissa tapauksissa:
// Perinteinen tapa: slotti-funktio
connect(form, &DataForm::dataSent, this, &MainWindow::onDataReceived);
// Moderni tapa: lambda-funktio
connect(form, &DataForm::dataSent, this, [this](QString text) {
ui->labelResult->setText("Vastaanotettiin: " + text);
qDebug() << "Saatu data:" << text;
});
Milloin käyttää lambdaa vs. slottia?
| Tilanne | Käytä | Syy |
|---|---|---|
| Yksinkertainen, lyhyt toiminto (1-5 riviä) | Lambda | Koodi pysyy samassa paikassa, helppo lukea |
| Monimutkainen logiikka (>5 riviä) | Slotti | Helpompi debugata ja testata erikseen |
| Sama toiminto useassa paikassa | Slotti | Vältytään koodin toistolta |
| HTTP-vastausten käsittely | Lambda | Vastaus liittyy juuri tähän pyyntöön |
HTTP-pyynnöissä käytämme signal-slot järjestelmää vastausten käsittelyyn. Kuten aiemmin opimme, käytämme lambda-funktioita vastausten käsittelyyn:
QNetworkReply *reply = manager->get(request);
// Kytketään finished-signaali lambda-funktioon
connect(reply, &QNetworkReply::finished, this, [reply, this]() {
if (reply->error() == QNetworkReply::NoError) {
QByteArray data = reply->readAll();
// Käsittele vastaus
}
reply->deleteLater();
});
Tässä finished-signaali on QNetworkReply:n sisäänrakennettu signaali, joka laukeaa automaattisesti kun HTTP-vastaus on vastaanotettu. Me emme kirjoita emit finished() koodia - Qt tekee sen puolestamme.
Signaalin ja slotin välinen yhteys voidaan purkaa disconnect()-funktiolla:
// Luo yhteys
QMetaObject::Connection connection = connect(sender, &Sender::signal,
receiver, &Receiver::slot);
// Pura yhteys myöhemmin
disconnect(connection);
Huom! Yhteyden purkaminen on harvoin tarpeen, koska:
Disconnect on hyödyllinen vain erityistilanteissa, kuten kun haluat väliaikaisesti estää signaalin käsittelyn.
signals:-osiossa (ei toteutusta!)private slots: tai public slots: osiossa