Взаимодействие с объектами 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:
object->setProperty("width", 500);
QQmlProperty(object, "width").write(500); В качестве альтернативы, вы можете привести объект к его фактическому типу и вызвать методы с безопасностью на этапе компиляции. В этом случае базовым объектом 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.
Предупреждение: Хотя можно использовать C++, чтобы получить доступ к объектам QML и манипулировать ими в глубоких слоях древовидной структуры, мы рекомендуем не применять этот подход за пределами тестирования и прототипирования приложения. Одним из преимуществ интеграции QML и C++ является возможность реализовать пользовательский интерфейс QML отдельно от логики C++ и базы данных, а этот подход нарушается, если сторона C++ добирается до глубоких слоев компонентов QML для их прямого изменения. Это затруднит, например, замену компонента представления QML другим, если в новом компоненте отсутствует необходимый objectName. Лучше, чтобы реализация C++ знала как можно меньше об реализации пользовательского интерфейса QML и структуре объекта QML.
Доступ к членам типа объекта 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(). Параметры и возвращаемые значения, передаваемые из QML, всегда преобразуются в значения QVariant в C++.
Вот приложение C++, которое вызывает метод QML с помощью QMetaObject::invokeMethod():
| QML |
// MyItem.qml
import QtQuick 2.0
Item {
function myQmlFunction(msg) {
console.log("Got message:", msg)
return "some return value"
}
} |
| C++ |
// main.cpp
QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml");
QObject *object = component.create();
QVariant returnedValue;
QVariant msg = "Hello from C++";
QMetaObject::invokeMethod(object, "myQmlFunction",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, msg));
qDebug() << "QML function returned:" << returnedValue.toString();
delete object; |
Обратите внимание, что аргументы Q_RETURN_ARG() и Q_ARG() для QMetaObject::invokeMethod() должны быть указаны как типы QVariant, так как это универсальный тип данных, используемый для параметров и возвращаемых значений методов QML.
Подключение к сигналам 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(string msg)
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 используется в качестве параметра сигнала, параметр должен использовать var в качестве типа, а значение должно быть получено в C++ с использованием типа QVariant:
// MyItem.qml
import QtQuick 2.0
Item {
id: item
width: 100; height: 100
signal qmlSignal(var anObject)
MouseArea {
anchors.fill: parent
onClicked: item.qmlSignal(item)
}
} |
class MyClass : public QObject
{
Q_OBJECT
public slots:
void cppSlot(const QVariant &v) {
qDebug() << "Called the C++ slot with value:" << v;
QQuickItem *item =
qobject_cast<QQuickItem*>(v.value<QObject*>());
qDebug() << "Item dimensions:" << item->width()
<< item->height();
}
};
int main(int argc, char *argv[]) {
QApplication 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/archives/qt-5.6/qtqml-cppintegration-interactqmlfromcpp.html