Сигналы, слоты и свойства

Определение и связь сигналов и слотов

Последнее обновление: 06.01.2024

Механизм сигналов и слотов представляет одну из отличительных особенностей Qt и позволяют сделать приложение отзывачивым, реагировать на действия пользователя, отслеживать различные события в приложении. Так, когда пользователь выполняет какое-либо действие с каким-либо элементом пользовательского интерфейса, должна быть выполнена определенная задача. Например, если пользователь нажимает кнопку "Закрыть" в верхнем правом углу окна, то ожидается, что окно закроется. То есть необходим механизм для отслеживания событий и реагирования на них. В среде Qt такой механизм предоставляют сигналы и слоты.

Сигнал — это сообщение, которое передается, чтобы сообщить об изменении состояния объекта. Сигнал может нести информацию о произошедшем изменении.

Слот — это специальная функция, вызываемая в ответ на определенный сигнал. Поскольку слоты — это функции, они содержат логику для выполнения определенного действия.

Встроенные виджеты Qt имеют множество предопределенных сигналов. Но также можно расширять имеющиеся классы и добавлять к ним свои собственные сигналы. Аналогичным образом можно добавить свои собственные слоты для обработки сигнала. Сигналы и слоты упрощают реализацию паттерна Observer (Наблюдатель), избегая при этом шаблонного кода.

Графически связь слотов и сигналов можно представить следующим образом:

Сигналы и слоты в Qt

Все классы, наследуемые от QObject или одного из его подклассов (например, QWidget), могут содержать сигналы и слоты. Для определения сигнала в классе применяется специальная секция signals:

class MyClass : public QObject
{
    Q_OBJECT
public: 
    MyClass(){}
signals:
    void signalName();  // определяем сигнал
};

Синтаксически сигнал представляет определение функции без тела. И для нее не надо определять реализацию.

В качестве слота может выступать потенциально любая функция, которая соответствует сигнатуре сигнала. Но для явного определения слота в классе можно использовать секцию slots:

Обычно сигналы генерируются объектами, когда они меняют свое состояние. Причем объект-генератор сигнала не знает и не заботится о том, получает ли другой объект сгенерированный сигнал. Благодаря такой композиции можно создавать независимые компоненты.

class MyClass : public QObject
{
    Q_OBJECT
public: 
    MyClass(){}
signals:
    void signalName();  // определяем сигнал
public slots:
    void slotName(){ некоторый код }  // определяем слот
};

При этом для одного сигнала можно подключить множество слотов. Аналогично один слот может обрабатывать несколько сигналов. Можно даже подключить сигнал напрямую к другому сигналу. (При этом второй сигнал будет генерироваться сразу же, как только будет излучен первый.)

Если к одному сигналу подключено несколько слотов, то функции-слоты будут выполняться в порядке подключения к сигналу.

Причем чтобы класс мог определять сигналы и слоты, класс не только должен наследоваться от QObject, но и внутри тела класса применять макрос Q_OBJECT. Специальный инструмент - Meta-Object Compiler (moc) генерирует дополнительный код для производных классов QObject. Инструмент считывает заголовочные файлы C++ и, если находит макрос Q_OBJECT, создает другой исходный файл C++ с метаобъектным кодом, который применяется для управления классами и объектами.

Чтобы сгенерировать сигнал, применяется специальный макрос emit:

emit signalName();

Установка связи между сигналами и слотами

Чтобы подключить сигнал к слоту, можно использовать функцию QObject::connect(), которая имеет ряд версий. Возьмем наиболее распространенную:

QMetaObject::Connection QObject::connect( 
    const QObject *senderObject, const char *signalName, 
    const QObject *receiverObject, const char *slotName, 
    Qt::ConnectionType type = Qt::AutoConnection)

Данная функция принимает следующие параметры:

  • senderObject: объект отправителя сигнала

  • signalName: название отправленного сигнала

  • receiverObject: объект-получатель сигнала

  • slotName: метод слота, который обрабатывает сигнал

  • type: тип устанавливаемого соединения. Он определяет, будет ли уведомление доставлено в слот немедленно или поставлено в очередь на потом. В Qt можно создать шесть различных типов соединений:

    • Qt::AutoConnection: тип соединения по умолчанию, определяется автоматически при генерации сигнала. Если и отправитель, и получатель находятся в одном потоке, то используется Qt::DirectConnection, иначе применяется Qt::QueuedConnection.

    • Qt::DirectConnection: в этом случае и сигнал, и слот находятся в одном потоке. Слот вызывается сразу после генерации сигнала.

    • Qt::QueuedConnection: в этом случае слот находится в разных потоках. Слот вызывается, как только управление возвращается в цикл обработки событий потока получателя.

    • Qt::BlockingQueuedConnection: аналогичен Qt::QueuedConnection за тем исключением, что поток сигнала блокируется до тех пор, пока слот не будет выполнен. Это соединение нельзя использовать, если отправитель и получатель находятся в одном потоке, чтобы избежать взаимоблокировки.

    • Qt::UniqueConnection: его можно комбинировать с любым из вышеупомянутых типов соединения, используя побитовую операцию ИЛИ. Применяется, чтобы избежать дублирования соединений. Соединение завершится неудачно, если оно уже существует.

    • Qt::SingleShotConnection: одноразовая обработка сигнала. В этом случае слот вызывается только один раз, и соединение разрывается после генерации сигнала. Данный тип можно использовать с другими типами соединений. Этот тип соединения был добавлен в Qt 6.0.

Существует несколько способов соединения сигналов и слотов. Наиболее часто используемый синтаксис выглядит следующим образом:

QObject::connect(this, SIGNAL(signalName()), this, SLOT(slotName()));

В данном случае при указании сигнала и функции слота применяются соответственно макросы SIGNAL() и SLOT(). Это наиболее старый синтаксис, применяемый с первых версий Qt.

В последних версиях Qt рекоммендуется применять другой синтаксис:

connect(sender, &MyClass::signalName, this, &MyClass::slotName);

В качестве слота также можно указать лямбда-выражение:

connect(sender, &MyClass::signalName, this, [=]() { sender->doSomething(); });

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

Сигнатуры сигналов и слотов могут содержать аргументы, и эти аргументы могут иметь значения по умолчанию. Cигнал можно подключить к слоту, если сигнал имеет как минимум столько же аргументов, сколько и слот, а также если существует возможность неявного преобразования между типами соответствующих аргументов. Возможные соединения сигнала и слота:

connect(sender, SIGNAL(signalName(int)), this, SLOT(slotName(int)));
connect(sender, SIGNAL(signalName(int)), this, SLOT(slotName()));
connect(sender, SIGNAL(signalName()), this, SLOT(slotName()));

Но в следующем случае соединение не будет установлено, так как слот имеет больше параметров, чем сигнал:

connect(sender, SIGNAL(signalName()), this, SLOT(slotName(int)));

Для разрыва соединения между сигналом и слотом применяется функция disconnect(). Она также имеет несколько различных версий. В простейшей форме она принимает соединение, которое надо разорвать:

bool QObject::disconnect(const QMetaObject::Connection &connection)
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850