QML <- c++ sending variables



  • 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!



  • 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



  • 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



  • 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

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



  • @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??



  • @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 :)



  • I will download and study it this weekend. Thanks!



  • 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!



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


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.