Qt QML Hot Tips #2
Mike Trahearn
Qt Professional Services | Qt Academy | Qt Lifetime Champion?????? | Director Codecept Software Pty Ltd | Qt QML C++ Specialist | Unique Thinker, Detailed Craftsman with Precise Foresight and a Personal Approach
Welcome to Part 2!
Continuing on a theme of raging against the tide of poorly written QML using examples from years of swooning over such elegantly and thoughtfully written mistakes...
Get your Property in Order!
Continuing on the theme of properties, a brief word on their declarations in C++. For C++ only Qt projects, you may only use them for e.g. some kind of RTTI, an abstraction for serialisation or just a mere convenience in Qt Creator's editor for generating all the getters and setters for you - which, by the way is a very nice feature.
But for QML projects using such C++ classes we need to take a bit more care...
QML Bindings
Of course you can't go far with QML without coming across bindings. They come in a variety of flavours from simple property assignments to relational couplings to other properties to function return values and many other exotic cases.
Let's take an example:
import QtQuick.Window // no version numbers needed since Qt 5.1
import My.Theme
Window {
?id: window
?width: 640
?height: 480
?visible: true
?color: MyTheme.backgroundColor
?Image {
???id: background
???width: window.width
???height: window.height
???source: Qt.resolvedUrl("semi-transparent-background.png")
?}
}
(Notwithstanding this is a contrived example since you could use QtQuick.Controls' ApplicationWindow and set the background delegate to the Image and you can forget the sizing as it is done for you or even with the above you could use anchors), you'll notice the two binding expressions for width and height in the background Image.
Similar to signals and slots in C++ the Window has a height and a width property declared in its C++ backend class which, while having setters and getters, also signals emitted for the change notifications. This causes in turn the Image's width and height property bindings to be re-evaluated automatically and its width and height properties written to accordingly. This obviously updates the Image size.
You'll also notice the window's color (its fill color) is bound to what is called a singleton and is imported by the shown import statement. How to do this is not covered here, but suffice to say that MyTheme.backgroundColor property has a fixed color value and doesn't ever change.
The Golden C++ Property Rules for QML usage
These rules will help you create well formed and well behaved properties for QML use - but also C++ too!
领英推荐
1) All C++ Q_PROPERTYs that may change their value that published to QML MUST have a NOTIFY signal so that any binding expression depending on it will be correctly re-evaluated when it changes. Note that this still applies even in the property is readonly from QML.
2) Q_PROPERTY setter methods should be declared as slots so that the C++ method is available for the QML engine to call.
Note: Normally this is the case, but setters as slots may not always be necessary if you don't need an imperative way to modify a member variable and you actively want to remove that option e.g. if you are just doing simple assignments or bindings. However there are many valid reasons in both QML and C++ (some obscure) where setters as slots are absolutely necessary. Consider your requirements case by case.
3) All C++ Q_PROPERTYs that do not change their value must be declared CONSTANT. This tells the QML Engine that it shouldn't change and will allow you to use it in bindings even without the NOTIFY signal which is not necessary in this case.
The QML Engine will issue a warning at runtime warning if the rules it requires are not followed.
4) The FINAL keyword should be added to ensure that the compiler can spot if a Q_PROPERTY is being overwritten. Note that in future versions of Qt and also in the current Qt for MCUs (Qt Quick UltraLite) this is already enforced and will be essential in future optimisation and performance of the QML runtime and the QML compiler.
5) The property setter shall NOT modify the incoming value such that the value returned from the READ function would different. This is a bit more subtle but can be summarised as:
"What you put in should be the same as what you get out"
A good example is with Qt 5.15 where a QUrl property setter method may have a relative url value resolved to the setting QML context's baseUrl. Or, in a real use case I saw it was deemed perfectly fine to take an absolute url (e.g. a web server) address with a hostname and internally resolve the host to an IP address. In both cases it rendered the url seen inside the setter method as actually different to that which was actually set, and in both cases the NOTIFY signal was not emitted. So not only do we have a different value on the property than as set should it every be read, but we never actually got to be told about it.
Qt 6 now drops this implicit url resolution with QUrl properties as described here:
But if you still need the old behaviour you can set the QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT environment variable.
Summary
Take care of your property investments!