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
OI
  • Ora Iro
  • 24 грудня 2024 р. 14:38

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

  • Результат:40бали,
  • Рейтинг балів-8
AD

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

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

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

  • Результат:80бали,
  • Рейтинг балів4
Останні коментарі
ИМ
Игорь Максимов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
ИМ
Игорь Максимов05 жовтня 2024 р. 14:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 18:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Donald Randolph
Donald Randolph30 грудня 2024 р. 10:59
Personal Injury lawyer Santa Monica As an experienced Santa Monica personal injury lawyer, Donald C. Randolph has recovered over $100 Million in verdicts and settlements for our clients. In severe injury cases, this compensation i…
Nirvana Yoga School
Nirvana Yoga School30 грудня 2024 р. 13:13
OAuth2.0 через VK, получение email Nirvana Yoga School is one of the most trusted and reputed traditional Rishikesh yoga courses , India certified by Yoga Alliance, USA. We aim to spread traditional yoga teachings so t…
s
sripark30 грудня 2024 р. 12:47
Mobile app development company in Chennai A Mobile app development company in Chennai focuses on creating personalized mobile applications to meet various business requirements. These companies offer a full range of services,…
a
amit8830 грудня 2024 р. 12:45
Excel in Exams with PSLE Maths Tuition Singapore Preparing for the PSLE can be challenging, but the right guidance makes all the difference. PSLE Maths tuition Singapore offers personalized coaching to help students master key concepts, improv…
a
awinash6230 грудня 2024 р. 12:23
Unlock Your Potential with the Certified Public Accountant Credential" Becoming a Certified Public Accountant (CPA) is a career milestone that opens doors to unparalleled opportunities in the world of accounting and finance. This globally recognized qualification s…

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