Tying a context to the lifetime of a qml component?
-
I have been trying to understand contexts. I found that if I add something to the root context via setContextProperty, then delete that object, and then try to access that property via qml it will crash the program. I had thought context property lifetime was tied to the object. This is not true. So I started experimenting with sub contexts (contexts based on root). I want to tie the lifetime of the context to a qml component. Most of our apps just use the global context, but I am concerned about the robustness of this approach. So I created the following program to test this:
main.cpp:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "contextcontainingclass.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); //ContextContainingClass* contextclass1 = new ContextContainingClass; NoContextContainingClass* nocontextclass1 = new NoContextContainingClass; ContextLauncher cl; QQmlApplicationEngine engine; auto context = engine.rootContext(); cl.setContext(context); context->setContextProperty("contextLauncher", &cl); //contextclass1->setContext(context); nocontextclass1->setContext(context); const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); delete nocontextclass1; // will cause crash when evaluated again in qml return app.exec(); }
main.qml:
import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 Window { id: window visible: true width: 640 height: 480 title: qsTr("Hello World") property int evalvar: 1 Column { anchors.fill: parent Text { width: 100 height: 20 //text: window.evalvar ? contextContaining.name : "nada" text: "dummy1" } Text { width: 100 height: 20 text: window.evalvar ? nocontextContaining.name : "nada" //text: "dummy2" } Button { text: "Eval" onClicked: { window.evalvar += 1 } } Button { text: "Launch sub window with context" onClicked: { console.log("launching") contextLauncher.launchWithContext("qrc:/SubView.qml") } } } }
context creation objects
contextcontainingclass.h:#ifndef CONTEXTCONTAININGCLASS_H #define CONTEXTCONTAININGCLASS_H #include <QObject> #include <QQmlContext> class ContextContainingClass : public QObject { Q_OBJECT Q_PROPERTY(QString name MEMBER m_name NOTIFY nameChanged) public: explicit ContextContainingClass(QObject *parent = nullptr); void setContext(QQmlContext* root); QQmlContext* getContext(); signals: void nameChanged(QString name); protected: QString m_name = "ContextContainingClass"; QQmlContext* m_context; }; class NoContextContainingClass : public QObject { Q_OBJECT Q_PROPERTY(QString name MEMBER m_name NOTIFY nameChanged) public: explicit NoContextContainingClass(QObject *parent = nullptr); void setContext(QQmlContext* root); signals: void nameChanged(QString name); protected: QString m_name = "NoContextContainingClass"; }; class ContextLauncher : public QObject { Q_OBJECT public: explicit ContextLauncher(QObject *parent = nullptr); void setContext(QQmlContext* root); signals: public slots: void launchWithContext(QUrl qmlfile); protected: QQmlContext* m_context; }; #endif // CONTEXTCONTAININGCLASS_H
contextcontainingclass.cpp:
#include "contextcontainingclass.h" #include <QQmlComponent> #include <QQmlApplicationEngine> #include <QQuickItem> //#include <QQmlEngine> #include <QDebug> ContextContainingClass::ContextContainingClass(QObject *parent) : QObject(parent) , m_context(nullptr) { } void ContextContainingClass::setContext(QQmlContext *root) { m_context = new QQmlContext(root, this); // set owner to object m_context->setContextProperty("contextContaining", this); } QQmlContext *ContextContainingClass::getContext() { return m_context; } NoContextContainingClass::NoContextContainingClass(QObject *parent) : QObject(parent) { } void NoContextContainingClass::setContext(QQmlContext *root) { root->setContextProperty("nocontextContaining", this); } ContextLauncher::ContextLauncher(QObject *parent) : QObject(parent) , m_context(nullptr) { } void ContextLauncher::setContext(QQmlContext *root) { m_context = root; } void ContextLauncher::launchWithContext(QUrl qmlfile) { if(!m_context) return; auto engine = dynamic_cast<QQmlApplicationEngine*>(m_context->engine()); if(!engine){ qInfo() << "no engine"; return; } auto window = engine->rootObjects()[0]; auto comp = new QQmlComponent(engine, qmlfile, window); // main window as parent //comp->loadUrl(qmlfile); // create ui element auto uielement = new ContextContainingClass(); uielement->setContext(m_context); auto newcontext = uielement->getContext(); qInfo() << uielement << newcontext; // create component with new context QQuickItem* subcomp = dynamic_cast<QQuickItem*>(comp->create(newcontext)); qInfo() << subcomp; uielement->setParent(subcomp); // component is parent of context so it gets destroyed with component }
SubView.qml:
import QtQuick 2.0 Item { z: 100 anchors.centerIn: parent width: 200 height: 200 Rectangle { anchors.fill: parent color: "steelblue" Text { width: parent.width height: 20 text: contextContaining.name } } }
This works and creates an object that is tied to a component with its own context. It sets properties specific to the qml file. It is not showing anything though as I have not tied the component to any property on an existing qml component.
To me this seemed kind of cumbersome (at least the way I did this). Is there a better way to do this?
-
I would strongly suggest to avoid setting context properties. If you're creating components from C++ anyway, just define them as real properties of the QML component, and use
QQmlComponent::createWithInitialProperties
to initialize them.You can use the same approach for your main entry point with
QQmlApplicationEngine::setInitialProperties
If you don't create your components manually, and want to provide access to values passed in from C++, consider using
qmlRegisterSingletonType
andqmlRegisterSingletonInstance
. -
@FKosmale said in Tying a context to the lifetime of a qml component?:
If you're creating components from C++ anyway
We currently are not. We create an object called a UI object that is subclassed from QObject. Then we set a context property in the root context for this UI object. The QML that uses it is then pushed on a stack and it uses that context property. I am unsure how to go from that design to a design that does it more cleanly. I don't see any examples for how to do this otherwise.