Spec-Zone.ru › Qt 5.11

Взаимодействие с объектами 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++ начнет напрямую изменять 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/archives/qt-5.11/qtqml-cppintegration-interactqmlfromcpp.html

Spec-Zone.ru

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