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. How to pass C++ collections to QML
Forum Updated to NodeBB v4.3 + New Features

How to pass C++ collections to QML

Scheduled Pinned Locked Moved Solved QML and Qt Quick
10 Posts 2 Posters 2.3k 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.
  • C Offline
    C Offline
    Catch_0x16
    wrote on last edited by Catch_0x16
    #1

    Hello everyone,

    I've spent the past few days reading a lot about QML and I'm still not seeing clearly the best way of using collections of C++ objects in my QML files (or even, how to do it at all!). I've got 10+ years experience with C++, but I'm basically completely new to QML.

    As anyone who has read my previous post (which has still not got any replies :'( ) will have seen, I'm building an application that uses the QML Map type to display airports and navigation data to the user.

    In my C++ model I have an AirportManager, which currently holds a QHash object of every airport in the world, parsed from a local database. The task I have set myself for this evening, is to get the C++ 'Airport' classes to appear on the map (I'll implement rudimentary BSP sorting tomorrow).

    In it's simplest form, I'd just like to have the following sequence:

    • User Scrolls the map in QML
    • Map sends out signal with new Lat and Lon coordinates
    • Airport manager class (C++) receives the signal and updates it's position of interest (then BSP filters the maps etc.)
    • Airport manager sends out signal with a QList of airports to render on the map
    • QML Map receives the signal, and renders the list of objects

    I've tried creating a Q_PROPERTY(QList<Airport> airports READ getAirports) but this implies that I need to tell the QML to specifically find the airport manager and get the list.
    Is there a way to rely purely on signals and slots? Ideally I'd like to abstract my C++ implementation from the QML implementation.

    I know that my QML map needs to have a MapItemView, which in my implementation has a model of type ListModel and a delegate of type Component. Within the delegate I added a MapQuickItem but I don't know how to make this refer to the coordinates from the model items?

    Please excuse my ignorance, I'm still really trying hard to get my head around the way to pass info between QML and C++. Literally any response that teaches me more about how this works is more than welcome.

    Thanks, Love you all

    Catch_0x16

    1 Reply Last reply
    0
    • sierdzioS Offline
      sierdzioS Offline
      sierdzio
      Moderators
      wrote on last edited by
      #2

      QML understands QObject pointers, you can send these via signals and slots, no problem.

      If you add your manager to QML context, like this:

      engine.rootContext()->setContextProperty(QStringLiteral("airportManager"), airportManager);
      

      you can then use it in Connections component like so:

      Connections {
        target: airportManager
        onSomeSignal: {
          // Handle your QObjects here
        }
      }
      

      You'll likely need to create new QML components and fill their data with data from these airport objects (because otherwise QML engine would have to take ownership of these objects, which is a big can of worms).

      Some docs on this:

      • https://doc.qt.io/qt-5/qtqml-cppintegration-topic.html
      • https://doc.qt.io/qt-5/qtqml-cppintegration-overview.html

      There are many ways to achieve QML-C++ connectivity so it's a bit hard for me to judge which approach is best in your case.

      (Z(:^

      1 Reply Last reply
      5
      • C Offline
        C Offline
        Catch_0x16
        wrote on last edited by
        #3

        Thank you so much for your informative reply. I've looked all over and come across many ways of passing data, which I think confused me a little. But your advice of setting the context property is I think what I wanted to do. That way the C++ has no idea of what is going on in the QML, but I can reference the slots and properties of the C++ object via QML - perfect!

        Thanks again.

        1 Reply Last reply
        1
        • C Offline
          C Offline
          Catch_0x16
          wrote on last edited by
          #4

          @sierdzio I wonder if you might be able to help me debug...

          my QML file, which looks like this:

              AirportMap {
                  id: airportMap
          
                  Connections {
                      target: AirportManager
          
                      onNewAirportDrawListReady: {
                          console.log("Called the event")
                          var drawList = AirportManager.getAirportDrawList
                          airportMap.onNewAirportData(drawList)
                          console.log("Finished the event")
                      }
                  }
              }
          

          never actually triggers the onNewAirportDrawListReady function.

          My AirportManager class looks like:

          #ifndef AIRPORTMANAGER_H
          #define AIRPORTMANAGER_H
          
          #include <QObject>
          #include <QFile>
          #include <QHash>
          #include <QPoint>
          
          #include "airport.h"
          
          class AirportManager : public QObject
          {
              Q_OBJECT
          
              Q_PROPERTY(QList<QWeakPointer<Airport>> airportDrawList READ getAirportDrawList)
          
          public:
              explicit AirportManager(QObject *parent = nullptr);
              QList<QWeakPointer<Airport>> getAirportDrawList() const;
          
          signals:
              void newMapFocusPosition(double lat, double lon);
              void newAirportDrawListReady();
          
          public slots:
              void onNewMapFocusPosition(double lat, double lon);
          
          private:
          
              QList<QWeakPointer<Airport>> mAirportDrawList;
              QHash<QString,QSharedPointer<Airport>> mAllAirports;
              QPointF mFocusPosition;
          };
          
          #endif // AIRPORTMANAGER_H
          

          And my main looks like:

          #include <QGuiApplication>
          #include <QQmlApplicationEngine>
          #include <QQmlContext>
          
          #include "airportmanager.h"
          
          int main(int argc, char *argv[])
          {
              QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
          
              QGuiApplication app(argc, argv);
          
              // Register types called by QML
              qmlRegisterType<Airport>("com.gtinteractive.airport", 1,0,"Airport");
          
              AirportManager mgr;
          
              QQmlApplicationEngine engine;
              engine.rootContext()->setContextProperty(QStringLiteral("AirportManager"), &mgr);
              engine.load(QUrl(QStringLiteral("qrc:/QML/main.qml")));
              if (engine.rootObjects().isEmpty())
                  return -1;
          
              return app.exec();
          }
          

          Any ideas why the events aren't triggering? (I'm definitely emitting the event, I've triple checked).

          1 Reply Last reply
          0
          • sierdzioS Offline
            sierdzioS Offline
            sierdzio
            Moderators
            wrote on last edited by
            #5
            • do you get any warnings at runtime? Look for any messages suggesting that there is some error with connection.
            • are you sure you have only one AirportManager object? Sounds silly I know, but I've seen people emit signals from one instance and wondering why slot is not invoked when it is connected to a different object
            • this is probably not the issue, but try renaming the context property to something starting with lower-case character (like airportManager). QML engine has some special handling for strings starting with a capital letter, perhaps you've hit some corner case

            Code looks good.

            (Z(:^

            1 Reply Last reply
            0
            • C Offline
              C Offline
              Catch_0x16
              wrote on last edited by Catch_0x16
              #6

              Thanks for your help again @sierdzio . I managed to work out that though I was emitting the signal... I hadn't started the application loop yet, so none of the signals/slots were working - doh!

              I'm now faced with the issue that no matter what I do, the data is always 'undefined' in the QML.

              Here's my QML, thanks to @sierdzio it's definitely receiving the event (thanks again mate).:

                  function onNewAirportData(airportsArray) {
              
                      largeAirportsModel.clear()
                      for (var i = 0; i < airportsArray.length; i++) {
                          //largeAirportsModel.append({coordinates: airport.coordinates, imageUrl: airport.imageUrl, name: airport.name, ident: airport.ident})
                          console.log("airportsArray length: " + airportsArray.length)
                          console.log("coords = " + airportsArray[i].coordinates)
                          console.log("imageUrl = " + airportsArray[i].imageUrl)
                          console.log("name = " + airportsArray[i].name)
                          console.log("ident = " + airportsArray[i].ident)
                      }
                  }
              

              Here's the output from that function (repeated 200 times):

              qml: airportsArray length: 200
              qml: coords = undefined
              qml: imageUrl = undefined
              qml: name = undefined
              qml: ident = undefined
              

              And here is the C++ code that compiles the QVariants:

              QPointer<AirportMapDrawData> Airport::getMapDrawData() const
              {
                  return QPointer<AirportMapDrawData>(new AirportMapDrawData(mIdent, mName , getImageURL(), mId, CoordinateType(mLat, mLon)));
              }
              
              void AirportManager::onNewMapFocusPosition(double lat, double lon)
              {
                  mFocusPosition = QPointF(lat, lon);
              
                  // TEMP
                  mAirportDrawList.clear();
                  for (int i = 0; i < 200; ++i)
                  {
                      QPointer<AirportMapDrawData> ptr = (mAllAirports.begin() + i).value()->getMapDrawData();
                      mAirportDrawList.push_back(QVariant::fromValue(ptr));
                  }
              
                  emit newAirportDrawListReady();
              }
              

              I've inserted a breakpoint on the line before the AirportMapDrawData object is pushed into the airport list and the data is definitely there.

              Here is the header for the AirportMapDrawData object:

              typedef QPair<double,double> CoordinateType;
              Q_DECLARE_METATYPE(CoordinateType);
              
              class AirportMapDrawData : public QObject
              {
                  Q_OBJECT
              
                  Q_PROPERTY(CoordinateType coordinates READ getPosition)
                  Q_PROPERTY(QString ident READ getIdent)
                  Q_PROPERTY(QString name READ getName)
                  Q_PROPERTY(QUrl imageUrl READ getImageURL)
                  Q_PROPERTY(int id READ getID)
              
              public:
                  explicit AirportMapDrawData(const QString& ident, const QString& name, const QUrl& imageUrl, int id, const CoordinateType& position, QObject *parent = nullptr);
              
                  CoordinateType getPosition() const;
                  QString getIdent() const;
                  QString getName() const;
                  QUrl getImageURL() const;
                  int getID() const;
              
              private:
              
                  QString mIdent;
                  QString mName;
                  QUrl mImageUrl;
                  int mId;
                  CoordinateType mPosition;
              };
              

              And it is currently being registered like so:

                  qmlRegisterUncreatableType<AirportMapDrawData>("com.gtinteractive.airportmapdata", 1,0,"AirportMapData","This is a POD class from C++");
              

              Any ideas why the data is null when it enters QML?

              1 Reply Last reply
              0
              • sierdzioS Offline
                sierdzioS Offline
                sierdzio
                Moderators
                wrote on last edited by
                #7

                QObjects do not need to be registered via qmlRegisterUncreatableType, although it should not hurt.

                To keep things simple (you can complicate again it once it starts working :-)), try sending a QObjectList and not a custom type. Your AirportMapDrawData should append to QObjectList without any problems.

                In QML, what is the output of:

                console.log("airportsArray length: " + airportsArray.length)
                console.log("current objecth: " + airportsArray[i])
                

                Is it also undefined, or does it print some object info?

                Also, what is airportsArray in QML? How do you create it?

                (Z(:^

                1 Reply Last reply
                1
                • C Offline
                  C Offline
                  Catch_0x16
                  wrote on last edited by
                  #8

                  Hey @sierdzio thanks again for the replies.

                  I've changed the QVariantList object to a QObjectList but now I'm getting:

                  QMetaProperty::read: Unable to handle unregistered datatype 'QObjectList' for property 'AirportManager::airportDrawList'
                  qrc:/QML/AirportMap.qml:37: TypeError: Cannot read property 'length' of undefined
                  

                  during run time, from the QML engine (presumably).

                  1 Reply Last reply
                  0
                  • C Offline
                    C Offline
                    Catch_0x16
                    wrote on last edited by
                    #9

                    Also, apologies, I did not answer your questions:

                    In QML, what is the output of:
                    
                    console.log("airportsArray length: " + airportsArray.length)
                    console.log("current objecth: " + airportsArray[i])
                    Is it also undefined, or does it print some object info?
                    
                    Also, what is airportsArray in QML? How do you create it?
                    

                    I'm creating the list via this code in the QML:

                            Connections {
                                target: airportManager
                    
                                onNewAirportDrawListReady: {
                                    var drawList = airportManager.airportDrawList
                                    airportMap.onNewAirportData(drawList)
                                }
                            }
                    

                    Since I changed the Q_PROPERTY of AirportManager to return Q_PROPERTY(QList<QPointer<AirportMapDrawData>> airportDrawList READ getAirportDrawList) (I got errors when I set it to QObjectList) I get the following outputs from QML:

                    qml: airportsArray length: undefined
                    qml: current object: undefined
                    
                    sierdzioS 1 Reply Last reply
                    0
                    • C Catch_0x16

                      Also, apologies, I did not answer your questions:

                      In QML, what is the output of:
                      
                      console.log("airportsArray length: " + airportsArray.length)
                      console.log("current objecth: " + airportsArray[i])
                      Is it also undefined, or does it print some object info?
                      
                      Also, what is airportsArray in QML? How do you create it?
                      

                      I'm creating the list via this code in the QML:

                              Connections {
                                  target: airportManager
                      
                                  onNewAirportDrawListReady: {
                                      var drawList = airportManager.airportDrawList
                                      airportMap.onNewAirportData(drawList)
                                  }
                              }
                      

                      Since I changed the Q_PROPERTY of AirportManager to return Q_PROPERTY(QList<QPointer<AirportMapDrawData>> airportDrawList READ getAirportDrawList) (I got errors when I set it to QObjectList) I get the following outputs from QML:

                      qml: airportsArray length: undefined
                      qml: current object: undefined
                      
                      sierdzioS Offline
                      sierdzioS Offline
                      sierdzio
                      Moderators
                      wrote on last edited by
                      #10

                      @Catch_0x16 said in How to pass C++ collections to QML:

                      qml: airportsArray length: undefined

                      That line used to work, so something is now more broken than it was ;-)

                      Unable to handle unregistered datatype 'QObjectList'

                      Bummer. Perhaps QList<QObject*> is registered. Anyway, one more thought: perhaps remove the QPointer<> from property definition, return types etc.? I'm not sure if QML engine handles QPointers well and it's not needed here anyway.

                      (Z(:^

                      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