Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Porting from Widgets to QML: suggestions needed on how objects interact
Forum Updated to NodeBB v4.3 + New Features

Porting from Widgets to QML: suggestions needed on how objects interact

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
11 Posts 3 Posters 1.6k Views 1 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.
  • M Offline
    M Offline
    meslor
    wrote on last edited by
    #1

    I'm evaluating porting a Linux desktop application from QWidgets to QML (Qt 5).
    I cannot post here any code, hope next few lines describe the scenario with a sufficient level of detail. I may eventually prepare a minimal project as example, if needed.

    A single state machine is present that:

    • Has no knowledge of the UI.
    • Communicates with the context by generating/ receiving events.

    The basic structure of the UI is based on a grid layout, made by 3 rows:

    • The top and bottom rows, whose widgets are always visible.
    • The center row, made by a single QStackedWidget, that shows the correct widget ('page') based on the current state of the application.

    In this scenario, the UI is composed at runtime:

    • All widgets ('pages') are programmatically added to the QStackedWidget at startup.
    • A reference to the widgets is kept so that UI can be updated depending on state machine transitions and/or user interaction.

    I've already ported applications from QWidgets to QML, but scenario was quite different, as interface was all described in a single .ui file, therefore creating a .qml file that could interact with its C++ counterpart was straightforward (basically, replacing calls to QWidget methods with property setting/getting did the job easily).

    That said:

    1. Before trying to replicate this design as-is with QML: is there some recommended approach that can be used? Links to any kind of example are more than welcome :)
      Most relevant aspect is that I would like to have no business logic in the QML, other that the one strictly necessary to update the UI, as this would let me reuse as much C++ code as possible from current project, especially the state machine.

    2. In case current design can be ported to QML, I would appreciate some guidance on who would be responsible for objects creation:
      a. Each C++ 'page' loads its own QML: how can I programmatically compose and update the UI? (that is, adding items to the stack from outside the QML).
      b. Each QML 'page' instantiates its C++ counterpart: how can the underlying C++ instances interact one with the other?

    Any help is greatly appreciated.

    1 Reply Last reply
    0
    • B Offline
      B Offline
      Bob64
      wrote on last edited by Bob64
      #2

      One thing that is not clear to me is how dynamic the "pages" are. For example, is there a finite set of possible pages known up front? Is the actual content of each page pre-determined, or is it driven by data? (By "content" I mean the set of widgets, etc., that is shown on each page.)

      At a high level, I would tend to reach for StackLayout for the basic stack management. The details of how you add and manage the child objects will really depend on the details of what they are. QML supports various types of "model-view" structure, where it is relatively easy to expose the model from C++. It sounds like that is the sort of thing you need.

      1 Reply Last reply
      0
      • M Offline
        M Offline
        meslor
        wrote on last edited by meslor
        #3

        Let's see if I can make things more clear :)

        • The set of possible pages is known in advance: they are all created at application startup and added to the QStackedWidget as its children.
        • The structure of each page is fixed: there are no widgets that are added/removed on the fly at runtime depending on some external trigger.
        • The actual data that is displayed by the widgets within each page can change (e.g., there are tables, images, labels, or other views that can be updated).

        Depending on the events coming from the state machine, the underlying models that are driving the views pick from different data sets, therefore the content visible to the user is updated.

        Some exampes:

        • one of the pages is responsible for the selection of an entry from a table: the state machine just requests one entry of a specific type X, the UI triggers a model reload based on X and waits for the user to select the entry, than notifies to the state machine that entry has been selected.
        • one page displays some message to the user asking for confirmation in order to proceed.
        • one page keeps the user updated on how the interaction with some external system is progressing.

        I'm glad you pointed to StackLayout, as it seems also to me to be more or less the QML equivalent of QStackedWidget, so maybe I can avoid a heavy refactoring.

        Currently, I am trying to implement point a in my original question:

        • Keep each page in a separated .qml file (as equivalent of the .ui)
        • Make the current C++ 'page' classes, that are derived from QWidget, the interfaces of these QMLs.
        • Create the QML items as QQmlComponents in the constructors of these interfaces (the old UI::<class> pointers are replaced with QQmlComponents ones).
        • Find a way to attach the C++ models to these components.
        • Find a way to add these components to the StackLayout in the main QML.

        This seems to me to be a quite conservative approach, as in fact I am trying to replace QWidgets with QML at the end. Last two point are the ones where some guidance would probably avoid me doing silly errors. Maybe, and that's why I opened this thread, there was a simpler / more recommended way to proceed.

        B 1 Reply Last reply
        0
        • M meslor

          Let's see if I can make things more clear :)

          • The set of possible pages is known in advance: they are all created at application startup and added to the QStackedWidget as its children.
          • The structure of each page is fixed: there are no widgets that are added/removed on the fly at runtime depending on some external trigger.
          • The actual data that is displayed by the widgets within each page can change (e.g., there are tables, images, labels, or other views that can be updated).

          Depending on the events coming from the state machine, the underlying models that are driving the views pick from different data sets, therefore the content visible to the user is updated.

          Some exampes:

          • one of the pages is responsible for the selection of an entry from a table: the state machine just requests one entry of a specific type X, the UI triggers a model reload based on X and waits for the user to select the entry, than notifies to the state machine that entry has been selected.
          • one page displays some message to the user asking for confirmation in order to proceed.
          • one page keeps the user updated on how the interaction with some external system is progressing.

          I'm glad you pointed to StackLayout, as it seems also to me to be more or less the QML equivalent of QStackedWidget, so maybe I can avoid a heavy refactoring.

          Currently, I am trying to implement point a in my original question:

          • Keep each page in a separated .qml file (as equivalent of the .ui)
          • Make the current C++ 'page' classes, that are derived from QWidget, the interfaces of these QMLs.
          • Create the QML items as QQmlComponents in the constructors of these interfaces (the old UI::<class> pointers are replaced with QQmlComponents ones).
          • Find a way to attach the C++ models to these components.
          • Find a way to add these components to the StackLayout in the main QML.

          This seems to me to be a quite conservative approach, as in fact I am trying to replace QWidgets with QML at the end. Last two point are the ones where some guidance would probably avoid me doing silly errors. Maybe, and that's why I opened this thread, there was a simpler / more recommended way to proceed.

          B Offline
          B Offline
          Bob64
          wrote on last edited by Bob64
          #4

          @meslor Thanks for the clarification.

          As far as your plan is concerned, I wonder if you are creating more complication than is necessary. You first point is reasonable - create the pages as separate QML components, each in its own file. However I don't understand the motivation for creating them as QQmlComponents in the C++ layer.

          Why not simply add them to the StackLayout in QML? So for example if you have Page1.qml, Page2.qml, ..., you might have a "PageStack" component that has this sort of structure:

          StackLayout {
              currentIndex: ???
              Page1 {
                     ...
              }
              Page2 {
                  ...
              }
             ...
          }
          

          Note that currentIndex is what determines which item is currently shown, counting the page items in definition order. You will probably want to tie this into your C++ layer. Because I am used to it, my approach here would probably be to expose a C++ class as a "context property" to QML and this would expose a "current page" property that would be tied to the currentIndex. However, rather than use a "context property" (see QQmlContext::setContextProperty) , I believe the modern advice to achieve the same goal is instead to use qmlRegisterSingletonType and expose the C++ functionality as a singleton class.

          I suspect the questions then will be about how to interface the pages' content to C++. As mentioned previously, a lot will depend on the details of what is in the pages. For example, if you already have instances of the various types of Qt model classes (QAbstractItemModel and sub-classes) in the C++ layer, these are fairly easy to expose to QML. Otherwise the mentioned setContextProperty/qmlRegisterSingletonType approach is available.

          M 1 Reply Last reply
          0
          • B Bob64

            @meslor Thanks for the clarification.

            As far as your plan is concerned, I wonder if you are creating more complication than is necessary. You first point is reasonable - create the pages as separate QML components, each in its own file. However I don't understand the motivation for creating them as QQmlComponents in the C++ layer.

            Why not simply add them to the StackLayout in QML? So for example if you have Page1.qml, Page2.qml, ..., you might have a "PageStack" component that has this sort of structure:

            StackLayout {
                currentIndex: ???
                Page1 {
                       ...
                }
                Page2 {
                    ...
                }
               ...
            }
            

            Note that currentIndex is what determines which item is currently shown, counting the page items in definition order. You will probably want to tie this into your C++ layer. Because I am used to it, my approach here would probably be to expose a C++ class as a "context property" to QML and this would expose a "current page" property that would be tied to the currentIndex. However, rather than use a "context property" (see QQmlContext::setContextProperty) , I believe the modern advice to achieve the same goal is instead to use qmlRegisterSingletonType and expose the C++ functionality as a singleton class.

            I suspect the questions then will be about how to interface the pages' content to C++. As mentioned previously, a lot will depend on the details of what is in the pages. For example, if you already have instances of the various types of Qt model classes (QAbstractItemModel and sub-classes) in the C++ layer, these are fairly easy to expose to QML. Otherwise the mentioned setContextProperty/qmlRegisterSingletonType approach is available.

            M Offline
            M Offline
            meslor
            wrote on last edited by
            #5

            @Bob64 Thanks for the detailed feedback.

            I am proceeding as per your suggestion:

            • All QML pages are loaded at startup as part of a single 'MainWindow.qml' file.
            • The MainWindow C++ class that previously implemented the interface to the MainWindow Widget now exposes invokables/slots/properties accessible from its .qml (will refactor this as next step, to date is fine).

            For the moment, old C++ classes that previously implemented the Widget pages are exposed by the MainWindow class via Q_INVOKABLE methods so that the QML can directly access signals and slots as if it was an UI file.
            By calling qRegisterMetatype("xxxPage*") I can directly invoke from QML their slots / emit their signals as if the 'old' user interface was present.

            To date, porting seems to be feasible, btw I encountered an issue I was not expecting.
            All my C++ 'Page' classes are derived from a common AbsPage class that provides signals and slots.
            It seems that I am not able to call the slots/signals that are exposed in the derived classes:

            • The AbsPage class relies on NVI pattern so that derived classes implement their own methods w/o overriding the slots.
            • In the QML, the slots declared in the AbsPage class are not accessible ("TypeError: Property 'init' of object xxxPage_QMLTYPE_NN is not a function".

            I've investigated a bit, but did not find any specific help on this, as all examples always present slots that are direct members of the C++ class that is accessed from QML.

            Do you have any idea if this is achievable or not?
            It seems strange to me that QML does not access slots that moc has already setup as members of base classes.

            B 1 Reply Last reply
            0
            • M meslor

              @Bob64 Thanks for the detailed feedback.

              I am proceeding as per your suggestion:

              • All QML pages are loaded at startup as part of a single 'MainWindow.qml' file.
              • The MainWindow C++ class that previously implemented the interface to the MainWindow Widget now exposes invokables/slots/properties accessible from its .qml (will refactor this as next step, to date is fine).

              For the moment, old C++ classes that previously implemented the Widget pages are exposed by the MainWindow class via Q_INVOKABLE methods so that the QML can directly access signals and slots as if it was an UI file.
              By calling qRegisterMetatype("xxxPage*") I can directly invoke from QML their slots / emit their signals as if the 'old' user interface was present.

              To date, porting seems to be feasible, btw I encountered an issue I was not expecting.
              All my C++ 'Page' classes are derived from a common AbsPage class that provides signals and slots.
              It seems that I am not able to call the slots/signals that are exposed in the derived classes:

              • The AbsPage class relies on NVI pattern so that derived classes implement their own methods w/o overriding the slots.
              • In the QML, the slots declared in the AbsPage class are not accessible ("TypeError: Property 'init' of object xxxPage_QMLTYPE_NN is not a function".

              I've investigated a bit, but did not find any specific help on this, as all examples always present slots that are direct members of the C++ class that is accessed from QML.

              Do you have any idea if this is achievable or not?
              It seems strange to me that QML does not access slots that moc has already setup as members of base classes.

              B Offline
              B Offline
              Bob64
              wrote on last edited by Bob64
              #6

              @meslor I wasn't sure about your slots inheritance question as I don't think it's something I have tried to do. However, I just tried it with a toy project and it seems to work for me.

              Below is my code, based on a QtCreator-generated empty Qt Quick app. Note that I tried using two methods of exposing C++ to QML - a context property and using qmlRegisterType and both seem to work . You mentioned qRegisterMetatype - I don't know if this was just a slip when you were writing it or whether it is what you really meant. If that is what you are using, I have to be honest and say that I have never made any use of that facility myself and am not sure how it relates to QML exposure, so I can't offer any advice.

              Note that my example doesn't do anything exciting - it just writes to debug output, so you need to look at application output to see it do anything.

              base.h

              #pragma once
              #include <QObject>
              
              class Base : public QObject
              {
                  Q_OBJECT
              public:
                  explicit Base(QObject *parent = nullptr);
              public slots:
                  void doIt();
              private:
                  virtual void doItImpl() = 0;
              };
              

              base.cpp

              #include "base.h"
              
              Base::Base(QObject *parent) : QObject(parent) {}
              
              void Base::doIt()
              {
                  doItImpl();
              }
              

              derived.h

              #pragma once
              #include "base.h"
              
              class Derived : public Base
              {
                  Q_OBJECT
              public:
                  Derived();
              private:
                  void doItImpl();
              };
              

              derived.cpp

              #include "derived.h"
              #include <QtDebug>
              
              Derived::Derived() : Base(nullptr) {}
              
              void Derived::doItImpl()
              {
                  qDebug() << "Here!";
              }
              

              main.cpp

                  ...
                  QQmlApplicationEngine engine;
                  // Note: two ways of exposing to QML tried:
              
                  // 1. Context property
                  Derived d;
                  engine.rootContext()->setContextProperty("dctxt", &d);
              
                  // 2. Registered type so instantiable from QML
                  qmlRegisterType<Derived>("MyTypes", 1, 0, "Derived");
              
                  ...
              

              main.qml

              import QtQuick 2.9
              import QtQuick.Controls 2.2
              import QtQuick.Window 2.2
              
              import MyTypes 1.0
              
              Window {
                  id: root
                  visible: true
                  width: 640
                  height: 480
                  title: qsTr("Hello World")
              
                  Button {
                      text: "Hello"
                      anchors.centerIn: parent
                      onClicked: {
                          dctxt.doIt(); // invoke on context property exposed object
                          d.doIt();     // invoke on QML object instantiated below
                      }
                  }
                  Derived { 
                      id: d
                  }
              }
              
              1 Reply Last reply
              0
              • M Offline
                M Offline
                meslor
                wrote on last edited by
                #7

                Many thanks for the valuable feedback.

                After testing some alternatives, I evaluated that exposing the C++ API to the QML (as per your suggestion) would quickly make the code less maintainable for complex projects. There are a lot of nested classes & widgets, and this approach requires to propagate these slots so that they are accessible from the context.

                As far as I understand so far, the preferred path when QML and C++ are involved is that the the QML modules are the 'main controllers' and the C++ stuff is used 'as needed', there is why for example the singleton makes sense somehow.
                The example you provided, where the Derived class is instantiated in the QML, make this very clear.

                What I am trying to achieve is the exact opposite: having C++ code that uses the QML.
                The cleanest way I've found to keep the C++ classes as the 'main controllers' is to totally decouple the C++ from the QML:

                • One single QML file instantiates all the UI (as per your suggestion with the stack).
                • All nested QML objects that need to be accessed by C++ are named, therefore the C++ can search for children by name.
                • All QMLs exposes signals / properties related to the main UI.

                In this way, each C++ class can still be directly managing the UI, as usually done when widgets are involved.
                Instead of having a setupUi() call, some other class method searches for the QML objects by name and connects to signals.
                This is where establishing connections from QML signals to C++ derived classes seems not to work.

                After some extensive test, my opinion is that the effort to manage the UI in this way is not convenient.
                For mobile a simple approach with QML seems good and very fast, but for complex Desktop applications simple widgets still seem the right choice to me. This is indeed due also to my lack of experience with QML, for sure.

                Of course, any feedback is greatly appreciated on this :)

                1 Reply Last reply
                0
                • GrecKoG Offline
                  GrecKoG Offline
                  GrecKo
                  Qt Champions 2018
                  wrote on last edited by
                  #8

                  Starting from scratch using QML for complex desktop applications is perfectly fine.
                  The recommended way of exposing C++ to QML and not manipulating QML from C++ makes perfect sense and is not a drawback, quite the opposite.

                  The QML code doesn't have to be the "main controller", it's just the first point of entry to it. You don't have to instantiate your business classes in QML like the Derived shown above.

                  M 1 Reply Last reply
                  0
                  • B Offline
                    B Offline
                    Bob64
                    wrote on last edited by
                    #9

                    I don't know if it's worth me saying that my example was never intended to propose any particular architectural approach. It was purely about demonstrating that the slot mechanism works in derived classes.

                    M 1 Reply Last reply
                    0
                    • GrecKoG GrecKo

                      Starting from scratch using QML for complex desktop applications is perfectly fine.
                      The recommended way of exposing C++ to QML and not manipulating QML from C++ makes perfect sense and is not a drawback, quite the opposite.

                      The QML code doesn't have to be the "main controller", it's just the first point of entry to it. You don't have to instantiate your business classes in QML like the Derived shown above.

                      M Offline
                      M Offline
                      meslor
                      wrote on last edited by
                      #10

                      @GrecKo I am sure this is the case, I was not very clear as I was referring to porting already complex applications from QWidgets to QML.
                      Starting from scratch, I expect that thing are easier, also considering my limited knowledge on QML.
                      Is there any chance you are aware of some open source project that made this step, so that I can take a look at how this has been addressed?

                      1 Reply Last reply
                      0
                      • B Bob64

                        I don't know if it's worth me saying that my example was never intended to propose any particular architectural approach. It was purely about demonstrating that the slot mechanism works in derived classes.

                        M Offline
                        M Offline
                        meslor
                        wrote on last edited by
                        #11

                        @Bob64 It was very clear, thanks :)

                        1 Reply Last reply
                        0

                        • Login

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