Qt-ohjelmointi
Signals & Slots

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.

Peruskonsepti

📡 Miten signal-slot toimii?

  1. Signaali (Signal) = "Jotain tapahtui!" - Olio lähettää signaalin kun jokin tapahtuma tapahtuu
  2. Slotti (Slot) = "Reagoi tapahtumaan" - Funktio joka suoritetaan kun signaali laukeaa
  3. Connect = Kytkee signaalin ja slotin yhteen

Connect-funktion syntaksi:

QObject::connect(lähettäjä, &LähettäjäLuokka::signaali,
                 vastaanottaja, &VastaanottajaLuokka::slotti);

Esimerkki QPushButton:n kanssa:

Tapa 1: Button luotu koodissa
QPushButton *button = new QPushButton("Klikkaa", this);
connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
Tapa 2: Button luotu UI-editorissa (yleisempi)
// Jos olet luonut buttonin UI-editorissa nimellä "btnKlikkaa"
// voit kytkeä sen slottiin näin:
connect(ui->btnKlikkaa, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
💡 Kumpaa käyttää?
  • UI-editori (tapa 2) - Yleisempi ja helpompi. Näet buttonin visuaalisesti ja voit muokata sen ominaisuuksia graafisesti.
  • Koodissa (tapa 1) - Käytä jos luot buttoneja dynaamisesti ajon aikana tai tarvitset täyttä kontrollia.

Kun nappia klikataan, clicked-signaali laukeaa ja onButtonClicked()-slotti suoritetaan.

Staattinen vs. Dynaaminen

Connect-funktion syntaksi riippuu siitä, onko oliot luotu staattisesti vai dynaamisesti:

Staattiset oliot (pinossa):

SenderClass sender;
ReceiverClass receiver;

// Huomaa &-merkki osoittimen saamiseksi
QObject::connect(&sender, &SenderClass::valueChanged,
                 &receiver, &ReceiverClass::updateValue);

Dynaamiset oliot (keossa):

SenderClass *sender = new SenderClass();
ReceiverClass *receiver = new ReceiverClass();

// Ei &-merkkiä, koska ne ovat jo osoittimia
QObject::connect(sender, &SenderClass::valueChanged,
                 receiver, &ReceiverClass::updateValue);
Käytännön esimerkki: Datan lähettäminen formien välillä

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.

Vaihe 1: Luo projekti ja DataForm

  1. Tee uusi Qt Widget -projekti nimeltään signalSlotExample
  2. Lisää MainWindow:iin:
    • QPushButton nimeltään btnOpenForm (teksti: "Avaa lomake")
    • QLabel nimeltään labelResult (teksti: "Odottaa dataa...")
  3. Luo uusi Qt Designer Form Class: Hiiren oikea -> Add New -> Qt Designer Form Class -> Widget
    Anna nimeksi DataForm
  4. Lisää DataForm:iin:
    • QLineEdit nimeltään lineEditName
    • QPushButton nimeltään btnSend (teksti: "Lähetä")

Vaihe 2: Määrittele signaali DataForm:ssa

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!

Vaihe 3: Emittoi signaali

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.

Vaihe 4: Vastaanota signaali MainWindow:ssa

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;
}

✅ Valmis! Testaa sovellus:

  1. Käynnistä sovellus
  2. Klikkaa "Avaa lomake"
  3. Kirjoita jotain tekstikenttään
  4. Klikkaa "Lähetä"
  5. Lomake sulkeutuu ja MainWindow:n label päivittyy
Parametrit signaalissa: Kyllä vai ei?

Signaalit voivat lähettää dataa tai olla ilman parametreja. Kumpi on parempi?

Signaali ilman parametreja:

// 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;
}

Signaali parametrilla (SUOSITUS):

// 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;
}
💡 Suositus: Lähetä data signaalin mukana kun mahdollista. Tämä tekee koodista:
  • ✅ Löyhemmin kytketyn - vastaanottaja ei tarvitse tietää lähettäjän sisäistä rakennetta
  • ✅ Helpommin testattavan - voit testata slottia ilman koko lähettäjä-oliota
  • ✅ Selkeämmän - data kulkee eksplisiittisesti signaalin kautta
Lambda-funktiot slottien sijaan

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
Signal-slot HTTP-pyynnöissä

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.

Disconnect - Yhteyden purkaminen

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:

  • Qt purkaa yhteyden automaattisesti kun lähettäjä- tai vastaanottaja-olio tuhotaan
  • Lambda-funktioilla yhteyksiä ei yleensä tarvitse purkaa erikseen

Disconnect on hyödyllinen vain erityistilanteissa, kuten kun haluat väliaikaisesti estää signaalin käsittelyn.

Yhteenveto

🎯 Tärkeimmät opit signal-slot järjestelmästä:

  1. Signaalit määritellään signals:-osiossa (ei toteutusta!)
  2. Slotit ovat tavallisia funktioita, määritellään private slots: tai public slots: osiossa
  3. emit-avainsanalla lähetetään signaali
  4. connect()-funktiolla kytketään signaali slottiin
  5. Lambda-funktioita kannattaa käyttää yksinkertaisissa tapauksissa
  6. Lähetä data signaalin parametrina kun mahdollista
  7. Qt purkaa yhteykdet automaattisesti olioiden tuhoutuessa



Toggle Menu