Spec-Zone.ru › Qt 5.13

Взаимодействие с объектами QML из C++

Все типы объектов QML являются производными от QObject, независимо от того, реализованы ли они внутри движка или определены сторонними источниками. Это означает, что движок QML может использовать систему метаобъектов Qt Meta Object System для динамической инициализации любого типа объекта 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(). Параметры и возвращаемые значения методов, переданные из 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/qt-5.13/qtqml-cppintegration-interactqmlfromcpp.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API