Взаимодействие с объектами QML из C++
Все типы объектов QML являются производными от QObject, независимо от того, реализуются ли они внутри движка или определены сторонними источниками. Это означает, что движок QML может использовать Qt Механизм метаобъектов для динамической инициализации любого типа объекта QML и проверки созданных объектов.
Это полезно для создания объектов QML из кода C++, будь то для отображения визуализируемого объекта QML или для интеграции данных невизуальных объектов QML в приложение C++. После создания объекта QML его можно проверить из C++, чтобы читать и записывать свойства, вызывать методы и получать уведомления о сигналах.
Загрузка объектов QML из C++
Документ QML может быть загружен с помощью QQmlComponent или QQuickView. QQmlComponent загружает документ QML как объект C++, который затем можно изменять из кода C++. QQuickView также делает это, но так как QQuickView является производным классом от QWindow, загруженный объект также будет отображаться на экране; QQuickView обычно используется для интеграции отображаемого объекта QML в пользовательский интерфейс приложения.
Например, предположим, что существует файл MyItem.qml, который выглядит следующим образом:
import QtQuick 2.0
Item {
width: 100; height: 100
} Этот документ QML может быть загружен с помощью QQmlComponent или QQuickView с помощью следующего кода C++. Использование QQmlComponent требует вызова QQmlComponent::create() для создания новой инстанции компонента, а QQuickView автоматически создаёт инстанцию компонента, доступную через QQuickView::rootObject():
// Using QQmlComponent
QQmlEngine engine;
QQmlComponent component(&engine,
QUrl::fromLocalFile("MyItem.qml"));
QObject *object = component.create();
...
delete object; |
// Using QQuickView
QQuickView view;
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
QObject *object = view.rootObject(); |
Это object — инстанция компонента MyItem.qml, которая была создана. Теперь вы можете изменить свойства элемента, используя QObject::setProperty() или QQmlProperty::write():
object->setProperty("width", 500);
QQmlProperty(object, "width").write(500); Разница между QObject::setProperty() и QQmlProperty::write() заключается в том, что последняя также удалит привязку помимо установки значения свойства. Например, предположим, что присвоение width выше было привязкой к height:
width: height
Если значение height объекта Item изменится после вызова object->setProperty("width", 500), width будет обновлено снова, так как привязка остаётся активной. Однако, если height изменится после вызова QQmlProperty(object, "width").write(500), width не будет изменено, так как привязки больше нет.
В качестве альтернативы, вы можете привести объект к его фактическому типу и вызвать методы с проверкой типов на этапе компиляции. В данном случае базовым объектом MyItem.qml является Item, который определяется классом QQuickItem:
QQuickItem *item = qobject_cast<QQuickItem*>(object); item->setWidth(500);
Вы также можете подключиться к любым сигналам или вызвать методы, определённые в компоненте, используя QMetaObject::invokeMethod() и QObject::connect(). Подробности см. в разделах Вызов методов QML и Подключение к сигналам QML ниже.
Доступ к объектам QML через хорошо определённые интерфейсы C++
Лучший способ взаимодействия с QML из C++ — определение интерфейса для этого в C++ и доступ к нему в самом QML. С другими методами переработка кода QML может легко привести к разрыву взаимодействия QML/C++. Это также помогает понять взаимодействие кода QML и C++, поскольку взаимодействие, управляемое QML, может быть легче понято как пользователями, так и инструментами, такими как qmllint. Доступ к QML из C++ приведёт к коду QML, который нельзя понять без ручного подтверждения того, что никакой внешний код C++ не изменяет данный QML-компонент, и даже тогда масштабы доступа могут со временем измениться, сделав дальнейшее использование этой стратегии проблемой при обслуживании.
Чтобы QML управлял взаимодействием, сначала необходимо определить интерфейс C++:
class CppInterface : public QObject
{
Q_OBJECT
QML_ELEMENT
// ...
}; Используя подход, управляемый QML, с этим интерфейсом можно взаимодействовать двумя способами:
Сиглетоны
Один из вариантов — зарегистрировать интерфейс как сиглетон, добавив макрос QML_SINGLETON в интерфейс, сделав его доступным для всех компонентов. После этого интерфейс становится доступным через простое оператор импорта:
import my.company.module
Item {
Component.onCompleted: {
CppInterface.foo();
}
} Используйте этот подход, если вам нужен интерфейс в большем количестве мест, чем в корневом компоненте, так как простое передача объекта потребовало бы явной передачи его другим компонентам через свойство или использование медленного и не рекомендуемого метода использования неквалифицированного доступа.
Начальные свойства
Другой вариант — пометить интерфейс как несоздаваемый с помощью QML_UNCREATABLE и предоставить его корневому QML-компоненту, используя QQmlComponent::createWithInitialProperties() и необходимое свойство со стороны QML.
Ваш корневой компонент может выглядеть примерно так:
import QtQuick
Item {
required property CppInterface interface
Component.onCompleted: {
interface.foo();
}
} Пометка свойства как обязательного защищает компонент от создания без установки свойства интерфейса.
Вы затем можете инициализировать свой компонент так же, как описано в Загрузка объектов QML из C++, за исключением использования createWithInitialProperties():
component.createWithInitialProperties(QVariantMap{{u"interface"_qs, QVariant::fromValue<CppInterface *>(new CppInterface)}}); Этот метод следует предпочесть, если вы знаете, что ваш интерфейс нужен только корневому компоненту. Он также позволяет проще подключаться к сигналам и слотам интерфейса со стороны C++.
Если ни один из этих методов не подходит, вы можете изучить использование C++-моделей вместо этого.
Доступ к загруженным объектам QML по имени объекта
QML-компоненты представляют собой иерархии объектов с потомками, имеющими братьев и своих собственных потомков. Доступ к дочерним объектам QML-компонентов можно получить, используя свойство QObject::objectName с QObject::findChild(). Например, если корневой элемент в MyItem.qml имел дочерний элемент Rectangle:
import QtQuick 2.0
Item {
width: 100; height: 100
Rectangle {
anchors.fill: parent
objectName: "rect"
}
} К дочернему элементу можно получить доступ следующим образом:
QObject *rect = object->findChild<QObject*>("rect");
if (rect)
rect->setProperty("color", "red"); Обратите внимание, что объект может иметь несколько потомков с одинаковым objectName. Например, ListView создаёт несколько инстанций своего делегата, поэтому, если его делегат объявлен с определённым objectName, у ListView будет несколько потомков с одинаковым objectName. В этом случае для поиска всех потомков с совпадающим objectName можно использовать QObject::findChildren().
Предупреждение: Хотя доступ к объектам QML из C++ и их изменение возможны, это не рекомендуемый подход, за исключением целей тестирования и прототипирования. Одно из преимуществ интеграции QML и C++ — возможность реализации пользовательских интерфейсов в QML отдельно от логики C++ и бэкенда данных, и это нарушается, если сторона C++ начинает напрямую изменять QML. Такой подход также затрудняет изменение QML-интерфейса без влияния на его C++-часть.
Доступ к членам типа QML-объекта из C++
Свойства
Любые свойства, объявленные в QML-объекте, автоматически доступны из C++. Учитывая QML-элемент такого вида:
// MyItem.qml
import QtQuick 2.0
Item {
property int someNumber: 100
} Значение свойства someNumber можно установить и прочитать, используя QQmlProperty или QObject::setProperty() и QObject::property():
QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml");
QObject *object = component.create();
qDebug() << "Property value:" << QQmlProperty::read(object, "someNumber").toInt();
QQmlProperty::write(object, "someNumber", 5000);
qDebug() << "Property value:" << object->property("someNumber").toInt();
object->setProperty("someNumber", 100); Вы всегда должны использовать QObject::setProperty(), QQmlProperty или QMetaProperty::write() для изменения значения QML-свойства, чтобы убедиться, что движок QML информирован о изменении свойства. Например, предположим, что у вас есть пользовательский тип PushButton со свойством buttonText, которое внутренне отражает значение члена-переменной m_buttonText. Не рекомендуется изменять член-переменную напрямую, как показано ниже:
//bad code QQmlComponent component(engine, "MyButton.qml"); PushButton *button = qobject_cast<PushButton*>(component.create()); button->m_buttonText = "Click me";
Так как значение изменяется напрямую, это обходит мета-объектную систему Qt и движок QML не уведомляется о изменении свойства. Это означает, что привязки свойств к buttonText не будут обновляться, и обработчики onButtonTextChanged не будут вызываться.
Вызов QML-методов
Все QML-методы доступны в мета-объектной системе и могут быть вызваны из C++ с помощью QMetaObject::invokeMethod(). Вы можете указать типы для параметров и возвращаемого значения после двоеточия, как показано в примере кода ниже. Это может быть полезно, например, когда вы хотите подключить сигнал в C++ с определённой сигнатурой к QML-определённому методу. Если вы опустите типы, сигнатура C++ будет использовать QVariant.
Вот приложение C++, которое вызывает QML-метод с помощью QMetaObject::invokeMethod():
| QML |
// MyItem.qml
import QtQuick 2.0
Item {
function myQmlFunction(msg: string) : string {
console.log("Got message:", msg)
return "some return value"
}
} |
| C++ |
// main.cpp
QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml");
QObject *object = component.create();
QString returnedValue;
QString msg = "Hello from C++";
QMetaObject::invokeMethod(object, "myQmlFunction",
Q_RETURN_ARG(QString, returnedValue),
Q_ARG(QString, msg));
qDebug() << "QML function returned:" << returnedValue;
delete object; |
Обратите внимание на указанные параметры и типы возвращаемого значения после двоеточия. Вы можете использовать базовые типы и типы объектов в качестве имён типов.
Если тип опущен в QML, вы должны указать QVariant как тип с Q_RETURN_ARG() и Q_ARG() при вызове QMetaObject::invokeMethod().
Подключение к QML-сигналам
Все QML-сигналы автоматически доступны в C++, и их можно подключить с помощью QObject::connect(), как и любой обычный Qt C++-сигнал. В ответ, любой C++-сигнал может быть получен QML-объектом с помощью обработчиков сигналов.
Вот компонент QML со сигналом, названным qmlSignal, который испускается с параметром типа строка. Этот сигнал подключен к слоту объекта C++ с помощью QObject::connect(), таким образом, метод cppSlot() вызывается всякий раз, когда испускается qmlSignal.
// MyItem.qml
import QtQuick 2.0
Item {
id: item
width: 100; height: 100
signal qmlSignal(msg: string)
MouseArea {
anchors.fill: parent
onClicked: item.qmlSignal("Hello from QML")
}
} |
class MyClass : public QObject
{
Q_OBJECT
public slots:
void cppSlot(const QString &msg) {
qDebug() << "Called the C++ slot with message:" << msg;
}
};
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQuickView view(QUrl::fromLocalFile("MyItem.qml"));
QObject *item = view.rootObject();
MyClass myClass;
QObject::connect(item, SIGNAL(qmlSignal(QString)),
&myClass, SLOT(cppSlot(QString)));
view.show();
return app.exec();
} |
Тип объекта QML в параметре сигнала преобразуется в указатель на класс в C++:
// MyItem.qml
import QtQuick 2.0
Item {
id: item
width: 100; height: 100
signal qmlSignal(anObject: Item)
MouseArea {
anchors.fill: parent
onClicked: item.qmlSignal(item)
}
} |
class MyClass : public QObject
{
Q_OBJECT
public slots:
void cppSlot(QQuickItem *item) {
qDebug() << "Called the C++ slot with item:" << item;
qDebug() << "Item dimensions:" << item->width()
<< item->height();
}
};
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQuickView view(QUrl::fromLocalFile("MyItem.qml"));
QObject *item = view.rootObject();
MyClass myClass;
QObject::connect(item, SIGNAL(qmlSignal(QVariant)),
&myClass, SLOT(cppSlot(QVariant)));
view.show();
return app.exec();
} |
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.2/qtqml-cppintegration-interactqmlfromcpp.html