How to access QJSValue's QQmlEngine in Q_INVOKABLE to support callback pattern for C++ models
-
I have code like this:
class Foo : QObject { ... Q_INVOKABLE void bar(const QJSValue &callback) { ... asynchronous code resulting in: auto e = qmlEngine(this); callback.call({e->toScriptValue(result)}); } }
Foo is created in C++ and exposed to QML via a QAbstractListModel implementation.
Now, because Foo is not constructed by QQmlEngine, qmlEngine(this) in bar() will return nullptr, leaving me with no way to actually call the callback with an argument that is not a primitive value.
I half expected that the object would get associated with the engine as soon as the engine called the Q_INVOKABLE, but that is apparently not the case.
There used to be a QJSValue::engine() method, that would have allowed me to get the engine from the callback itself, but it was deprecated for some reason in Qt 5 and does not exist in Qt 6. This method would be the ideal solution to this problem.
I've now worked around it by having a helper object (constructed by QML) that calls the callback on Foo::bar()'s behalf, essentially proxying access to Foo. This is less than ideal for obvious reasons. Another workaround is to have Foo depend on a QQmlEngine instance, which it can use to construct the callback's argument list. I don't like this solution because then I would have to store the engine instance inside the data model.
How would you solve this problem? Or is it time for a feature request :)
-
Just a lucky shot: did you try using qjsEngine(this) instead of qmlEngine(this)?
-
Just a lucky shot: did you try using qjsEngine(this) instead of qmlEngine(this)?
@Kai-Nickel said in How to access QJSValue's QQmlEngine in Q_INVOKABLE to support callback pattern for C++ models:
Just a lucky shot: did you try using qjsEngine(this) instead of qmlEngine(this)?
I did not and I'm happy to report that qjsEngine() does work!
Looking at the source code of qmlEngine() vs qjsEngine() it is obvious why: qmlEngine() only works if there is a qml context for that object.
I was under the assumption that qmlEngine() simply called
qobject_cast<QQmlEngine*>(qjsEngine(obj))
, which is why I didn't look at the differences between the two. Evidently my assumptions proved wrong.QQmlEngine *qmlEngine(const QObject *obj) { QQmlData *data = QQmlData::get(obj, false); if (!data || !data->context) return nullptr; return data->context->engine; } QJSEngine *qjsEngine(const QObject *object) { QQmlData *data = QQmlData::get(object, false); if (!data || data->jsWrapper.isNullOrUndefined()) return nullptr; return data->jsWrapper.engine()->jsEngine(); }
-