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 хостинг.

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

Комментарии

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

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

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 26 октября 2024 г. 8:37

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

  • Результат:80баллов,
  • Очки рейтинга4
m
  • molni99
  • 26 октября 2024 г. 8:29

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

  • Результат:20баллов,
  • Очки рейтинга-10
Последние комментарии
ИМ
Игорь Максимов22 ноября 2024 г. 19:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 октября 2024 г. 21:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 15:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 14:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 18:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
Evgenii Legotckoi
Evgenii Legotckoi24 июня 2024 г. 22:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 ноября 2024 г. 14:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject4 июня 2022 г. 10:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 октября 2024 г. 16:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

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