Динамически загружать QQuickWindow вместо QQuickWidget в модульном тесте Qt C++

В нашем проекте у нас есть модульные тесты C++ для источников QML. Он использует следующий код для динамической загрузки компонента для дальнейшей обработки.

class MyTest {
    ...
    QScopedPointer<QQuickWidget> quickWidget;
    QQuickItem* root = nullptr;

    void setQmlSource(const QString& source)
    {
        quickWidget.reset(new QQuickWidget);
        quickWidget->rootContext()->engine()->addImportPath("qrc:/qml");
        quickWidget->setSource(QUrl::fromUserInput(source));
        root = quickWidget->rootObject();
    }
}

Отлично работал для таких компонентов qml:

my.qml:

Rectangle {
   ...
}

Однако, когда я завернул компонент в Dialog

Dialog {
    ...
    Rectangle {
       ...
    }
}

он перестал работать:

Ошибка: QQuickWidget поддерживает загрузку только корневых объектов, производных от QQuickItem.

Что ожидается, поскольку Dialog является QQuickWindow. Однако при попытке загрузить QQuickItem через QQuickView вот так https://doc.qt.io/qt-5/qquickview.html#details:

void MyTest::setQmlWindow(const QString& source)
{
    QQuickView *view = new QQuickView;
    view->rootContext()->engine()->addImportPath("qrc:/qml");
    view->setSource(QUrl::fromUserInput(source));
    root = view->rootObject();
}

Сбой с вышеуказанной ошибкой, а также. И загрузка через QQmlApplicationEngine, как здесь https://stackoverflow.com/a/23936741/630169:

void MyTest::setQmlWindow(const QString& source)
{
    engine = new QQmlApplicationEngine;
    //engine->addImportPath("qrc:/qml");
    engine->load(QUrl::fromUserInput(source));
    QObject *myObject = engine->rootObjects().first();;
    QQuickWindow *window = qobject_cast<QQuickWindow*>(myObject);
    root = window->contentItem();
}

вылетает с другой ошибкой:

QQmlApplicationEngine не удалось загрузить компонент
QWARN : MyTest::myMethodTest() модуль "mynamespace.mymodule" не установлен
QWARN : MyTest::myMethodTest() модуль "mynamespace.mymodule" не установлен
...

Почему view->setSource() правильно загружает эти модули для элемента Rectangle, а QQmlApplicationEngine не работает для того же исходного кода qml элемента, но заключенного в Dialog?

Примечание. эти модули написаны на C++ и нормально загружаются с view->setSource().

Если я попытаюсь использовать и загрузить через QQmlComponent, как указано в документации: https://doc.qt.io/qt-5/qqmlcomponent.html#details:

void MyTest::setQmlWindow(const QString& source)
{
    engine = new QQmlApplicationEngine;
    //engine->addImportPath("qrc:/qml");
    QQmlComponent *component = new QQmlComponent(engine, QUrl::fromUserInput(source));
    component->loadUrl(QUrl::fromUserInput(source));
    QQuickWindow *window = qobject_cast<QQuickWindow*>(component->create());
    root = window->contentItem();
}
  • затем другая ошибка:
    #P14#

если engine->addImportPath() не вызывается, и сбой с

Местонахождение: [Неизвестный файл(0)]

ошибка при вызове engine->addImportPath().

Как правильно загрузить Dialog (QQuickWindow) и получить root QQuickItem в C++ для тестирования? Любые идеи? Спасибо!


person Aleksey Kontsevich    schedule 25.01.2020    source источник


Ответы (1)


Может быть, ваш вопрос касается QQuickWindow/QQuickView? Как QQuickWidget относится к проблеме? Это просто оболочка для QQuickWindow. В любом случае, вместо этого я бы использовал некоторую оболочку динамической загрузки QML. Идея состоит в том, чтобы загружать каждый компонент последовательно. Пример ниже просто иллюстрирует идею. Если вам не нужно функциональное тестирование, просто удалите таймеры и подключения и вместо этого поиграйте с Loader.Ready.

main.qml

import QtQuick 2.5
import QtQuick.Window 2.2

