Qt Graphs vs Qt Charts. Objective Take on the New Data Visualization Solution
The Qt Graphs is a powerful module of the Qt framework that enables developers to create interactive, both static and […]
There is no argue that QML is an amazing technology for developing an outstanding user interface matching today’s trends. The QML language has beautiful and easy-to-learn syntax, but the code in the QML file doesn’t structure itself that. By its nature, it can easily get messy. Nested object declarations are one of the reasons for that.
Therefore, you need to learn how to write clean QML code in order to keep your Qt development project easily maintainable. Ergo, to make savings on money and time.
First, if you do not know Qt framework and what features it contains, check out our blog post about Qt and how to create GUIs using it.
Let’s start by answering the question what makes QML code clean? Can the code actually be dirty? Well, yes. There are a lot of things you can do wrong in a QML document without realizing it.
The code style is the way code is formatted. It is how your code looks. I find this definition pretty plain and simple. It’s true that it doesn’t affect the application’s execution in any way, but it significantly affects the readability of your code. So, it affects the application’s maintainability and therefore time future developers would need to make changes.
Code formatting is about indentation for properties, functions, and nested object declarations. It is about the usage of white spaces and new lines, code grouping, and QML object tree organization. Many of those things are personal preferences and there is no point in arguing about small things like where the opening bracket should be.
The goal is to have consistent code formatting across the project. I have seen software developers who couldn’t be persistent in their own code, so it’s even more difficult when more people work on the same codebase. That’s why you should try automating code formatting.
QML code has a tendency to have many nesting levels and it quickly spreads too much to the right. Therefore, my first tip would be to decrease the indent size to 2 spaces to limit this effect.
In order to do that, enter Qt Creator Options and navigate to Qt Quick settings. You should find a tab related to the code style. You cannot edit the default code style, so click on the Copy button in order to create your own style.
After giving your style a name click on the Edit button in order to modify the details of the newly created style. I wish there were more options to adjust, but at least indentation settings can be changed here. You should see that both Tab size and Indent size equal 4. Decrease them to 2 and you are done. The change should be reflected in the code editor and by tools like Reformat file tool.
Another tip would be to use QML/JS Reformat file tool. How does it work? It takes your code and reformats it just to look nicer. Let’s have a look at the component below. It is jut random code to showcase the tool.
import QtQuick Item { id: root width:30;height:50; Text { id: textItem font.pixelSize: 48; wrapMode: Text.WordWrap lineHeight: 0.75 Behavior on color { ColorAnimation { duration: 120; easing.type: Easing.OutElastic} } states: [State { name: "pressed" when: mouse.pressed PropertyChanges { target: textItem color :Qt.lighter(root.color) }} ] } MouseArea { id: mouse anchors.fill: parent onClicked: { // ... } } function foo() { // ... }}
The above example is definitely not the most beautiful QML code that you have seen. There are several issues including indentations that are different from each other, wrong braces positions, and spaces that are sometimes missing and sometimes redundant. To fix it quickly and easily, navigate to the Tools option from Qt Creator’s top bar menu and find Reformat file under QML/JS group. Running the tool can save a lot of your time.
import QtQuick Item { id: root width: 30 height: 50 Text { id: textItem font.pixelSize: 48 wrapMode: Text.WordWrap lineHeight: 0.75 Behavior on color { ColorAnimation { duration: 120 easing.type: Easing.OutElastic } } states: [ State { name: "pressed" when: mouse.pressed PropertyChanges { target: textItem color: Qt.lighter(root.color) } } ] } MouseArea { id: mouse anchors.fill: parent onClicked: { // ... } } function foo() {// ... } }
If you’re committed enough to give up Qt Creator some control over your QML code formatting, then try setting up Reformat file tool to be run automatically on file save. However, this processing QML code is not always perfect.
There is also a tool called qmlformat that you can find next to the Qt framework’s installation. It is a command line tool that does basically the same. Just in case you want to have something to be used from the command line.
Coding conventions go beyond the remit of formatting. The QML code can be formatted nicely and look okay from the code style perspective, but it may need polishing anyway. In Scythe Studio we are pretty restrictive about our coding conventions that slightly differ from official QML coding conventions, but they also add a lot of other concepts like to declare child objects in a particular order. However, these official conventions are good for you to start with, so following them is suggested.
Below you will find a few of the conventions that we follow in Scythe Studio company in order to provide high-quality Qt development services and have clean QML code. We enforce following conventions even on job candidates while working on their technical assignments during screening.
In order to improve readability and traceability, setting an id of a top-level item in a file to root is suggested. Then, in every single QML file, you know that the reference to the root is about the top-level item.
import QtQuick Item { id: root }
This is exactly what was done in the above example. You can actually name it differently than root, but that’s how we do it at Scythe Studio, and many others do it the same way. The benefit of that is that even besides having many nested object declarations, you always know that the child object using root, refers to the top item in QML document.
If the string is visible to the user always wrap it in qsTr() function. Also instead of using end translation as strings displayed by default use THIS_NOTATION. For example instead of writing text: qsTr(„This is displayed”) use text: qsTr(„THIS_DISPLAYED”). Thanks to this approach if any string is missing a translation we can instantly see that in UI.
Encapsulation and proper scoping should matter when you code in QML. Just like in C++, you do not want all members of your class to be public, so the same should apply to the QML code. Not all properties and functions should be visible from the outside.
There is often a need for helping properties and functions, but they have no value or can even cause some unexpected harm when used from outside. Therefore wrapping them into an internal QtObject is suggested. ID property of such QML object I often set to just underscore.
QtObject { id: _ // or id: internal readonly property real timeout: 1000 property bool isExpanded: false function addDestination() { // ... } }
Official QML conventions cover the order of attributes and proposed look for QML object tree, but we just define it a bit differently and in more detail. It simply pays off for us in big projects. So following is our proposal on how to declare child objects and attributes in a QML file.
import QtQuick import QtQuick.Controls // Qt modules first import com.scythestudio.ownmodule 1.0 // then own modules import "./controls/" // then local directories // A hypothetical custom button item. Item { id: root /// The text to be placed on the button property string title: "title" // properties declaration /// The button's background color property alias color: button.color signal buttonClicked() // signal declaration Button { // child items id: button width: parent.width // properties derived from Item height: parent.height icon.color: "black" // Button's own properties onClicked: { root.buttonClicked() } } states: State { // states name: "selected" PropertyChanges { target: button; icon.color: "red" } } transitions: Transition { // transitions from: "" to: "selected" ColorAnimation { target: button; duration: 200 } } PropertyAnimation { // special items id: closeDelayAnimation // ... } Timer { // special items } Connections { // special items target: someCppController function onProcessFailed() { // ... } } QtObject { // private item id: _ readonly property real timeout: 1000 property bool isExpanded: false function addDestination() { // ... } } onWidthChanged: { // slots } function close() { // public functions isExpanded = false closeDelayAnimation.start() } }
From the code snippet above you can deduce a lot of rules about QML object tree and attributes order. It should give you some hints on how to write QML code and define child objects. Let’s highlight some of the rules.
The order of each import statement is not random. Built-in Qt modules import statement should be above own modules;
Functions definitions are preferred to be on the bottom of the file in order to see the component’s children’s QML objects first and quickly understand how the item is built visually. After all, QML code defines firstly look and then the flow of the application;
States and transitions tend to grow fast, so that’s why they should be placed below child objects though they are also properties;
The component’s public API (properties and functions) should be documented;
In the documentation of the Qt framework, you can find a list of best practices for QML and Qt Quick. I find all of them quite useful and some of them principal to have a clean QML code. Probably the most crucial practice described on that page is a rule to separate UI from logic. When you code using QML, you may be tempted to use QML and JavaScript for many things including application logic and network communication.
Try to move such things to C++ as then you have a clear separation of the frontend written in QML and let’s say the backend written in C++. Therefore, your application’s architecture offers more scalability. One of the things that should almost always be written in C++ is data models. You can go with QML models only if your list is static. Otherwise, you will finally either lose your time trying to perform advanced operations using QML models or rewriting them to C++ models.
In this blog post about Improving Performance and Optimizing QML apps, you will find a lot of other reasons to use C++ to implement logic. There are many benchmarks there to prove the point. And let’s put it this way. Clean QML code defines UI, not the application’s critical features.
In Scythe Studio we conduct online and offline workshops dedicated to various aspects of Qt software development. One of the topics of the workshops is „Advanced Qt QML”. See our offer regarding Qt training and C++ workshops to learn more.
Another good practice is to use qmllint tool which would help you easier spot the issues in your Qml code. It’s a pretty useful tool warning about many issues including unused imports and unqualified access to properties. Since Qt 6 it is even nicely integrated with Qt Creator IDE .
You can even make qmllint return a JSON output. Then after processing QML code you have all the issues and errors in a format that you can then integrate with GitHub, GitLab, or other CI/CD tools.
During Qt World Summit 2022, I had a talk named CMake and Qt. qt_add_qml_module() in practice. I recommend you watch this video on YouTube. The purpose of this talk was to explain what are modules in QML code and how you can use them to structure your QML objects in a reasonable way.
To keep it simple. Modules are the way to organize your QML files and JavaScript code in packages similar to C++ namespaces. This way you can put items like UI controls or basic helping items (Constants, Theme, etc) that are used across source code in separated modules named in a meaningful way.
I will not go into technical details, because it was explained during that presentation, but the benefits of using the modules are:
Possibility to separate features and items;
Having a modularized project structure that helps using particular modules across many subprojects;
Sharing sets of QML source code in form of libraries
Keeping the codebase clean and maintainable;
Possibility to introduce versioning;
At this point, you should know what clean code means and what you can do to ensure that the code you write follows these rules. So now let’s answer the question why is it so important to write clean QML code? It is one of the factors that make your code high quality.
When you have a set of conventions and rules to follow, it’s easier to spot errors and therefore you immediately start to make time savings. Moreover, as clean code concepts include good practices, the developers who follow these directives write code according to the art. It doesn’t require then that much effort during the reviewing process. Also, your senior developers do not need to then point out a lot of tiny visual syntax issues.
During coding, there are plenty of tiny decisions to make. Looking at the very same code after some time, you rather have no idea why you wrote the code in a particular way. On the other hand, when you follow clean QML code rules, you outsource those little decisions to the code. It frees up your thinking resources.
Finally, of course, the most important reason to write clean QML code is the fact that it significantly improves the maintainability of the project. Someone said that you write the code once, but it’s read multiple times. Therefore, you need to care about the readability of your code as well as about all the good practices. Then it’s easier for you and other developers to jump into the project, understand a particular piece and make changes.
Now you may ask how strict about following clean QML code rules your team should be. Well, it all depends on the length of the project that you’re currently working on. It is also important how many people participate or will participate in the future. In my point of view, it’s hard to overdo it when it comes to ensuring high-quality code.
Even if you prepare a small demo of which code will be public, it’s good to follow all these rules to be finally content with the results of your work. After a while, it gets into the blood, and developers stop even thinking about it as an additional effort.
If you maintain a long-life project such as the Qt framework, you have many reasons to be extremely restrictive about the code quality. I described my experience of contributing to Qt Charts in Qt 6.2 module in a blog post. There are a lot of conventions and rules to follow and it’s actually quite difficult at the beginning to get used to them.
Thankfully, there are CI/CD mechanisms that save reviewers time by, for example, looking for typos. In the beginning, it was a struggle to make my code match all those requirements. Nevertheless, I know that it’s a must-have in this project and I can say that it was even enjoyable. Basic CI/CD setup for Qt QML project was explained in the blog post Cross-platform Qt CI/CD setup – make it easy.
Let’s summarize the blog post to again remind the most important rules for writing clean QML code. Many of those rules should be applied to your project, and to your source code regardless of the used technology.
Maintaining a clean QML codebase is an important aspect of a healthy Qt project;
KISS – keep it short and simple. Not for you, but for the reader. Always choose the simpler implementation option over the more complicated one;
Document public interfaces (API) of created items;
Do not add comments describing how something is achieved. If the reader can’t deduce that from your code, then the code needs refactoring. Comment on not obvious solutions that may look surprising;
Do not reinvent the wheel;
Adapt the code to the existing codebase. If you have improvement ideas, first discuss them with other developers;
Follow the coding conventions and good QML practices;
Let's face it? It is a challenge to get top Qt QML developers on board. Help yourself and start the collaboration with Scythe Studio - real experts in Qt C++ framework.
Discover our capabilitiesThe Qt Graphs is a powerful module of the Qt framework that enables developers to create interactive, both static and […]
Embedded systems are everywhere, from household appliances to industrial machinery. At the heart of these systems is embedded firmware, the […]
Outsourcing embedded systems development has become an increasingly popular strategy for businesses looking to enhance efficiency, reduce costs, and leverage […]