Эта статья является наиболее полным описанием сигналов и слотов в 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 также могут иметь аргументы