Singleton Controllers in Times of Declarative QMLController objects have been the main way to glue your QML UI to your application's actual implementation of the I/O and business logic. However, over the years, the way to actually expose that controller object has changed. And now, we contributed a change in QQmlEngine that allows you to change it once again, and we believe: for the better.What are "controllers" anyway?Conceptually, controllers are a thin glue layer between your business logic and your QML, exposing the data that the GUI needs in a format it can easily use. They are implemented as QObject-derived instances, usually with properties exposing values that may or may not be writable, as well as potentially some Q_INVOKABLE methods that can be triggered by the QML and maybe some signals.Usually, these controllers are specific to a single logical group of values and functions within the wider application. An application may have a hand-full to dozens of them for a big system. Models exposing collections of data are usually made available as read-only properties returning a QAbstractItemModel-derived data model on these controllers.Often, these controllers need to be instantiated with some initialization, as they need references to the business-logic objects they expose to the GUI, listen for signals to get notifications of changes, etc. And that's where the trouble starts...Pre-Qt 6Context propertiesIn the early days of QML, one would often use controller instances exposed to QML as context properties. Doing that allowed one to instantiate the controllers under control of C++, giving it all the references the objects needed at that time. We would often expose them to QML using a naming pattern like starting the name with a double underscore __someController so that it was easy to recognize in the QML code. Using context properties however is no longer recommended. Their lookup is slow, and the QML compilers cannot reason about them, so code using them cannot be optimized. Nor is tooling available to help the QML programmer, as code completion and the likes are not possible.Singleton InstancesThen came the qmlRegisterSingletonInstance method. This method allowed one to register a QML singleton, but it would return the instance that you passed it as an argument and that you could instantiate however you needed. That was a good solution, but it didn't have a long useful life as it didn't mesh well with the declarative registration and it had issues with the one instance being the instance for every QML engine in your application (if you had more than one).Post-Qt 6Since Qt 6, the recommended way to write QML is to create QML modules using declarative registration for C++-based objects. That has many benefits in terms of tooling and optimization, so it's good practice to do this. But it also meant that since Qt 6, one could no longer mix-and match imperative registration with declarative: you either used the one, or the other; which rendered the qmlRegisterSingletonInstance method above useless.There are many possible approaches that I have seen being applied to still control the creation of controller objects, usually by registering a singleton that has a static create factory function and returning some C++ singletons there or something along those lines. That works, but isn't very elegant. An alternative approach is using initial properties on the root object, but that either requires accessessing the root id from other QML files or propagating the controllers all the way down the stack of items. Neither is a great solution for different reasons. My colleague Javier Cordero Pérez is making a couple of videos about ways to do this, so I won't go into detail here. These videos will be added here once they have been released.New approachThat building this connection between C++ and QML was so inelegant - despite being so important - inspired me to finally take matters into my own hands and write a patch.The result is available starting with Qt 6.12 onward and it combines the good things of qmlRegisterSingletonInstance and the declarative registration: you still register your controller type as a QML singleton so that the type is fully known by the tooling and access to it can be optimized. But we gain back the ability to provide a ready-made instance to the QML engine.setExternalSingletonInstanceThe API on QQmlEngine gained a single new method: QQmlEngine::setExternalSingletonInstance. It allows you to provide an instance of a type declared as a singleton as the instance to use in any QML running in that engine, just like you could with qmlRegisterSingletonInstance. In contrast to that old registration function, however, you call this method on your specific QQmlEngine instance. Note that the type has to be (declaratively) registered as a singleton type for this call to work. If you are using more than one engine, it is up to you to decide if you want to provide the same instance to these different engines, or have separate instances.This simple method gives you back an elegant, supported way to fully control the instantiation of the QML singleton, and thus easily connect it to your business logic or whatever else you need to with it. However, it is up to you make sure that you do this call before any QML code actually tries to access the singleton. Otherwise, the engine will (try to) create it's own instance as it used to. You cannot replace an already existing singleton instance, so once there is one, it is the one.It’s up to you to make sure that the provided singleton instance outlives the QML that depends on it. You can do that in any way that works in your context, but you could consider parenting the instance to the QQmlEngine instance, ordering the variables containing them on the stack correctly, or using QQmlEngine::setObjectOwnership to hand ownership of the singleton to the QML engine.QML_UNCREATABLE for singletonsIf you are providing your QML singleton instance yourself anyway, you logically also don't need it to be creatable by the engine either - although, it still can be, of course. If your controller type has a non-default constructor - perhaps to take in some references to your business logic instances - you can now mark your singleton with QML_UNCREATABLE, just like you can with other QML types. If you do that, you no longer need to supply a factory function (and even if you do, it won't be used).Of course, if you mark a singleton as uncreatable, it is up to you to make sure you actually supply an instance via QQmlEngine::setExternalSingletonInstance before the singleton is needed from QML.The post Singleton Controllers in Times of Declarative QML appeared first on KDAB...
KDE e.V., the non-profit organisation supporting the KDE community, is looking to hire a software engineer to help improve the software stack that KDE software relies on. Please see the call for proposals for more details about this contract opportunity. We are looking forward to your application. The full call for proposals has more details...
Over 180 individual programs plus dozens of programmer libraries and feature plugins are released simultaneously as part of KDE Gear. Today they all get new bugfix source releases with updated translations, including: akonadi: Fix crash in EntityTreeView when selecting multiple items (Commit) kcachegrind: Don't read out of bounds (Commit, fixes bug #519280) konsole: Prevent QTabBar from closing tabs on middle mouse clicks (Commit, fixes bug #515014) Distro and app store packagers should update their application packages. 26.04 release notes for information on tarballs and known issues. Package download wiki page 26.04.1 source info page 26.04.1 full changelog...
If you have been on invent.kde.org lately you might have seen some merge requests about “Install Qt metatypes” and wondered what that’s all about. When defining QML types in C++ the buildsytem tries to capture as much information about the type as possible, so that tools like qmllint, qmlls, and the QML compiler know about what API the type provides. If that information cannot be gathered the code will still work fine at runtime, but the development experience will be degraded. Normally, when all the types involved are from the local project (or Qt), and you are using the qt_add_qml_module CMake API as well as declarative type registration, things will work mostly out of the box. However, in some cases that’s not enough, and we need some extra steps. Imagine we have a library MyLib, that exposes a class MyModel. That model isn’t registered to QML at all. Now we have a program MyProgram, that creates a subclass of MyModel, and registers that to QML: #include <MyLib/MyModel> class MySubModel : public MyModel { Q_OBJECT QML_ELEMENT ... } This will work fine at runtime, but produces a suspicious build warning: Warning: mysubmodel.h:3: MyModel is used as base type but cannot be found. Opening any QML file using MySubModel in an qmlls-capable editor will show that type information for MySubModel is limited or nonexistant. So how do we fix this? Enter: metatypes files. During the build process moc processes your classes and extracts information about properties, signals, invokables, etc. That information is then processed by the QML tooling. For Qt’s own types that’s done out of the box, and for custom QML module’s types too, but if your custom module is using types from another library some extra steps are needed. First, the library needs to extract its metatypes into a consumable file. This is done using the qt_extract_metatypes CMake API: add_library(MyLib) qt_extract_metatypes(MyLib) This will produce a JSON file that contains information about the types in MyLib. If the QML module needing this is in the same buildsystem that’s enough to make things work. However, quite often it will be used by something in another project, so we need to install the file alongside the library: add_library(MyLib) # the path of the generated file will be stored in ${METATYPES_FILE} qt_extract_metatypes(MyLib OUTPUT_FILES METATYPES_FILE) install(TARGETS MyLib) install(FILES ${METATYPES_FILE} DESTINATION ${KDE_INSTALL_QTMETATYPESDIR}) This will install the file, but that’s not enough for the consuming project to pick it up, we need to associate the metatypes file with the library. To make that happen we add the (public) sources for that library: add_library(MyLib) # the path of the generated file will be stored in ${METATYPES_FILE} qt_extract_metatypes(MyLib OUTPUT_FILES METATYPES_FILE) # extract the filename from the path get_filename_component(METATYPES_FILE_NAME ${METATYPES_FILE} NAME) # add metatypes file to the interface sources set target_sources(MyLib INTERFACE $<INSTALL_INTERFACE:${KDE_INSTALL_QTMETATYPESDIR}/${METATYPES_FILE_NAME}>) install(TARGETS MyLib) install(FILES ${METATYPES_FILE} DESTINATION ${KDE_INSTALL_QTMETATYPESDIR}) This scary looking line of CMake basically boils down to “Everything that links against MyLib will get the installed metatypes file added to its sources”. This makes the QML machinery in the application pick it up. With that, no changes to the application are necessary. The build warning disappears, and type information in the editor starts working. Since it’s hard to know in advance whether a library’s types are going to be used that way it’s probably a good idea to do this for any library, especially since there’s effectively no cost to this, other than some CMake code. It would be great if Qt would take care of most of that code though, see https://qt-project.atlassian.net/browse/QTBUG-123052 and related patches. A word on the install location: When installing Qt-related files there’s some subtleties involved when determining where to install things. Fortunatley ECM takes care of that for us, so it gained a new variable KDE_INSTALL_QTMETATYPESDIR for this. This has been applied to a few KDE libraires already, but there’s likely more where it would be benefitial, to allow for better QML tooling and ultimately a better developer experience...