QML - Урок 036. Работа с сигналами и слотами в QML

Qt, QML, Signal, Slot, cpp

Данная статья является наиболее полным описанием сигналов и слотов в QML по сравнению со всеми предыдущими статьями на этом сайте.

В этой статье я попытаюсь объяснить следующее при работе с Qt/QML + Qt/C++:

  • способы объявления сигналов и слотов, также вызываемых методов в C++ классе, который будет зарегистрирован в QML слое
  • способы подключения к сигналам классов объявленных в C++ в качестве контекста
  • работу с Q_PROPERTY, который также требует наличия сигналов и слотов
  • способы соединения сигналов и слотов в QML
  • и т.д.

Сигналы и слоты из C++ класса

Создадим свой первый класс, который будет работать с сигналами и слотами в QML. Это один из самых первых примеров, который я уже показывал, но повторю данный пример, чтобы статья была максимально полной.

В данном примере я хочу создать приложение, которое имеет одну кнпоку и по нажатию этой кнопки увеличивает счётчик, который находится внутри C++ класса. Данный C++ класс будет регистрироваться в качестве контекстного свойства в QML движке нашего приложения.

Внешний вид приложения будет следующий

AppCore.h

Объявление сигналов и слотов в C++ коде будет мало чем отличаться от классического Qt/C++.

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>


class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr);

signals:
    void sendToQml(int count);

public slots:
    void receiveFromQml();

private:
    int m_counter {0};
};

#endif // APPCORE_H

AppCore.cpp

Также как и реализация самих методов.

#include "AppCore.h"

AppCore::AppCore(QObject* parent) : QObject(parent)
{
}

void AppCore::receiveFromQml()
{
    // Увеличиваем счётчик и высылаем сигнал с его значением
    ++m_counter;
    emit sendToQml(m_counter);
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "AppCore.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    AppCore appCore;    // Создаём ядро приложения с сигналами и слотами

    QQmlApplicationEngine engine;
    QQmlContext *context = engine.rootContext();

    /* Загружаем объект в контекст для установки соединения,
     * а также определяем имя "appCore", по которому будет происходить соединение
     * */
    context->setContextProperty("appCore", &appCore);

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

main.qml

А вот теперь самое интересное. Как использовать объект, загруженный в контекст QML и как подключаться к его сигналам.

Как вы помните, мы загрузили объект в контекст QML под именем appCore , по нему мы и будем обращаться к данному объекту. А вот для подключения к сигналу, нам нужно будет использовать QML тип Connections .

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QML Signals and Slots")

    /* С помощью объекта Connections
     * Устанавливаем соединение с объектом ядра приложения
     * */
    Connections {
        target: appCore // Указываем целевой объект для соединения
        /* Объявляем и реализуем функцию, как параметр
         * объекта и с имененем похожим на название сигнала
         * Разница в том, что добавляем в начале on и далее пишем
         * с заглавной буквы
         * */
        onSendToQml: {
            labelCount.text = count // Устанавливаем счётчик в текстовый лейбл
        }
    }


    Label {
        id: labelCount
        text: "0"

        anchors.bottom: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottomMargin: 15
    }

    Button {
        text: qsTr("Increase counter")
        onClicked: appCore.receiveFromQml() // Вызов слота

        anchors.top: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
    }
}

Таким образом можно обратиться к объекту, который был загружен в контекст движка QML, вызвать его слот, и обработать сигнал от этого объекта.

Также не обязательно объявлять в данном случае метод receiveFromQml() в качестве слота. Данный метод можно объявить и как Q_INVOKABLE метод.

public:
    explicit AppCore(QObject *parent = nullptr);
    Q_INVOKABLE void receiveFromQml();

Использование Q_PROPERTY

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

Q_PROPERTY(int counter READ counter WRITE setCounter NOTIFY counterChanged)

В данном свойстве имеются следующие составляющие:

  • тип свойства, а также его имя: int counter , которые привязываются к переменной int m_counter внутри класса, такова логика кодогенерации в Qt
  • имя метода для чтения, совпадает с имененем свойства: counter
  • имя метода для установки значения: setCounter
  • сигнал, который сообщает об изменениях свойства: counterChanged

