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
Ua

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

  • Результат:84бали,
  • Рейтинг балів4
Ua

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

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

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

  • Результат:47бали,
  • Рейтинг балів-6
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Дмитрий
Дмитрий03 лютого 2025 р. 06:24
Создание deb-пакета. Как создать ярлык на рабочем столе после установки собственного deb-пакета? Всем привет. Сделал свой deb-пакет с программой. Всё устанавливается и работает. Ставлю по пути /usr/bin/my_application. Как для пользователя при установке пакета сразу создать ярлык на раб…
NW
Nayo Wai30 січня 2025 р. 09:22
не запускается компьютер!!! Не запускается компьютер (точнее работает блок , но сам монитор вообще жесть)В общем я ничего с интернета не скачивала в последнее время. На компе никаких левых пр…
n
nkly03 січня 2025 р. 02:52
Нужно запретить перемещение только некоторых итемов, остальные перемещать можно. Вопрос решен. Узнать QModelIndex элемента на который мы перетаскиваем другой элемент, можно с помощью функции indexAt(event->position().toPoint()) представления QTreeViev вызываемой в переопр…
M
Marsel16 серпня 2023 р. 14:26
OAuth2.0 через VK, получение email Спасибо большое за помощь и простите за то что отнял время своей невнимательностью.
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.

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