Sök…


Introduktion

Signaler och platser används för kommunikation mellan objekt. Signaler och slots-mekanismen är ett centralt drag i Qt. När vi ändrar en widget vill vi i GUI-programmering ofta att en annan widget ska meddelas. Mer generellt vill vi att objekt av alla slag ska kunna kommunicera med varandra. Signaler avges av objekt när de ändrar tillstånd på ett sätt som kan vara intressant för andra objekt. Spår kan användas för att ta emot signaler, men de är också normala medlemsfunktioner.

Anmärkningar

Officiell dokumentation om detta ämne finns här .

Ett litet exempel

Signaler och platser används för kommunikation mellan objekt. Signaler och slots-mekanismen är ett centralt inslag i Qt och förmodligen den del som skiljer sig mest från de funktioner som tillhandahålls av andra ramverk.

Det minimala exemplet kräver en klass med en signal, en plats och en anslutning:

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 sätter ett nytt värde. Vi kan kontrollera hur kortplatsen heter och skriva ut värdet.

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

Slutligen, vår projektfil:

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

Den nya Qt5-anslutningssyntaxen

Den konventionella connect som använder SIGNAL och SLOT makron fungerar helt under körning, vilket har två nackdelar: det har viss driftskostnadsöverskridning (vilket resulterar också i binära storlekskostnader), och det finns ingen kontroll av korrekt kompileringstid. Den nya syntaxen tar upp båda frågorna. Innan vi kontrollerar syntaxen i ett exempel, skulle vi bättre veta vad som händer särskilt.

Låt oss säga att vi bygger ett hus och vi vill ansluta kablarna. Det är exakt vad connect-funktionen gör. Signaler och slots är de som behöver denna anslutning. Poängen är att om du gör en anslutning måste du vara försiktig med de ytterligare överlappande anslutningarna. När du ansluter en signal till en lucka försöker du säga kompilatorn att när signalen släppts helt enkelt anropar slotfunktionen. Det här är exakt vad som händer.

Här är ett exempel på 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();
}

Tips: den gamla syntaxen ( SIGNAL / SLOT makron) kräver att Qt-metakompilatorn (MOC) körs för alla klasser som har antingen luckor eller signaler. Ur kodningssynpunkt betyder det att sådana klasser måste ha Q_OBJECT (vilket indikerar nödvändigheten av att köra MOC på denna klass).

Den nya syntaxen å andra sidan kräver fortfarande MOC för att signaler ska fungera, men inte för kortplatser. Om en klass endast har platser och inga signaler behöver den inte ha Q_OBJECT och därför kanske inte påkalla MOC, vilket inte bara minskar den slutliga binära storleken utan också minskar kompileringstiden (inget MOC-samtal och inget efterföljande kompilatorsamtal för den genererade *_moc.cpp fil).

Ansluter överbelastade signaler / platser

Samtidigt som den är bättre i många avseenden har den nya anslutningssyntaxen i Qt5 en stor svaghet: Ansluta överbelastade signaler och slots. För att låta kompilatorn lösa överbelastningen måste vi använda static_cast s för medlemmens funktionspekare, eller (börjar i Qt 5.7) qOverload och vänner:

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

Multi-fönster signalanslutning

Ett enkelt exempel på flera vindar med signaler och slots.

Det finns en MainWindow-klass som styr huvudfönstervyn. Ett andra fönster som kontrolleras av webbplatsklass.

De två klasserna är anslutna så att när du klickar på en knapp i webbplatsfönstret händer något i MainWindow (en textetikett ändras).

Jag gjorde ett enkelt exempel som också finns på 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();
}

Projektets sammansättning:

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

HEADERS  += mainwindow.h \
    website.h

FORMS    += mainwindow.ui \
    website.ui

Tänk på att Uis ska vara sammansatt:

  • Huvudfönster: en etikett som heter "text" och en knapp som heter "openButton"
  • Webbplatsfönster: en knapp som heter "ändra knapp"

Så knappsatserna är anslutningarna mellan signaler och platser och hantering av fönsterpekare eller referenser.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow