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. QML <- c++ sending variables
Qt 6.11 is out! See what's new in the release blog

QML <- c++ sending variables

Scheduled Pinned Locked Moved Unsolved General and Desktop
9 Posts 2 Posters 5.5k 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
    MScottM
    wrote on last edited by
    #1

    I've been interested in, and working on, analyzing data from log files that come from data written out of a CANBus network. I've been able to write a couple programs that load, parse and graph the data from the log files. Now I want to keep extending my learning by now trying to look at the live data flowing over the bus. I've been working with the CANBus example that comes with a recent version of QT Creator, and I was able to get that working without too much trouble. I've been trying to extend it to allow me to see specific data that I'm interested in, and have had some success. I can look for a specific PGN on the bus and write out to the qDebug console the decoded messages. Here is the code I added to the examples' "processReceivedFrames" function:

    void MainWindow::processReceivedFrames()
    {
        if (!m_canDevice)
            return;
    
        while (m_canDevice->framesAvailable()) {
            const QCanBusFrame frame = m_canDevice->readFrame();
    
            QString view;
            if (frame.frameType() == QCanBusFrame::ErrorFrame)
                view = m_canDevice->interpretErrorFrame(frame);
            else
                view = frame.toString();
                //qDebug() << view;
                QTextStream canStream(&view);
                QString line;
    
                while (canStream.readLineInto(&line)) {
                    const QStringList canParts = line.split(' ',QString::KeepEmptyParts);
                    CanId = canParts.at(0);
                    DB0 = canParts.at(5);
                    DB1 = canParts.at(6);
                }            
    
               const unsigned int bitmask = 0x01FFFF;
               int canArray = CanId.toInt(&ok,16);
               //qDebug()<<canArray;
    
               canArray = canArray>>8;
               //qDebug()<<canArray;
    
                PgnNum = canArray & bitmask;
                //qDebug() << PgnNum << " " << D0;
    
                PgnDoSomething(PgnNum, DB0, DB1);
    
            const QString time = QString::fromLatin1("%1.%2  ")
                    .arg(frame.timeStamp().seconds(), 10, 10, QLatin1Char(' '))
                    .arg(frame.timeStamp().microSeconds() / 100, 4, 10, QLatin1Char('0'));
    
            const QString flags = frameFlags(frame);
    
            m_ui->receivedMessagesEdit->append(time + flags + view);
        }
    }
    

    and here is my function 'pgnDoSomething' (it's a mess, I know):

    int MainWindow::PgnDoSomething(int mPgnNum, QString mD0, QString mD1) {
    
        QVariant hMessage;
        //qDebug() << mPgnNum << " " << mD0;
    
        if (mPgnNum = heading) {
            QString m_vHeading = mD0 + mD1;
            double computedVHeading = m_vHeading.toInt(&ok, 16);
            hMessage = computedVHeading;
    
            qDebug() << "computed vesselHeading " << computedVHeading;
    
            return computedVHeading;
        }
    
    }
    

    I'm trying to load a QML window with a dial and set the value of the dial with the computed value from the pgnDoSomething function, and I'm making no progress on getting it to work. My latest error is "QQmlComponent: Component is not ready"
    At this point I'm just trying to send a value over to the QML window and have it show up. Here is my 'compass.qml':

    import QtQuick 2.2
    import QtQuick.Window 2.3
    import QtQuick.Controls 1.4
    import QtQuick.Controls.Styles 1.4
    import QtQuick.Extras 1.4
    
    Window {
    
    
    
    Item {
        Gauge {
            id: hGauge
            objectName: "hdGauge"
            minimumValue: 0
            value: vesselHeading
            maximumValue: 360
            anchors.centerIn: parent
            function myHeadingFunction(hMessage) {
                console.log("got message", hMessage)
            }
        }
    }
    }
    

    and my main.cpp:

    #include "mainwindow.h"
    #include <QApplication>
    #include <QtQml/QQmlApplicationEngine>
    #include <QtQml/QQmlComponent>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
        QQmlEngine engine;
        QQmlComponent component(&engine, QUrl::fromLocalFile("qrc:/compass.qml"));
        QObject *object = component.create();
    
        object->setProperty("value", "100");
    
        return a.exec();
    }
    

    If things look pretty messy, it's because I've spent hours (over days) trying to modify things to get it to work. I've read over numerous QT documentation pages here, for example:

    Interacting with QML Objects from C++

    and here:

    Using QML Bindings in C++ Applications

    and many other pages and examples, but I just can't seem to get anything to work - I just don't quite understand it.

    I would appreciate any advice, and/or (working) examples, or tips to get me on the right track

    Thanks!

    1 Reply Last reply
    0
    • KillerSmathK Offline
      KillerSmathK Offline
      KillerSmath
      wrote on last edited by KillerSmath
      #2

      Hi there.

      When QUrl::fromLocalFile tries to resolve the url, this fuction return the wrong path to file.

      >> Debug log

      Documents/build-testProject-Desktop_Qt_5_6_2_MinGW_32bit-Debug/qrc:/main.qml:-1 File not found\n
      

      To fix this problem:

      main.cpp

      ...
      int main(int argc, char *argv[])
      {
          QApplication a(argc, argv);
          MainWindow w;
          w.show();
      
          QQmlEngine engine;
          // QQmlComponent component(&engine, QUrl::fromLocalFile("qrc:/compass.qml"));
          QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
          QObject *object = component.create();
      
          object->setProperty("value", "100");
      
          return a.exec();
      }
      ...
      

      But when the componet is created, the function component.create() returns the main father of components (Window), but i noticed you are trying to set value property of a child of Window, this line object->setProperty("value", "100") will try to find the value property in Window element.

      But if you want to get the child with objectName hdGauge and set the property value, you can use this code:

      ... 
           //main function
           QObject *object = component.create(); // create the object
           QObject *child = object->findChild<QObject*>("hdGauge"); // find child with objectName hdGauge
           child->setProperty("value", 100); // set integer value
      ...
      

      Or, you can use an alias property to expose the value to father.

      main.qml

      ...
      
      Window {
      
          property alias gaugeValue: hGauge.value
          
          Item {
              Gauge {
                  id: hGauge
                  objectName: "hdGauge"
                  minimumValue: 0
                  value: vesselHeading
                  maximumValue: 360
                  anchors.centerIn: parent
                  function myHeadingFunction(hMessage) {
                      console.log("got message", hMessage)
                  }
              }
          }
      }
      

      main.cpp

      ...
           QObject *object = component.create(); // create the object
           child->setProperty("gaugeValue", 100); // set integer value using alias to hGauge.value
      ...
      

      I hope I have helped you, good coding.

      >> References

      • Qml Component Documentation
      • Acessing a qml object by Object name
      • Property Alias Documentation

      @Computer Science Student - Brazil
      Web Developer and Researcher
      “Sometimes it’s the people no one imagines anything of who do the things that no one can imagine.” - Alan Turing

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

        Hello @KillerSmath ,

        Thank you for taking the time to answer! Your answers were clear and you helped me get back on track.

        I'm able to get the program to display a circular gauge now, but I'm struggling with where to call certain functions so that only the value that needs to be updated gets passed to the gauge. I put the lines

        QQmlEngine engine;
        QQmlComponent component(&engine, QUrl("qrc:/compass.qml"));
        object = component.create(); // create the object
        
        

        in the MainWindow setup section and defined 'object' in the mainwindow.h file in the Public section.

        I then added the line:

        object->setProperty("gaugeValue", hMessage); // set integer value using alias to hGauge.value
        

        to the function like so:

        int MainWindow::PgnDoSomething(int mPgnNum, QString mD0, QString mD1) {
        
            if (mPgnNum = heading) {
                QString m_vHeading = mD0 + mD1;
                hMessage.setValue((m_vHeading.toInt(&ok, 16)*.0001)*57.296);
                object->setProperty("gaugeValue", hMessage); // set integer value using alias to hGauge.value
                //qDebug() << "computed vesselHeading " << hMessage;
        
                return hMessage.toInt();
            }
        
        }
        

        Now the window with the gauge appears, but the whole program crashes as soon as it starts receiving CAN frames (or at least when I start the CAN messages on the bus).

        I could get it to barely work (needle moving) when I instantiated the object inside the PgnDoSomething function but it was creating a new one every time the function was called...not good!

        Thanks again,

        -Scott

        1 Reply Last reply
        0
        • KillerSmathK Offline
          KillerSmathK Offline
          KillerSmath
          wrote on last edited by KillerSmath
          #4

          okay, i have some questions:

          • Why do you need to send a object to qml ?
          • If you need to send a object to qml, did you try to set this object in context of qml engine?

          I suggest you to read about C++ Object and Qml interation

          • http://doc.qt.io/qt-5/qtqml-cppintegration-overview.html#choosing-the-correct-integration-method-between-c-and-qml
          • http://doc.qt.io/qt-5/qtqml-cppintegration-contextproperties.html
          • http://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html

          >> Below is an example of how you can create an object to directly interaction with qml

          message.h

          #ifndef MESSAGE_H
          #define MESSAGE_H
          
          #include <QObject>
          
          class Message : public QObject
          {
              Q_OBJECT
          
              Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
          
          public:
              Message();
          
              int value();
              void setValue(int value);
          
              Q_INVOKABLE void cppFunction();
          
          
          signals:
              void valueChanged(int value);
          
          private:
              int m_value;
          };
          
          #endif // MESSAGE_H
          

          message.cpp

          #include "message.h"
          #include <qDebug>
          
          Message::Message()
          {
              m_value = 0;
          }
          
          int Message::value()
          {
              return m_value;
          }
          
          void Message::setValue(int value)
          {
              if(m_value != value){
                  m_value = value;
          
                  emit valueChanged(m_value);
              }
          }
          
          void Message::cppFunction()
          {
              qDebug() << "I has been called from Cpp";
          }
          

          main.cpp

          #include <QQmlContext>
          #include <QTimer>
          
          ...
              Message hMessage;
          
              QQmlApplicationEngine engine;
              
              engine.rootContext()->setContextProperty("hMessage", &hMessage);
          
              QTimer::singleShot(5000, [&hMessage](){hMessage.setValue(50);}); // set the Value after 5 seconds
          
          ...
          

          compass.qml

          Window { 
                 Gauge {
                  id: hGauge
                  objectName: "hdGauge"
                  minimumValue: 0
                  maximumValue: 360
                  value: hMessage.value
                  anchors.centerIn: parent
                  
              }
              
              Connections {
                  target: hMessage
                  onValueChanged: console.log("The value has changed to: " + value)
              }
                 
              Component.onCompleted: hMessage.cppFunction()
          }
          

          Observation: the c++ object need to be setted on context before any qml file is loaded or the qml file will not find the c++ object.

          @Computer Science Student - Brazil
          Web Developer and Researcher
          “Sometimes it’s the people no one imagines anything of who do the things that no one can imagine.” - Alan Turing

          1 Reply Last reply
          3
          • M Offline
            M Offline
            MScottM
            wrote on last edited by
            #5

            @KillerSmath - thank you again for taking time to respond and help noobies like me! To answer your question "Why do you need to send a object to qml ?" - answer, so I can understand how. I like the apparent ease of creating instrument layouts in QML, but with Qt, have only ever coded in c++, so I'm trying to learn a new skill!

            Okay, I've spent the last week trying to adapt the example above to my code and I finally got it to compile, but my QML gauge still isn't updating with the incoming CAN messages. If I uncomment the QTimer though, it WILL update the gauge. Any thoughts? Here is my relevant code so far:

            main.cpp

            #include "mainwindow.h"
            #include "message.h"
            #include <QApplication>
            #include <QtQml/QQmlApplicationEngine>
            #include <QtQml/QQmlComponent>
            //#include <QQuickView>
            //#include <QQuickItem>
            #include <QObject>
            #include <QQmlContext>
            #include <QTimer>
            
            int main(int argc, char *argv[])
            {
                QApplication a(argc, argv);
                MainWindow w;
                w.show();
            
                    Message hMessage;
            
                    QQmlApplicationEngine engine;
                    QQmlComponent component(&engine, QUrl("qrc:/compass.qml")); //added 6-14
                    engine.rootContext()->setContextProperty("hMessage", &hMessage);
                    QObject *object = component.create(); //added 6-14
            
                    //QTimer::singleShot(5000, [&hMessage](){hMessage.setValue(50);}); // set the Value after 5 seconds
            
                return a.exec();
            }
            

            my function pgnDoSomething() in MainWindow.cpp, which takes in the messages and in the future I want it to send parsed data to different gauges/text displays:

            void MainWindow::PgnDoSomething(int mPgnNum, QString mD0, QString mD1, QString mD2) {
            
                Message hMessage;
            
                if ((mPgnNum == heading)) {
                    QString m_vHeading = mD1 + mD0;
                    int msg_vHeading = (m_vHeading.toInt(&ok, 16)*.0001)*57.296;
                    //hMessage.setValue(msg_vHeading);
                    hMessage.setValue(msg_vHeading);
                    qDebug() << "sent value: " << msg_vHeading;
                    }
            }
            

            message.h:

            #ifndef MESSAGE_H
            #define MESSAGE_H
            
            #include <QObject>
            
            class Message : public QObject
            {
                Q_OBJECT
            
                Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
            
            public:
                Message();
            
                int value();
                Q_INVOKABLE void setValue(int);
            
            signals:
                void valueChanged(int value);
            
            private:
                int m_value;
            };
            
            #endif // MESSAGE_H
            

            message.cpp is unchanged from above.
            Compass.qml:

            import QtQuick 2.2
            import QtQuick.Window 2.3
            import QtQuick.Controls 1.4
            import QtQuick.Controls.Styles 1.4
            import QtQuick.Extras 1.4
            
            Window {
                //flags: Qt.WindowStaysOnTopHint
                visible: true
                width: 320
                height: 320
            
            //    property alias gaugeValue: hGauge.value
            
                Item {
                    anchors.centerIn: parent
                   CircularGauge {
                        id: hGauge
                        objectName: "hdGauge"
                        minimumValue: 0
                        value: hMessage.value
                        maximumValue: 360
                        anchors.centerIn: parent
                        function myHeadingFunction(gaugeValue) {
                            console.log("got message", hMessage)
                        }
                    }
                }
            
                Connections {
                        target: hMessage
                        onValueChanged: console.log("The value has changed to: " + value)
                    }
            
                    //Component.onCompleted: hMessage.setValue(value)
            }
            

            My guess is that my function (pgnDoSomething) isn't interacting with the Message class correctly??

            1 Reply Last reply
            0
            • KillerSmathK Offline
              KillerSmathK Offline
              KillerSmath
              wrote on last edited by
              #6

              @MScottM
              Hi Again.

              I noticed you are efforting so much to know new features about qt and i apreciate that :)

              Signals and Slots are good mechanims when you need to create a communication among differente parts of your code.

              Your code emite a signal and this signal call a function (slot)

              Signal Slot Documentation <- Read More

              You will see many examples about this feature in Qt examples.

              Below is an github repository where i uploaded a good example about your implementation.

              Gauge Project Repository

              Good study and good coding :)

              @Computer Science Student - Brazil
              Web Developer and Researcher
              “Sometimes it’s the people no one imagines anything of who do the things that no one can imagine.” - Alan Turing

              1 Reply Last reply
              4
              • M Offline
                M Offline
                MScottM
                wrote on last edited by
                #7

                I will download and study it this weekend. Thanks!

                1 Reply Last reply
                1
                • M Offline
                  M Offline
                  MScottM
                  wrote on last edited by
                  #8

                  Okay, so I mostly get how this example is working. My problem is how to connect MY function, that is updating a number whenever a message passes the if() statement, to 'sendMessage', instead of having to use a button? I've been reading up on signals and slots, but I just don't quite get it yet!

                  1 Reply Last reply
                  0
                  • KillerSmathK Offline
                    KillerSmathK Offline
                    KillerSmath
                    wrote on last edited by
                    #9

                    @MScottM You could create a signal at MainWindow and connect this signal to slot of Message Class.

                    PseudoCode
                    sendData(*/ Your Data */) -> receiveData(/* Your Data */);

                    So, you implement the receiveData slot to set the data that you need.

                    @Computer Science Student - Brazil
                    Web Developer and Researcher
                    “Sometimes it’s the people no one imagines anything of who do the things that no one can imagine.” - Alan Turing

                    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