Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QT QML/C++ Hybrid Application Best Practices
Forum Updated to NodeBB v4.3 + New Features

QT QML/C++ Hybrid Application Best Practices

Scheduled Pinned Locked Moved General and Desktop
12 Posts 5 Posters 2.2k Views 3 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    druepy
    wrote on 18 Jun 2021, 03:28 last edited by
    #1

    Hello,

    I'm new to the QT world, and I'm attempting to learn the QT framework through a basic chat client. However, I'm having trouble finding a good method to structure my code.

    My original thought was to have every controller instantiated via MainController. To do this, I needed to call engine.load() early, so that the root context was not nullptr. If I move engine.load() after I attempt to set the rootContext, then I get a segfault because the parent of of MainController is nullptr. If I want MainController to instantiate all other controllers, then I need it to have the rootContext as the parent, so I can call findChild, which is demonstrated in the bottom-most function.

    I'm now running into the issue where I want to add a custom model with. I've followed the examples, but I receive the error ReferenceError: cppChatModel is not defined. This has lead to me posts saying that the context needs to be set before calling engine.load(). However, this brings me to the problem described in the previous paragraph.

    I think my issue is that I don't know the best practice with structuring code for QT.

    1. Is it good practice to have a single entry point for the controllers? I like the idea of the main controller instantiating other controllers, but this might be a bad assumption.

    2. Should the first objects created have the rootContext as the parent? This is practice is demonstrated in the example below, and stems from my assumption in 1).

    3. What is the best approach for this example?
      Most examples I've found were single use where these types of questions weren't encountered.

    I'm using QT 6 for Desktop.

    Here is my entry point: client_main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    
    #include <controllers/main_controller.h>
    #include <controllers/settings_controller.h>
    #include <controllers/messageController.h>
    
    #include <models/activeChatModel.h>
    
    int main(int argc, char* argv[])
    {
        QGuiApplication app(argc, argv);
    
        // register the custom C++ types
        qmlRegisterType<MainController>("MainController", 1, 0, "MainController");
        qmlRegisterType<SettingsController>("SettingsController", 1, 0, "SettingsController");
        qmlRegisterType<MessageController>("MessageController", 1, 0, "MessageController");
        qmlRegisterType<Message>("Message", 1, 0, "Message");
        qmlRegisterType<ActiveChatModel>("ActiveChatModel", 1, 0, "ActiveChatModel");
    
    
        // create the engine
        QQmlApplicationEngine engine;
        const QUrl url(QStringLiteral("qrc:/views/MainView.qml"));
        engine.load(url);
    
        // grab the root object so that we can use it as the parent for the main controller
        auto rootObject = engine.rootObjects().first();
    
        // create the main controller
        MainController mainController(rootObject);
        engine.rootContext()->setContextProperty("mainController", &mainController);
    
    
        ActiveChatModel chatModel(rootObject);
        engine.rootContext()->setContextProperty("cppChatModel", &chatModel);
    
        // I don't know what this does yet.
        QObject::connect(
          &engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject* obj, const QUrl& objUrl) {
              if (!obj && url == objUrl)
                  QCoreApplication::exit(-1);
          },
          Qt::QueuedConnection);
    
        return app.exec();
    }
    

    MainView.qml

    ListView {
                    id: messageListView
                    Layout.fillHeight: true
                    width: parent.width
                    anchors.fill: parent
                    anchors.margins: 20
                    spacing: 20
                    clip: true
                
                    model: cppChatModel // says it is undefined
                    delegate: messageBox
                }
    

    MainController ()

    MainController::MainController(QObject* parent)
      : QObject(parent),
        settingsController(new SettingsController(this)),
        messageController(new MessageController(this))
    {
        QObject* settingsView = parent->findChild<QObject*>("settingsView");
        QObject* sendButton = parent->findChild<QObject*>("sendButton");
    
        if (sendButton == nullptr) {
            qDebug() << "messageModel is sadly nullptr";
            return;
        }
    
        QObject::connect(settingsView, SIGNAL(saveSettings()),              settingsController, SLOT(onSaveButton()));
        QObject::connect(sendButton,   SIGNAL(addMessageToModel(QString)),  this,               SLOT(onAddMessageToModel(QString)));
        QObject::connect(sendButton, SIGNAL(addMessageToModel(QString)), messageController, SLOT(onMessageSend(QString)));
    }
    
    1 Reply Last reply
    1
    • S Offline
      S Offline
      sierdzio
      Moderators
      wrote on 18 Jun 2021, 05:00 last edited by
      #2

      @druepy said in QT QML/C++ Hybrid Application Best Practices:

      qmlRegisterType<MainController>("MainController", 1, 0, "MainController");

      You attach your controller as mainController already - this means you don't need to register it with QML like this. Remove those lines! Otherwise you may instantiate multiple main controllers and get into many weird bugs.

      // grab the root object so that we can use it as the parent for the main controller
      auto rootObject = engine.rootObjects().first();

      // create the main controller
      MainController mainController(rootObject);
      

      This is completely unnecessary. Your MainController does not need any parent - it is a variable created on stack, it will be deleted automatically when your app finishes.

      model: cppChatModel // says it is undefined

      This should fix it:

       model: cppChatModel === undefined ? 0 : cppChatModel
      

      // I don't know what this does yet.

      It exits the application when QML engine reports an error during loading of main QML file. Without this, app would continue running but only show empty window (if there was an error in your QML code).

      QObject::connect(settingsView, SIGNAL(saveSettings()), settingsController, SLOT(onSaveButton()));
      QObject::connect(sendButton, SIGNAL(addMessageToModel(QString)), this, SLOT(onAddMessageToModel(QString)));
      QObject::connect(sendButton, SIGNAL(addMessageToModel(QString)), messageController, SLOT(onMessageSend(QString)));

      Use new (functor-based) connect syntax: https://doc.qt.io/qt-5/signalsandslots-syntaxes.html

      (Z(:^

      D 1 Reply Last reply 18 Jun 2021, 05:18
      3
      • S sierdzio
        18 Jun 2021, 05:00

        @druepy said in QT QML/C++ Hybrid Application Best Practices:

        qmlRegisterType<MainController>("MainController", 1, 0, "MainController");

        You attach your controller as mainController already - this means you don't need to register it with QML like this. Remove those lines! Otherwise you may instantiate multiple main controllers and get into many weird bugs.

        // grab the root object so that we can use it as the parent for the main controller
        auto rootObject = engine.rootObjects().first();

        // create the main controller
        MainController mainController(rootObject);
        

        This is completely unnecessary. Your MainController does not need any parent - it is a variable created on stack, it will be deleted automatically when your app finishes.

        model: cppChatModel // says it is undefined

        This should fix it:

         model: cppChatModel === undefined ? 0 : cppChatModel
        

        // I don't know what this does yet.

        It exits the application when QML engine reports an error during loading of main QML file. Without this, app would continue running but only show empty window (if there was an error in your QML code).

        QObject::connect(settingsView, SIGNAL(saveSettings()), settingsController, SLOT(onSaveButton()));
        QObject::connect(sendButton, SIGNAL(addMessageToModel(QString)), this, SLOT(onAddMessageToModel(QString)));
        QObject::connect(sendButton, SIGNAL(addMessageToModel(QString)), messageController, SLOT(onMessageSend(QString)));

        Use new (functor-based) connect syntax: https://doc.qt.io/qt-5/signalsandslots-syntaxes.html

        D Offline
        D Offline
        druepy
        wrote on 18 Jun 2021, 05:18 last edited by
        #3

        @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

        S J.HilkJ 2 Replies Last reply 18 Jun 2021, 05:25
        0
        • D druepy
          18 Jun 2021, 05:18

          @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

          S Offline
          S Offline
          sierdzio
          Moderators
          wrote on 18 Jun 2021, 05:25 last edited by
          #4

          @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.

          (Z(:^

          1 Reply Last reply
          3
          • D druepy
            18 Jun 2021, 05:18

            @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

            J.HilkJ Offline
            J.HilkJ Offline
            J.Hilk
            Moderators
            wrote on 18 Jun 2021, 05:28 last edited by
            #5

            @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.html

            with your c++ instance pointer (e.g cppChatModel ) as target


            Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


            Q: What's that?
            A: It's blue light.
            Q: What does it do?
            A: It turns blue.

            D 1 Reply Last reply 18 Jun 2021, 20:33
            4
            • D Offline
              D Offline
              druepy
              wrote on 18 Jun 2021, 12:59 last edited by
              #6

              I did not realize that. Thank you.

              When should you choose one method over the other?

              1 Reply Last reply
              0
              • KH-219DesignK Offline
                KH-219DesignK Offline
                KH-219Design
                wrote on 18 Jun 2021, 18:12 last edited by
                #7

                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.)

                www.219design.com
                Software | Electrical | Mechanical | Product Design

                D 1 Reply Last reply 18 Jun 2021, 20:42
                6
                • J.HilkJ J.Hilk
                  18 Jun 2021, 05:28

                  @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.html

                  with your c++ instance pointer (e.g cppChatModel ) as target

                  D Offline
                  D Offline
                  druepy
                  wrote on 18 Jun 2021, 20:33 last edited by
                  #8

                  @J-Hilk That's exactly where I got that idea from. Thank you for the clairty.

                  1 Reply Last reply
                  0
                  • KH-219DesignK KH-219Design
                    18 Jun 2021, 18:12

                    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.)

                    D Offline
                    D Offline
                    druepy
                    wrote on 18 Jun 2021, 20:42 last edited by
                    #9

                    @KH-219Design

                    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.

                    1 Reply Last reply
                    0
                    • KH-219DesignK Offline
                      KH-219DesignK Offline
                      KH-219Design
                      wrote on 18 Jun 2021, 21:16 last edited by
                      #10

                      @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.

                      www.219design.com
                      Software | Electrical | Mechanical | Product Design

                      1 Reply Last reply
                      4
                      • S Offline
                        S Offline
                        sierdzio
                        Moderators
                        wrote on 20 Jun 2021, 09:43 last edited by
                        #11

                        @KH-219Design these are splendid hints, and very well described. Many thanks!

                        (Z(:^

                        1 Reply Last reply
                        1
                        • A Offline
                          A Offline
                          Aliciasmith2023
                          wrote on 23 Oct 2023, 16:24 last edited by
                          #12
                          This post is deleted!
                          1 Reply Last reply
                          0
                          • T talksik referenced this topic on 16 Mar 2024, 05:26
                          • B Bob64 referenced this topic on 29 Mar 2025, 11:51

                          • Login

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • Users
                          • Groups
                          • Search
                          • Get Qt Extensions
                          • Unsolved