Evgenii Legotckoi
Evgenii Legotckoi4 ноября 2019 г. 5:15

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

Эта статья является наиболее полным описанием сигналов и слотов в 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()
{
    // We increase the counter and send a signal with its value
    ++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;    // Create the application core with signals and slots

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

    /* We load the object into the context to establish the connection,
     * and also define the name "appCore" by which the connection will occur
     * */
    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")

    /* Using the Connections Object
     * Establish a connection with the application core object
     * */
    Connections {
        target: appCore // Specify the target to connect
        /* Declare and implement the function as a parameter
         * object and with a name similar to the name of the signal
         * The difference is that we add on at the beginning and then write
         * capitalized
         * */
        onSendToQml: {
            labelCount.text = count // Set the counter to the text label
        }
    }


    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

Вы также можете передать этому макросу дополнительные параметры, но это выходит за рамки данной статьи. А также свойство может быть только для чтения, то есть без сеттера.

Теперь посмотрите на полный код, используя 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. Но для таких переменных, как счетчик, свойства, вероятно, будут намного удобнее.

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")

    // Counter property
    property int counter: 0

    // Method for counter manipulation
    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: {
            // When the button is created, then connect the click signal on the button
            // to the method for increasing the counter in the application window
            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")

    // Announcing a button click signal in the application window
    signal buttonClicked;

    Label {
        id: labelCount
        text: counter

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

        // Counter property
        property int counter: 0

        // Method for counter manipulation
        function inrementCounter()
        {
            ++counter;
        }
    }

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

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

        Component.onCompleted: {
            // When the button is created, then connect the click signal on the button
            // to the method for increasing the counter in the application window
            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 with argument
    signal setCounter(var number);

    Label {
        id: labelCount
        text: counter

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

        // Counter property
        property int counter: 0

        // Method for counter manipulation, takes an argument
        function setCounter(number)
        {
            counter = number;
        }
    }

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

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

        onClicked: {
            // We call the signal of the application window for installing the counter indicating the new counter value
            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 хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

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

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

  • Результат:30баллов,
  • Очки рейтинга-10
ПЩ

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

  • Результат:80баллов,
  • Очки рейтинга4
d

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

  • Результат:50баллов,
  • Очки рейтинга-4
Последние комментарии
k
kmssr8 февраля 2024 г. 18:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 1:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 10:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 8:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik18 декабря 2023 г. 21:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
P
Pisych27 февраля 2023 г. 4:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 января 2024 г. 11:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCT27 декабря 2023 г. 8:57
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
Дмитрий10 января 2024 г. 4:18
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii Legotckoi12 декабря 2023 г. 6:48
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

Следите за нами в социальных сетях