Также в данный макрос можно передавать ещё дополнительные параметры, но это выходит за рамки данной статьи. А также свойство может быть read only, то есть без сеттера.

А теперь посмотрим на полный код с использованием Q_PROPERTY

AppCore.h

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>


class AppCore : public QObject
{
    Q_OBJECT
public:
    Q_PROPERTY(int counter READ counter WRITE setCounter NOTIFY counterChanged)
    explicit AppCore(QObject *parent = nullptr);

    int counter() const;

public slots:
    void setCounter(int counter);

signals:
    void counterChanged(int counter);

private:
    int m_counter {0};
};

#endif // APPCORE_H

AppCore.cpp

#include "AppCore.h"

AppCore::AppCore(QObject* parent) : QObject(parent)
{

}

int AppCore::counter() const
{
    return m_counter;
}

void AppCore::setCounter(int counter)
{
    if (m_counter == counter)
        return;

    m_counter = counter;
    emit counterChanged(m_counter);
}

main.qml

Здесь вы увидите, что соединение свойства и обращение к нему стало стало проще благодаря декларативному стилю QML кода. Конечно, не всегда можно использовать свойства, иногда нужно использовать просто сигналы, слоты и Q_INVOKABLE методы. Но для подобных переменных, как counter, свойства скорее всего будут гораздо удобнее.

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QML Signals and Slots")

    Label {
        id: labelCount
        text: appCore.counter

        anchors.bottom: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottomMargin: 15
    }

    Button {
        text: qsTr("Increase counter")
        onClicked: ++appCore.counter

        anchors.top: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
    }
}

Подключение сигналов внутри QML файлов

А теперь рассмотрим вариант подключения сигналов и слотов (функций) внутри файлов QML. Здесь уже не будет никакого кода на C++.

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12

Window {
    id: mainWindow
    visible: true
    width: 640
    height: 480
    title: qsTr("QML Signals and Slots")

    // Свойство счётчика
    property int counter: 0

    // Метод для манипуляций со счётчиком
    function inrementCounter()
    {
        ++counter;
    }

    Label {
        id: labelCount
        text: mainWindow.counter

        anchors.bottom: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottomMargin: 15
    }

    Button {
        id: button
        text: qsTr("Increase counter")

        anchors.top: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter

        Component.onCompleted: {
            // Когда кнопка создана, то подключим сигнал клика по кнопке
            // к методу для увеличения счетчика в окне приложения
            button.clicked.connect(mainWindow.inrementCounter)
        }
    }
}

Помимо прочего можно использовать и отключение сигналов от слотов

button.clicked.disconnect(mainWindow.inrementCounter)

Подключение сигнала к сигналу

Также в QML по прежнему имеется возможность подключать сигналу к сигналу, как и в Qt/C++. Посмотрите на следующий искусственный пример.

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12

Window {
    id: mainWindow
    visible: true
    width: 640
    height: 480
    title: qsTr("QML Signals and Slots")

    // Объявляем сигнал надатия кнопки в окне приложения
    signal buttonClicked;

    Label {
        id: labelCount
        text: counter

        anchors.bottom: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottomMargin: 15

        // Свойство счётчика
        property int counter: 0

        // Метод для манипуляций со счётчиком
        function inrementCounter()
        {
            ++counter;
        }
    }

    Button {
        id: button
        text: qsTr("Increase counter")

        anchors.top: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter

        Component.onCompleted: {
            // Когда кнопка создана, то подключим сигнал клика по кнопке
            // к методу для увеличения счетчика в окне приложения
            button.clicked.connect(mainWindow.buttonClicked)
        }
    }

    Component.onCompleted: {
        buttonClicked.connect(labelCount.inrementCounter)
    }
}

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

Использование переменных в сигналах

Также в QML имеется возможность использования переменных в сигналах.

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12

