Evgenii Legotckoi
Evgenii Legotckoi04 листопада 2019 р. 05: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()
{
    // Збільшуємо лічильник і висилаємо сигнал з його значенням
    ++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 хостинг.

Вам це подобається? Поділіться в соціальних мережах!

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
d
  • dsfs
  • 26 квітня 2024 р. 04:56

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

  • Результат:80бали,
  • Рейтинг балів4
d
  • dsfs
  • 26 квітня 2024 р. 04:45

C++ - Тест 002. Константы

  • Результат:50бали,
  • Рейтинг балів-4
d
  • dsfs
  • 26 квітня 2024 р. 04:35

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

  • Результат:73бали,
  • Рейтинг балів1
Останні коментарі
k
kmssr08 лютого 2024 р. 18:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 01:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 10:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 08:38
Boost - статичне зв&#39;язування в проекті 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" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
IscanderChe
IscanderChe30 квітня 2024 р. 04:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…
G
Gar22 квітня 2024 р. 05:46
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil Academics20 квітня 2024 р. 07:45
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasov14 квітня 2024 р. 06:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев14 квітня 2024 р. 02:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь

Слідкуйте за нами в соціальних мережах