QT QML/C++ Hybrid Application Best Practices
-
@sierdzio Within MainController::MainController(), I find the child objects and then connect the signals/slots. If I don't pass in the engine.rootObject() as the parent, how do I still find the child objects?
Or, does this new syntax you linked to fix this problem?
Thank you for the help,
Drue -
@druepy said in QT QML/C++ Hybrid Application Best Practices:
@sierdzio Within MainController::MainController(), I find the child objects and then connect the signals/slots. If I don't pass in the engine.rootObject() as the parent, how do I still find the child objects?
Why do you need to find them at all? Are they objects instantiated in QML? Then add a Q_PROPERTY for them in main controller, and in main QML file set this property to point to those objects.
If they are instantiated in C++ - well then just instantiate them in main controller and expose them to QML (again - via Q_PROPERTY).
Or, does this new syntax you linked to fix this problem?
No.
-
@druepy said in QT QML/C++ Hybrid Application Best Practices:
Within MainController::MainController(), I find the child objects and then connect the signals/slots. If I don't pass in the engine.rootObject() as the parent, how do I still find the child objects?
where did you get the idea that the connects, between c++ and QML, have to be made on the c++ side ? My guess is from here :D
public slots, or Q_INVOKABLE marked public functions can be called as a normal function call on QML side,
for c++ signals, simply use :
https://doc.qt.io/qt-5/qml-qtqml-connections.htmlwith your c++ instance pointer (e.g
cppChatModel
) as target -
Regarding the general topic of how to structure code for a hybrid QML/C++ application...
The team I belong to has been extremely happy with an approach we usually refer to as an instance of the well-known MVVM pattern, but which I also argue is the same approach described in 2002 as "The Humble Dialog Box".
The main points we follow are:
- keep the QML "dumb" and declarative. avoid logic (loops, if/else) in the QML.
- anything in the QML that should change (change text, change color, etc) will change based on a Q_PROPERTY controlled in C++
- anything the application needs to "run" or "do" in response to clicks on the QML will be done in a Q_INVOKABLE (such that the logic is in a C++ function, but QML can call that function)
In other words, we treat the QML more like HTML than like Javascript. We use QML for layout, not logic.
Anything QML needs to read comes from a Q_PROPERTY.
But we avoid having QML write to a Q_PROPERTY. Instead, QML can call an Q_INVOKABLE and that function can update any properties as needed. By treating the exposed properties as read-only from the QML side, this helps avoid binding loops. (If you haven't seen a binding loop error yet... just wait... you will.)
By keeping the QML "dumb" (or "humble" in the terminology from 2002), all the business logic can be unit tested by writing C++ unit tests.
You probably already suspected this level of testability and separation was possible, which might be what drew you to C++/QML. Kudos and welcome!
In the 2002 article, the "ChainComposer" contains the business logic. I would write a ChainComposer in C++. The view (which for us nowadays is a QML view) can then call methods (Q_INVOKABLE methods) on this object. The ChainComposer has access to a "view" and calls setters on the view. In a C++/QML situation, I argue that a ChainComposer altering a Q_PROPERTY (and emitting a signal to tell QML this property changed) is analogous to the 2002 article advocating for the ChainComposer to call setters on the view.
This is all a bit academic, but old-school fans of Michael Feathers (author of Humble Dialog Box paper) will find a lot to love.
You can find working examples of this read-a-Q_PROPERTY/call-a-Q_INVOKABLE "humble QML" approach in these repositories:
- https://github.com/219-design/qt-qml-project-template-with-ci
- my currently-rudimentary "toy" app for Midi piano: https://github.com/pestophagous/heory
Having said all that, though, I still often struggle with binding to a
model
from QML. I find it much easier to bind to a plain boolean or string Q_PROPERTY than to bind to a full fledged model object.For learning patterns relating to model types in QML, you might want to peruse the Qt examples:
- https://doc.qt.io/qt-5.12/qtquick-tableview-gameoflife-example.html
- https://doc.qt.io/qt-5/qtquick-models-objectlistmodel-example.html
(I realize my links are for Qt5 and you want Qt6, but I don't think these parts have changed that much.)
-
Do you consider formatting logic safe?
I understand business logic being handled in C++. Is logic within the QML only for conditional formatting okay?And thanks for all the information.
-
@druepy Good question about logic for formatting.
If you are thinking of things like
printf
and how many decimal places to show in a floating point number and things like that, I personally tend to still do all that in C++.But I am a pragmatist, not a fanatic nor a purist. There are definitely a handful of QML/Javascript functions in most of my projects. (one example) (a second example)
The mantra of "no logic in the QML" is a guiding philosophy, but not a hard line in the sand.
When I do put logic in the QML, I tend to ask myself:
- what is my comfort level with having ZERO automated test coverage of this logic?
- what is the worst outcome for the UI (and therefore for the user) if this logic fails unexpectedly?
If the logic is complex enough to risk putting the UI into an utterly unusable state, then I will feel nervous enough to move the logic into a tested C++ unit. But if the only risk of the QML logic is that something might get rendered in the wrong color, or a possibly sub-optimal font size, then I would not worry.
-
@KH-219Design these are splendid hints, and very well described. Many thanks!
-
This post is deleted!
-