Поиск…


Вступление

Сигналы и слоты используются для связи между объектами. Механизм сигналов и слотов является центральной особенностью Qt. В GUI-программировании, когда мы меняем один виджет, мы часто хотим, чтобы другой виджет был уведомлен. В более общем плане мы хотим, чтобы объекты любого типа могли общаться друг с другом. Сигналы излучаются объектами, когда они меняют свое состояние таким образом, который может быть интересен другим объектам. Слоты могут использоваться для приема сигналов, но они также являются нормальными функциями-членами.

замечания

Официальную документацию по этой теме можно найти здесь .

Маленький пример

Сигналы и слоты используются для связи между объектами. Механизм сигналов и слотов является центральной особенностью Qt и, вероятно, той частью, которая больше всего отличается от функций, предоставляемых другими фреймами.

Минимальный пример требует класса с одним сигналом, одним слотом и одним соединением:

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 задает новое значение. Мы можем проверить, как вызывается слот, распечатывая значение.

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

Наконец, наш файл проекта:

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

Новый синтаксис соединения Qt5

Обычный синтаксис connect который использует макросы SIGNAL и SLOT полностью работает во время выполнения, что имеет два недостатка: у него есть некоторые служебные данные во время выполнения (в результате также возникают издержки в двоичном размере), и проверка правильности времени компиляции не выполняется. В новом синтаксисе рассматриваются обе проблемы. Прежде чем проверять синтаксис в примере, нам лучше знать, что происходит в частности.

Предположим, мы строим дом, и мы хотим подключить кабели. Это именно то, что делает функция connect. Сигналы и слоты - это те, которые нуждаются в этом соединении. Дело в том, что если вы используете одно соединение, вам нужно быть осторожным в отношении дальнейших перекрывающихся соединений. Всякий раз, когда вы подключаете сигнал к слоту, вы пытаетесь сообщить компилятору, что всякий раз, когда выдается сигнал, просто вызывайте функцию слота. Именно это и происходит.

Вот пример 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();
}

Подсказка: старый синтаксис (макросы SIGNAL / SLOT ) требует, чтобы метакомпилятор Qt (MOC) запускался для любого класса, имеющего либо слоты, либо сигналы. С точки зрения кодирования это означает, что для таких классов должен быть макрос Q_OBJECT (что указывает на необходимость запуска MOC для этого класса).

С другой стороны, новый синтаксис по-прежнему требует MOC для работы сигналов, но не для слотов. Если у класса есть только слоты и нет сигналов, он не обязательно должен иметь макрос Q_OBJECT и, следовательно, не может вызывать MOC, что не только уменьшает окончательный двоичный размер, но также сокращает время компиляции (без вызова MOC и последующего вызова компилятора для сгенерированного *_moc.cpp файл).

Подключение перегруженных сигналов / слотов

Хотя во многих отношениях лучше, новый синтаксис соединения в Qt5 имеет одну большую слабость: подключение перегруженных сигналов и слотов. Чтобы позволить компилятору разрешить перегрузки, нам нужно использовать static_cast s для указателей функций-членов или (начиная с Qt 5.7). qOverload и friends:

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

Разъем для подключения нескольких оконных сигналов

Простой пример с несколькими окнами, использующий сигналы и слоты.

Существует класс MainWindow, который управляет представлением главного окна. Второе окно контролируется классом Веб-сайта.

Эти два класса подключены таким образом, что при нажатии кнопки на веб-сайте происходит что-то в MainWindow (текстовая метка изменяется).

Я сделал простой пример, который также находится на 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();
}

Состав проекта:

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

HEADERS  += mainwindow.h \
    website.h

FORMS    += mainwindow.ui \
    website.ui

Рассмотрим Uis, который должен быть составлен:

  • Главное окно: ярлык «текст» и кнопка «openButton»
  • Окно веб-сайта: кнопка "changeButton"

Таким образом, ключевыми точками являются соединения между сигналами и слотами и управление указателями или ссылками на окна.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow