Взаимодействие с объектами 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. В этом случае, для поиска всех дочерних элементов с совпадающим objectName можно использовать QObject::findChildren().
Предупреждение: Хотя доступ к объектам QML из C++ и их изменение возможны, это не рекомендуется, за исключением целей тестирования и прототипирования. Одним из преимуществ интеграции QML и C++ является возможность реализации пользовательских интерфейсов в QML отдельно от логики C++ и бэкенда с данными, и это нарушается, если сторона C++ начинает напрямую изменять QML. Такой подход также усложняет изменение QML-UI без влияния на его 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); Для изменения значения QML-свойства всегда следует использовать QObject::setProperty(), QQmlProperty или QMetaProperty::write(), чтобы убедиться, что движок 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, то при вызове QMetaObject::invokeMethod необходимо указать QVariant в качестве типа с помощью Q_RETURN_ARG() и Q_ARG().
Подключение к 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-5.14/qtqml-cppintegration-interactqmlfromcpp.html