Compiling QML to C++: QtQuick Controls and optional imports

This is the sixth installment in the series of blog posts on how to adjust your QML application to take the maximum advantage of qmlsc. In the first post we've set up the environment. You should read that post first in order to understand the others.

This time I will show how to deal with the optional imports provided by Qt's own modules. When compiling any QML file that imports QtQuick.Controls you are greeted with a message of this kind:

Warning: QtQuick.Controls uses optional imports which are not supported. Some types might not be found.

Indirectly, among other problems, this leads to:

Warning: CategoryLabel.qml:145:9: unknown grouped property scope anchors.
    anchors.verticalCenter: txt.verticalCenter

As qmlsc doesn't know the QtQuick.Controls types, it doesn't know they provide anchors. This one is an ImageToolButton, derived ToolButton as probided by QtQuick.Controls. As qmlsc doesn't know the type of the property the binding is assigned to, it cannot compile the binding.

Profiling using QML Profiler reveals that the 26 calls calls to the binding on anchors.verticalCenter take 54.2µs, of which 29.5µs are spent on JavaScript execution.

What does the warning mean? QtQuick.Controls uses different styles, depending on your platform. On windows it usually uses a "windows" style, on macOS a "macOS" style, etc. However, you can override the automatic style selection at run time using the QT_QUICK_CONTROLS_STYLE environment variable, via a configuration file, and in a number of other ways. This means, when QtQuick.Controls is imported, qmlsc does not know what types it will provide. It could be the types from either of the styles available.

We can fix this by nailing down the style to be used at compile time. Since our Timeline module uses QtQuick.Controls practically everywhere, the easiest way to do it is to just import the specific style we want to use from the module definition in the CMakeLists.txt. There we can use CMake to choose a different style depending on platform or other criteria we may need. We change our module as follows and drop all the QtQuick.Controls imports from the individual QML files:

if(WIN32)
  set(QUICK_CONTROLS "QtQuick.Controls.Windows")
elseif(APPLE)
  set(QUICK_CONTROLS "QtQuick.Controls.macOS")
else()
  set(QUICK_CONTROLS "QtQuick.Controls.Basic")
endif()

qt_add_qml_module(Tracing
  URI "QtCreator.Tracing"
  VERSION "1.0"
  NO_PLUGIN
  DEPENDENCIES
    QtQuick
  IMPORTS
    ${QUICK_CONTROLS}
  QML_FILES
    ${TRACING_QML_FILES}
  RESOURCES
    ${TRACING_QML_RESOURCES}
  SOURCES
    ${TRACING_CPP_SOURCES}
)

In reality, we cannot do this to Qt Creator because people value the choice they have in customizing Qt Creator's look and feel to whatever desktop environment they are using. If you are developing a commercial application that tightly controls its look and feel, and possibly ships its own style, you may want to fix the style at compile time anyway, though. In that case, you can reap the benefits of compiling the QML code that uses it to C++ in addition.

After applying this change, the binding on anchors.verticalCenter can be compiled to C++. Now the 26 calls take 29µs, of which 13.7µs are spent on JavaScript execution. That's a rather significant improvement from the 54.1µs and 29.5µs we've seen before.

Compatibility

Module-level imports have been available since Qt 5.14, but before Qt 6 they have a number of flaws. First, in Qt 5 you don't get to use qt_add_qml_module(). Therefore you have to write your qmldir files manually or better, write your own build system code to generate them. Then, the qmldir "import" directive in Qt 5 will only behave reasonably if:

  1. The module in question also declares some QML types itself.
  2. All of the other types are C++-based (not .qml files).
  3. The module has a plugin. You cannot directly compile it into your application.

Blog Topics:

Comments