Evgenii Legotckoi
Evgenii Legotckoi4 ноября 2019 г. 16: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 хостинг.

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

Комментарии

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

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

  • Результат:70баллов,
  • Очки рейтинга1
РК

Qt - Тест 001. Сигналы и слоты

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

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

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
k
kmssr9 февраля 2024 г. 0:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 7:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 16:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 14:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 3:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
NSProject
NSProject23 июня 2024 г. 19:48
добавить qlineseries в функции А куда собстаенно делся Евгений раз на сайте такой бордак творится?
BlinCT
BlinCT5 мая 2024 г. 11:46
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii Legotckoi2 мая 2024 г. 20:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.
IscanderChe
IscanderChe30 апреля 2024 г. 10:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…
G
Gar22 апреля 2024 г. 11:46
Clipboard Как скопировать окно целиком в clipb?

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