Incubating QML component through C++ not working, maybe wrong QQmlContext?
-
I've been trying to use QQmlIncubator to asynchronously create a component from C++, the same way that
Component.incubateObject()
does. The object is technically created, but it is not visible, and produces many errors, like it's not able to access certain variables/properties that it otherwise would be able to if I created it from QML usingComponent.incubateObject()
.qrc:/sonabona/pages/LibraryPage.qml:240: TypeError: Cannot read property 'scanning' of undefined qrc:/sonabona/pages/LibraryPage.qml:237: TypeError: Cannot read property 'scanning' of undefined qrc:/sonabona/pages/LibraryPage.qml:260: TypeError: Cannot read property 'mode' of undefined qrc:/sonabona/pages/LibraryPage.qml:269: TypeError: Cannot read property 'mode' of undefined qrc:/sonabona/pages/LibraryPage.qml:255: TypeError: Cannot read property 'mode' of undefined qrc:/sonabona/pages/LibraryPage.qml:282: TypeError: Cannot read property 'mode' of undefined qrc:/bon/imports/bon/layout/List.qml:64: TypeError: Value is undefined and could not be converted to an object
All of these variables are defined, and these errors do not show up when created from QML. These variables are outside the created object itself. It seems as if it is being created in an incorrect scope, such that it cannot find these variables. I'm guessing that QQmlContext is what handles that? But I don't know how to set it to the correct context.
The QML side is pretty simple, I just have a PageIncubator QML type defined from C++ that handles the loading/unloading of pages. The
pageContentComponent
variable is used to set the component to be shown, and thepageContentContainer
variable is a reference to whatever item will be used as the parent item of the page. I have it set to an Item that fills the window./* ... */ property PageIncubator pageIncubator: PageIncubator { pageContentComponent: root.pageContentComponent //is set whenever a page is opened pageContentContainer: _pageContentContainer //reference to an item that takes up most of the window } /* ... */
In the C++ side, I have the PageIncubator class which defines the QML type, and a PageIncubatorPrivate class which extends QQmlIncubator. I'm trying to do it the same way that Qt/QML's
Component.incubateObject()
function does, and am reading the source code at https://github.com/qt/qtdeclarative/blob/v6.5.1/src/qml/qml/qqmlcomponent.cpp#L1800 , but am having trouble finding exactly what it's doing differently.PageIncubator.h
#pragma once #include <QObject> #include <QQmlEngine> #include <QQuickItem> #include <QQmlIncubator> class PageIncubatorPrivate; class PageIncubator : public QObject { Q_OBJECT QML_ELEMENT Q_PROPERTY(QQmlComponent* pageContentComponent READ pageContentComponent WRITE setPageContentComponent RESET resetPageContentComponent NOTIFY pageContentComponentChanged) Q_PROPERTY(QQuickItem* pageContentContainer MEMBER m_pageContentContainer NOTIFY pageContentContainerChanged REQUIRED) friend class PageIncubatorPrivate; public: explicit PageIncubator(QObject *parent = nullptr); signals: void pageContentComponentChanged(); void pageContentContainerChanged(); public slots: QQmlComponent* pageContentComponent(); void setPageContentComponent(QQmlComponent* newComponent); void resetPageContentComponent(); private: QQuickItem* m_pageContentContainer; QQmlComponent* m_pageContentComponent; PageIncubatorPrivate* incubator; };
PageIncubator.cpp (irrelevant logs and comments removed)
#include "pageincubator.h" #include <QQmlContext> #include <QGuiApplication> class PageIncubatorPrivate : public QQmlIncubator { public: PageIncubatorPrivate(PageIncubator* newPageIncubator, QQmlIncubator::IncubationMode mode = Asynchronous) : QQmlIncubator(mode) { pageIncubator = newPageIncubator; } void setInitialState(QObject *o) override { o->setParent(pageIncubator->m_pageContentContainer); QVariant containerVariant = QVariant::fromValue(pageIncubator->m_pageContentContainer); o->setProperty("anchors.fill",containerVariant); } PageIncubator* pageIncubator; }; PageIncubator::PageIncubator(QObject *parent) : QObject{parent} { incubator = new PageIncubatorPrivate(this, QQmlIncubator::Asynchronous); } QQmlComponent* PageIncubator::pageContentComponent() { return m_pageContentComponent; } void PageIncubator::setPageContentComponent(QQmlComponent* newComponent) { incubator->clear(); if (incubator->object()) { incubator->object()->deleteLater(); } QQmlContext context = QQmlEngine::contextForObject(m_pageContentContainer); m_pageContentComponent = newComponent; m_pageContentComponent->create(*incubator, &context); while (!incubator->isReady()) { QCoreApplication::processEvents(QEventLoop::AllEvents, 50); } QObject *object = incubator->object(); } void PageIncubator::resetPageContentComponent() { incubator->clear(); if (incubator->object()) { incubator->object()->deleteLater(); } }
Again, I'm guessing it has something to do with the QQmlContext. I am using the QQmlContext of the parent object (the pageContentContainer), passing that to the
QQmlComponent::create()
function. Is this the right QQmlContext to set it to? If not, what do I need to set it to? Am I doing it in the wrong place, or setting it the wrong way?Or maybe I am setting the parent item incorrectly? Although, when I print out information about the created object, the component, and the incubator, it says that there are no errors, the parent object is set correctly, and it is loaded fine. So I don't think it's a problem with the parent item.
qDebug() << pageIncubator->m_pageContentContainer << o->parent() << pageIncubator->m_pageContentComponent->errors() << pageIncubator->m_pageContentComponent->errorString();
insetInitialState
returnsQQuickItem(0x2b6f600, parent=0x2b6db40, geometry=355,60 1085x564) QQuickItem(0x2b6f600) QList() ""
To be clear, I used to use
pageContentComponent.incubateObject(pageContentContainer, {"anchors.fill": pageContentContainer});
from QML/JS, and this worked fine. Well, not exactly, it had some weird problems, which is why I'm trying to incubate it from C++ instead. I mean that it loaded the component fine, it appeared on the screen and didn't have any weird variable resolution/scope errors.