Window {
    id: window
    visible: true
    width: 640
    height: 480
    title: qsTr("Test window")
    property var objects: ["Item1.qml", "Item2.qml", "Item3.qml"]
    property int currentObject: 0
    property var testResults: [0, 0]
    property bool testRunning: false

    onTestRunningChanged: {
        if(window.testRunning)
            console.log("start testing");
        else
            console.log("all the objects has tested. passed: " + window.testResults[0] + ", failed: " + window.testResults[1]);
    }

    function loadObject() {
        try
        {
            loader.source = window.objects[window.currentObject];
        }
        catch(ex) {}
    }

    function checkNext(prevStatus) {
        if(!window.testRunning)
            return;

        window.testResults[prevStatus ? 0 : 1]++;
        timer.running  = false;
        console.log(window.objects[window.currentObject] + ":" + ((prevStatus) ? "passed" : "failed"))
        if(window.currentObject === window.objects.length - 1) {
            window.testRunning = false;
        } else {
            window.currentObject ++;
            loadObject();
            timer.start();
        }
    }

    Loader {
        id: loader
        anchors.centerIn: parent
        onStatusChanged: {
            if (loader.status == Loader.Error) {
                loader.source = "";
                checkNext(false);
            }
        }
    }

    Connections {
        target: loader.item
        onTestPassed: checkNext(true);
    }

    Timer {
        id: timer
        interval: 5000
        repeat: false
        onTriggered: checkNext(false);
    }

    Component.onCompleted: {
        window.testRunning = true;
        loadObject();
    }
}

Item1.qml

import QtQuick 2.0
import QtQuick.Controls 2.5

Dialog {
    id: item
    signal testPassed(bool value);
    title: "Dialog test"
    modal: true
    standardButtons: Dialog.Ok
    visible: true
    onAccepted: item.testPassed(true);
}

Item2.qml

import QtQuick 2.5
import QtQuick.Controls 2.5

Rectangle {
    id: item
    signal testPassed(bool value);

    width: 200
    height: 200
    color: "orange"

    Button {
        anchors.centerIn: parent
        text: "Test"
        onClicked: item.testPassed(true);
    }
}

Item3.qml

import QtQuick 2.5

Item {
    Itemssss {

    }
}
person folibis    schedule 25.01.2020
comment
Но мне нужно создать объект на С++, в модульном тесте, а не в QML. - person Aleksey Kontsevich; 25.01.2020
comment
Почему именно на С++? Я имею в виду, что вам следует максимально избегать создания компонентов QML на C++. В любом случае, таким же образом вы можете использовать вместо него QQmlComponent. - person folibis; 26.01.2020
comment
Пожалуйста, прочитайте мой первоначальный вопрос: мне нужен модульный тест, поэтому загружайте туда компоненты QML. А также приложение QML, инициализированное и загруженное таким же образом - через QQmlApplicationEngine в C++;) - person Aleksey Kontsevich; 26.01.2020
comment
О QQmlComponent - пытался с тем же результатом и ошибкой: QQmlComponent: Component is not ready, если addImportPath() не вызывается, и сбой с ошибкой Loc: [Unknown file(0)] при вызове. - person Aleksey Kontsevich; 26.01.2020
comment
Хорошо, вы не можете загрузить Dialog в качестве корневого элемента, сообщение об ошибке вполне понятно. И QQuickWidget, и QQuickWindow требуют, чтобы корневой элемент был потомком QQuickItem. Использование QQmlComponent, конечно, не поможет. Вы можете попробовать использовать `QQuickWindow` вместо QQuickWidget - person folibis; 26.01.2020
comment
Я, конечно, использую QQuickWindow, но его можно было загрузить только через QQmlComponent: component = new QQmlComponent(&engine); component->loadUrl(url); Тоже не повезло - см. вопрос, я только что обновил его. - person Aleksey Kontsevich; 26.01.2020
comment
Загрузка компонента с помощью QQmlComponent::loadUrl по умолчанию является асинхронной операцией. Вы должны либо установить QQmlComponent::PreferSynchronous, либо переключиться на statusChanged и создать компонент, когда он будет QQmlComponent::Ready - person folibis; 26.01.2020
comment
Да, спасибо, наверное. Однако на данный момент это решено путем удаления Dialog (отмененные изменения) и повторного использования quickWidget->setSource(), что работает. - person Aleksey Kontsevich; 26.01.2020