Zoeken…


Invoering

Signalen en slots worden gebruikt voor communicatie tussen objecten. Het signaal- en slots-mechanisme is een centraal kenmerk van Qt. Wanneer we in GUI-programmering een widget wijzigen, willen we vaak dat een andere widget op de hoogte wordt gesteld. Meer in het algemeen willen we dat objecten van welke aard dan ook met elkaar kunnen communiceren. Signalen worden uitgezonden door objecten wanneer ze hun status wijzigen op een manier die interessant kan zijn voor andere objecten. Slots kunnen worden gebruikt voor het ontvangen van signalen, maar het zijn ook normale lidfuncties.

Opmerkingen

Officiële documentatie over dit onderwerp vindt u hier .

Een klein voorbeeld

Signalen en slots worden gebruikt voor communicatie tussen objecten. Het signaal- en slots-mechanisme is een centraal kenmerk van Qt en waarschijnlijk het deel dat het meest afwijkt van de functies van andere frameworks.

Het minimale voorbeeld vereist een klasse met één signaal, één slot en één verbinding:

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

De main stelt een nieuwe waarde in. We kunnen controleren hoe het slot wordt genoemd, de waarde afdrukken.

#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();
}

Eindelijk ons projectbestand:

SOURCES   = \
            main.cpp
HEADERS   = \
            counter.h

De nieuwe syntaxis van de Qt5-verbinding

De conventionele connect syntaxis die SIGNAL en SLOT macro's gebruikt, werkt volledig tijdens runtime, wat twee nadelen heeft: het heeft wat runtime-overhead (wat ook resulteert in binaire grootte overhead), en er is geen compilatie-tijd correctheidcontrole. De nieuwe syntaxis lost beide problemen op. Voordat we de syntaxis in een voorbeeld controleren, moeten we beter weten wat er in het bijzonder gebeurt.

Laten we zeggen dat we een huis bouwen en de kabels willen aansluiten. Dit is precies wat de connect-functie doet. Signalen en slots zijn degenen die deze verbinding nodig hebben. Het punt is dat als je één verbinding maakt, je voorzichtig moet zijn met de verdere overlappende verbindingen. Telkens wanneer u een signaal op een slot aansluit, probeert u de compiler te vertellen dat wanneer het signaal werd uitgezonden, u eenvoudig de slotfunctie oproept. Dit is precies wat er gebeurt.

Hier is een voorbeeld van 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();
}

Hint: de oude syntaxis ( SIGNAL / SLOT macro's) vereist dat de Qt metacompiler (MOC) wordt uitgevoerd voor elke klasse die slots of signalen heeft. Vanuit het oogpunt van codering betekent dit dat dergelijke klassen de macro Q_OBJECT moeten hebben (wat aangeeft dat MOC op deze klasse moet worden uitgevoerd).

De nieuwe syntaxis daarentegen vereist nog steeds MOC om signalen te laten werken, maar niet voor slots. Als een klasse alleen slots en geen signalen heeft, hoeft deze niet de Q_OBJECT macro te hebben en dus mogelijk niet de MOC aan te roepen, die niet alleen de uiteindelijke binaire grootte vermindert, maar ook de compilatietijd vermindert (geen MOC-oproep en geen daaropvolgende compiler-oproep voor de gegenereerde *_moc.cpp bestand).

Overbelaste signalen / slots aansluiten

Hoewel het in veel opzichten beter is, heeft de nieuwe verbindingssyntaxis in Qt5 één grote zwakte: het verbinden van overbelaste signalen en slots. Om de compiler de overbelastingen te laten oplossen, moeten we static_cast s gebruiken om static_cast pointers aan te static_cast , of (vanaf Qt 5.7) qOverload en vrienden:

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

Verbinding met meerdere vensters voor sleufsignalen

Een eenvoudig voorbeeld met meerdere vensters met behulp van signalen en slots.

Er is een MainWindow-klasse die de weergave Hoofdvenster bestuurt. Een tweede venster gecontroleerd door Website-klasse.

De twee klassen zijn verbonden zodat wanneer u op een knop in het websitevenster klikt, er iets gebeurt in het hoofdvenster (een tekstlabel is gewijzigd).

Ik heb een eenvoudig voorbeeld gemaakt dat ook op GitHub staat :

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

Project samenstelling:

SOURCES += main.cpp\
        mainwindow.cpp \
    website.cpp

HEADERS  += mainwindow.h \
    website.h

FORMS    += mainwindow.ui \
    website.ui

Beschouw de Uis als samengesteld:

  • Hoofdvenster: een label met de naam "tekst" en een knop met de naam "openButton"
  • Websitevenster: een knop met de naam "changeButton"

De sleutelpunten zijn dus de verbindingen tussen signalen en slots en het beheer van Windows-verwijzingen of verwijzingen.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow