[Interest] Qt 5 -> 6: In QQmlEngine, Qt.Checked, etc. are no longer available

Ulf Hermann ulf.hermann at qt.io
Thu Jun 23 23:21:04 CEST 2022


Thanks for pointing this out!

>      .import QtQml 2.15 as CoreQML
>      console.log(CoreQML.Qt.Checked)
> 
> yields:
> 
>       At line 2: ReferenceError: CoreQML is not defined
> 
> Is that something I should report as a bug? I'd appreciate any other potential workarounds.

The .import dance as shown above only works if the JS file is itself 
imported from a QML file. Otherwise any .import statement simply does 
nothing. If that's a bug then it has been a bug for a long time. It 
should probably just be documented to work that way. If you're importing 
the JS file from a QML file, then most likely QtQml is already in scope, 
which makes the whole thing pointless.

There are some interesting aspects to the availability and members of 
the Qt object in Qt5 and Qt6:

With QJSEngine, you generally don't get a Qt object at all. You can 
trigger the creation of the Qt object by installing 
QJSEngine::TranslationExtension, though. In Qt5 that gives you an object 
with pretty much only the "uiLanguage" property. In Qt6 you get a Qt 
object with all properties and methods, but without the Qt namespace enums.

With QQmlEngine, in Qt5 you always get the complete Qt object, while in 
Qt6 at first you get the same thing as with QJSEngine and the 
translation extension. Once the QtQml module is imported, you get the 
namespace enums, too.

Now, how did this happen?

In Qt5 if we have a QQmlEngine, each and every Qt namespace enum value 
is added as a property to the Qt object, keyed by a newly created 
QString, wrapped into a JS string, and the whole thing every time you 
query one of those enums (until you query something that doesn't exist). 
You may guess why I've removed this particular piece of code.

In Qt6 once the QtQml module is in scope, the "Qt" name refers to 
something else: The QtQml module exposes the same Qt object as a 
singleton also named "Qt", with an extension that is the Qt namespace. 
Singletons rank above the global object in precedence. That's the secret 
sauce on how the enums are added.

If you want to work around the problem, there are a number of ways to 
extend the JavaScript environment. For example:

     QJSEngine engine;
     engine.installExtensions(QJSEngine::AllExtensions);
     engine.globalObject().setProperty(
                 QStringLiteral("QtNamespace"),
                 engine.newQMetaObject(&Qt::staticMetaObject));
     engine.evaluate(QStringLiteral("console.log(QtNamespace.Checked)"));

You may also want to use QJSEngine::registerModule() if you're working 
with ECMAScript modules.

Finally, I just came up with this neat trick:

     QJSEngine engine;
     engine.installExtensions(QJSEngine::AllExtensions);
     QJSValue qtObject = engine.globalObject().property("Qt");
     QJSValue qtNamespace = engine.newQMetaObject(&Qt::staticMetaObject);
     qtNamespace.setPrototype(qtObject);
     engine.globalObject().setProperty("Qt", qtNamespace);
     engine.evaluate(QStringLiteral("console.log(Qt.Checked)"));

This pretty much gives you the Qt5 Qt object, probably at a lower cost.

I haven't checked how fast the above is, but it probably doesn't 
repeatedly create strings for all enum values in the Qt namespace.

Now, it wouldn't be all that hard to do the above prototype trick 
already when adding the Qt object to the JS global object. I need to 
check if it opens new compatibility pitfalls, though.

best regards,
Ulf


More information about the Interest mailing list