[SOLVED] QML/C++ integration: QDeclartiveView and Context property usage
-
EDIT: I believe this was an issue with multi-threading NOT with setting the context property after set source. Read final post for more info.
Hey everyone,
Would any of you mind posting links and/or an explanation as to why setting the context property after setting the source on a QDeclarativeView fails? I tried to search for a while but only found it addressed peripherally. If there's an actual qt-project article that would be great.
From what I could piece together using
@viewer->rootContext()->setContextProperty(---)@
modifies the global object. QML/Javascript doesn't allow the global object to be modified. Thus there's some machinery under the hood that allows the Qt/QML application to modify the global object before the root/source QML component is instantiated. Perhaps the global object itself hasn't been instantiated yet and that's why it works? Clarification here would help.
Also, what are the mitigation possibilities? Currently I have stubs for the properties I'm interested in setting after setSource has been called (i.e. I use setContextProperty with an instance containing bogus data, then set it again later with a valid instance). This seems kind of hackish. I'm thinking about using setContextObject instead and amalgamating all of the other QML accessible properties I want into a class. Is there a better approach? Ideally using setContextProperty after setSource would be nice but I understand if this isn't possible.
Thanks!
-
Just out of curiosity, what are you trying to do? Update an object you pass into QML?
-
[quote]QML/Javascript doesn’t allow the global object to be modified[/quote]
Really? Why am I doing it on daily basis, then :O
Maybe because I'm passing QObject pointers as global properties. But I also don't remember any problems in changing those properties after setting QML file source.
-
bq. Really? Why am I doing it on daily basis, then :O
bq. Maybe because I’m passing QObject pointers as global properties. But I also don’t remember any problems in changing those properties after setting QML file source.
Well, from what I've read "here,":http://qt-project.org/doc/qt-5.0/qtqml/qtqml-javascript-hostenvironment.html#javascript-environment-restrictions it would seem that you're doing something different than adding properties to the global object. Although I'm a noob so that link may just pertain to javascript files only.
bq. Just out of curiosity, what are you trying to do? Update an object you pass into QML?
I'm trying to use set context property after the source has been set:
@
viewer->setSource(QUrl("qrc:/QML/main.qml"));
viewer->setWindowFlags(Qt::WindowStaysOnTopHint);
viewer->setWindowFlags(Qt::FramelessWindowHint);
viewer->setRenderHint(QPainter::TextAntialiasing);
...
viewer->rootContext()->setContextProperty("worklistModel", &m_worklistListModel);
@For example, suppose there's some code that forms a model for a list view I want to use. Right before the application gets to a particular screen it creates this model. I want to use set context property and add this instance to the root context. However unless that property was already set before I get an assert in JSTypeInfo (or something similiar) saying that the global object is being modified.
The way around this was basically to create a bogus instance and use set context property before set source, then update it afterwards. That though feels kind of hackish:
@
viewer->rootContext()->setContextProperty("worklistModel", &bogus);
viewer->setSource(QUrl("qrc:/QML/main.qml"));
...
viewer->rootContext()->setContextProperty("worklistModel", &m_worklistListModel);
@ -
Don't do it globally, then. Send the model in a signal-slot connection.
Or add a getter to some global object that returns the model (see "this":https://github.com/sierdzio/closecombatfree/blob/master/src/ccfmain.cpp#L71 and "this":https://github.com/sierdzio/closecombatfree/blob/master/src/ccfgamemanager.h#L58). I am basically doing the same thing you are trying to achieve, only in a slightly different way. And it works without problems. Feel free to download the whole project and take a look at it in Qt Creator.
-
@slerdzio: The signal slot mechanism may work. Currently I'm using a class that derives from an abstract list model, thus it's nice using the context property way of doing things.
The project you linked to does exactly what I was mentioning (and trying to avoid). It calls set context property in your CcfMain constructor. As you're basically making a singleton that constructor will only be run once, before your viewer->setSource is called in main.cpp. Thus you aren't modifying the global object before you set source. While maintaining such a global object works in your framework ideally I'd like to hide mine behind the privacy of a view class I have (MVC), if only for architectural reasons (and because I won't have some overly large function setting a bunch of context properties, another thing I'm trying to avoid).
For my own understanding, do any of you know how the setContextProperty works under the hood with regards to the global object? I could look through the source myself but polling the community seemed apt prior to going down that rabbit hole.
-
Hi,
The global JS object is frozen, yes. But the symbol resolution rules also include looking up context properties.
Every [removed]be it a binding expression or signal expression) is evaluated within a context. You can read more about it in the documentation, but basically some components have an explicit context associated with them (eg, components which instantiate QML file-defined types) and you end up with a context hierarchy (as the QObject parent of the context of that component, will be the context of the instantiating component). So, let's say you have:
@
// One.qml
import QtQuick 2.0
Item {
property int a: 50
}
// Two.qml
import QtQuick 2.0
Item {
property One b: One {
a: 60
}
}
// test.qml
import QtQuick 2.0
Item {
Component {
id: twoComp
Two {
}
}
}
@You can see that there is a context (scope) chain being generated.
The symbol resolution rules say that if a given symbol can't be resolved in the immediate scope (ids of objects in the immediate context, properties of the current object, properties of the root object in the immediate context, properties of the immediate context) then it will "look up the scope chain" trying to resolve the symbol.
Eventually, if it doesn't resolve the symbol prior, it will get to the root context managed by the QML engine (which is exposed via QQuickView->rootContext() etc) - and at this point, if you have set a context property on the root context with that name (ie, the symbol you're trying to resolve) it will resolve to that context property.
TL;DR: global JS object is frozen, but this has nothing to do with context properties. The root context is always is scope of every object in a given object hierarchy. You CAN modify root context properties whenever you like (even after setting the source) but doing so is not advised, as it will cause re-evaluation of ALL binding expressions (for obvious reasons).
Cheers,
Chris.APPENDIX: working example ;-)
@
// main.cpp
#include <QtGui/QGuiApplication>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickView>
#include <qqml.h>int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);QQuickView viewer; viewer.engine()->rootContext()->setContextProperty("rootContextOne", 1); viewer.setSource(QStringLiteral("test.qml")); viewer.engine()->rootContext()->setContextProperty("rootContextTwo", 2); viewer.show(); return app.exec();
}
//ctxtprop.pro
QT += qml quick
SOURCES += main.cpp
FILES += test.qml
TARGET=ctxtprop//test.qml
import QtQuick 2.0Item {
property int boundOne: rootContextOne
property int boundTwo: rootContextTwoComponent.onCompleted: { console.log(boundOne + " " + boundTwo) } onBoundOneChanged: console.log("boundOne now " + boundOne) onBoundTwoChanged: console.log("boundTwo now " + boundTwo)
}
// output:
boundOne now 1
1 0
file:///home/chriadam/Code/test/ctxtprop/test.qml:5: ReferenceError: rootContextTwo is not defined
boundTwo now 2
@ -
@chrisadams Thanks! Both the explanation and example code helped me get through the hurdle.
Here's what was happening. I have a multi-threaded application which uses MVC. While the main thread was doing viewer->setSource(QUrl("qrc:/QML/main.qml")), a worker thread was running in the background attempting to set a context property. From what I can see in the backtrace the main thread and the worker thread were both trying to execute something in QTJSC. I'm assuming this isn't thread safe thus the crash was occurring due to one thread writing a value/pointer that another thread was using. If this assumption is wrong let me know, otherwise I think this one is solved.
-
jquick: maybe you solved your particular problem, but I think the broader problem merits discussion: what order to setSource() and setContextProperty(). I seem to have encountered this problem, just not with the same errors you got using multitasking.
It seems to me that if you do it in the reverse order, then when you call setSource(), your objects in the context are undefined, and QML parser might be quiet about it. Later, when you again setContextProperty(), I don't think it will automagically flow into the undefined references. I could be wrong.
As discussed by ChrisAdams, if you have defined a (as you say, bogus) property in the context using setContextProperty(), then the name is defined and through the magic of binding expressions, when you later again setContextProperty() the property with a non-bogus value, it will flow. But this is an update, I don't think it works unless the name is defined in context at the time of setSource(). Again, I could be wrong and it might depend on the code: for example if you template an object and pass the undefined name, I'm not sure that the updated context property flows through that construction.
A minor point is that I don't understand why you need to create the model after setSource(). It seems to me that the model should not depend on the QML view, so why can't you create it and set it in the context before you setSource(). I don't know your app, but in my app I could.
-
jquick: maybe you solved your particular problem, but I think the broader problem merits discussion: what order to setSource() and setContextProperty(). I seem to have encountered this problem, just not with the same errors you got using multitasking.
It seems to me that if you do it in the reverse order, then when you call setSource(), your objects in the context are undefined, and QML parser might be quiet about it. Later, when you again setContextProperty(), I don't think it will automagically flow into the undefined references. I could be wrong.
As discussed by ChrisAdams, if you have defined a (as you say, bogus) property in the context using setContextProperty(), then the name is defined and through the magic of binding expressions, when you later again setContextProperty() the property with a non-bogus value, it will flow. But this is an update, I don't think it works unless the name is defined in context at the time of setSource(). Again, I could be wrong and it might depend on the code: for example if you template an object and pass the undefined name, I'm not sure that the updated context property flows through that construction.
A minor point is that I don't understand why you need to create the model after setSource(). It seems to me that the model should not depend on the QML view, so why can't you create it and set it in the context before you setSource(). I don't know your app, but in my app I could.