Взаимодействие с объектами 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 по имени объекта
Компоненты 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. В этом случае QObject::findChildren() может использоваться для поиска всех потомков с совпадающим objectName.
Предупреждение: Хотя доступ к объектам QML из C++ и их изменение возможны, это не рекомендуется, за исключением целей тестирования и прототипирования. Одним из преимуществ интеграции QML и C++ является возможность реализации пользовательских интерфейсов в QML, независимо от логики C++ и базы данных на стороне 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.0/qtqml-cppintegration-interactqmlfromcpp.html