Дана стаття є найбільш повним описом сигналів і слотів в 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 також можуть мати аргументи