Window {
    id: mainWindow
    visible: true
    width: 640
    height: 480
    title: qsTr("QML Signals and Slots")

    // Сигнал с аргументом
    signal setCounter(var number);

    Label {
        id: labelCount
        text: counter

        anchors.bottom: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottomMargin: 15

        // Свойство счётчика
        property int counter: 0

        // Метод для манипуляций со счётчиком, принимает аргумент
        function setCounter(number)
        {
            counter = number;
        }
    }

    Button {
        id: button
        text: qsTr("Increase counter")

        anchors.top: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter

        onClicked: {
            // Вызываем сигнал окна приложения для установки счётчика с указанием новой величины счётчика
            mainWindow.setCounter(labelCount.counter + 1)
        }
    }

    Component.onCompleted: {
        setCounter.connect(labelCount.setCounter)
    }
}

Заключение

По большей части вся данная статья укладывается в несколько тезисов:

  • В C++ для взаимодействия с QML слоем вы можете использовать сигналы, слоты, Q_INVOKABLE методы, а также создавать свойства с использованием макроса Q_PROPERTY
  • Для того, чтобы реагировать на сигналы из объектов, можно использовать QML тип Connections
  • Q_PROPERTY подчиняются декларативному стилю QML и при изменении свойства могут автоматически устанавливать новые значения, если свойство было добавлено к какому-либо объекту в QML. В данном случае сигнал слотовые соединения устанавливаются автоматически.
  • В QML можно соединять и отсоединять сигнал/слотовые соединения используя следующий синтаксис:
    • object1.signal.connect(object2.slot)
    • object1.signal.disconnect(object2.slot)
  • Сигналы в QML также можно подключать к другим сигналам, как жто делается и в Qt/C++
  • Сигналы в QML также могут иметь аргументы
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
- блог компании
Поддержать автора Donate

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Donate

Здравствуйте, уважаемые пользователи EVILEG !!!

Если сайт вам помог, то поддержите разработку сайта финансово, пожалуйста.

Вы можете сделать это следующими способами:

Спасибо, Евгений Легоцкой

M
26 февраля 2020 г. 21:48
Metalhaker

C++ - Тест 001. Первая программа и типы данных

  • Результат:60баллов,
  • Очки рейтинга-1
a
25 февраля 2020 г. 5:40
ayb

C++ - Тест 005. Структуры и Классы

  • Результат:83баллов,
  • Очки рейтинга4
ДЗ
24 февраля 2020 г. 14:47
Дмитрий Злобин

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:40баллов,
  • Очки рейтинга-8
Последние комментарии
24 февраля 2020 г. 3:54
Евгений Легоцкой

Добрый день. Там будет url, на который указывает ссылка тега a в пагинаторе, если правильно помню )) Написал этот код и забыл.
B
24 февраля 2020 г. 0:37
BahaMeirman

Евгений Здравствуйте! Не могу понять вот эту часть кода: url: jQuery(this).attr('action') наверное здесь должен быть путь к url, тогда 'action' на какой url указывает?
17 февраля 2020 г. 3:22
Евгений Легоцкой

Добрый день. Это кастомный тег, помещается в файл, который находится в каталоге templatetags myapp/ templatetags/ myapp.py
B
16 февраля 2020 г. 13:36
BahaMeirman

Добрый вечер! Монжно по подробней о теге get_companion? ссылка не работает.
Сейчас обсуждают на форуме
28 февраля 2020 г. 9:08
Pavel.K

Нужно реализовать драг N дроп из одной части экрана в другую (из 1й listview в другую) Я думаю реализовать это с помощью копирования текущего (выбранного) delegate , кто-нибудь сталкив…
m
27 февраля 2020 г. 14:12
mihenze

Попробовал у себя дома. Все работает. Вот прикладлываю проект. использовал QT5.14, MinGW5.3.0, но объединение работало и на более ранних версиях TestWowdTable.rar
27 февраля 2020 г. 10:43
mkdir Некрасов

Блаодарю! Вы очень помогли
v
27 февраля 2020 г. 9:00
vlaaad20

Проблема решилась путем следующих манипуляций: 1. Небольшое изменение url (на https://identitysso-cert.betfair.com/api/certlogin) 2. Установки OpenSSL 32-bit (разрядность должна соотве…
ДА
27 февраля 2020 г. 2:39
Денис Аргер

Добрый день!Стоит задача вносить изменения в заранее подготовленый файл. Места изменений заранее известны. Подскажите, пожалуйста.
EVILEG
О нас
Услуги
© EVILEG 2015-2019
Рекомендует хостинг TIMEWEB