Rel
Rel

Reputation: 43

How to interact with qml item values after passing them to cpp?

I'm trying to get values of items in qml to my cpp code, from there I want to add those values to CAN messages and send them via CAN bus.

So far, I can successfully get the values and states of many qml items to my cpp. Also, I can transmit CAN messages on to the CAN bus with static values. However, some of these values shouldn't be static but dynamically updated with values of items in qml.

Here is backend.h:

#ifndef BACKEND_H
#define BACKEND_H

#include <QObject>
#include <QCanBusDevice>

class BackEnd : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int elemVal READ getElemVal WRITE setElemVal NOTIFY elemValChanged)

public:
    explicit BackEnd(QObject *parent = nullptr);
    //elemVal
    int getElemVal();
    void setElemVal(const int &elemVal);
    int m_elemVal;

    //can
    void run();
    void oneShotConnectCan();
    QCanBusDevice *m_canDevice = nullptr;

signals:
    void elemValChanged();

public slots:
    void sendCanFrame();
};

#endif // BACKEND_H

Here is backend.cpp:

BackEnd::BackEnd(QObject *parent) : QObject(parent)
{

}

//elemVal get set
int BackEnd::getElemVal()
{
    return m_elemVal;
}

void BackEnd::setElemVal(const int &elemVal)
{
    if(elemVal == m_elemVal)
        return;

    m_elemVal = elemVal;
    emit elemValChanged();
    qDebug() << "elemVal is: " << m_elemVal;
}
//end of elemVal get set

... 
CAN Bus initialization
...

void BackEnd::sendCanFrame()
{
    quint32 frameid = 131;
    QByteArray payload;
    payload[0] = 0x04;
    payload[1] = 0x03;
    payload[2] = m_elemVal;

    QCanBusFrame testFrame(frameid, payload);
    testFrame.setFrameType(QCanBusFrame::DataFrame);

    m_canDevice->writeFrame(testFrame);
    if (m_canDevice->writeFrame(testFrame)) {
    qDebug() << "test frame: " << testFrame.toString();
    }
    else {
        qFatal("Write failed");
    }
}

Here is main.cpp:

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    qmlRegisterType<BackEnd>("io.qt.examples.backend", 1, 0, "BackEnd");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    BackEnd m_can;
    m_can.oneShotConnectCan(); //creating CAN device
    m_can.run(); //sending the CAN message

    return app.exec();
}

Here is main.qml file:

Window {
    id: window
    objectName: "window"
    visible: true
    visibility: Window.FullScreen

    onWindowStateChanged: {
        console.log( "onWindowStateChanged (Window), state: " + windowState );
    }

    BackEnd{
        id: backend
    }

    Dial {
        id: dial
        x: 181
        y: 38
        stepSize: 1
        to: 255
        value: backend.elemVal
        onValueChanged: backend.elemVal = value
    }
}

The actual output of qDebug is like this:

elemVal is:  0
test frame :  "     083   [3]  04 03 05"
test frame :  "     083   [3]  04 03 05"
elemVal is:  1
elemVal is:  2
elemVal is:  3
test frame :  "     083   [3]  04 03 05"
elemVal is:  4
elemVal is:  5
elemVal is:  6
test frame :  "     083   [3]  04 03 05"

How I expect it to be:

elemVal is:  0
test frame :  "     083   [3]  04 03 00"
test frame :  "     083   [3]  04 03 00"
elemVal is:  1
elemVal is:  2
elemVal is:  3
test frame :  "     083   [3]  04 03 03"
elemVal is:  4
elemVal is:  5
elemVal is:  6
test frame :  "     083   [3]  04 03 06"

The third byte of the message, which is 05 in actual output, should be changing dynamically with m_elemVal variable which is connected to dial in qml. Although I can read and write the value of m_elemVal, I cannot write it to my CAN message.

Sorry if the post is too long, tried to be as specific as possible.

Any help is appreciated...

Upvotes: 4

Views: 570

Answers (2)

eyllanesc
eyllanesc

Reputation: 244301

The Backend object that you created in QML

BackEnd {
    id: backend
}

and it is different from the one created in C++:

BackEnd m_can;

There are several possible solutions:

1. Export a Backend object through setContextProperty()

The advantage over the other proposed solution is that now the backend object can be accessed from any QML like console.log().

#include <QQmlContext>
// ...
int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    BackEnd m_can;
    m_can.oneShotConnectCan(); //creating CAN device
    m_can.run(); //sending the CAN message

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("backend", &m_can);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

*.qml

Window {
    id: window
    objectName: "window"
    visible: true
    visibility: Window.FullScreen

    onWindowStateChanged: {
        console.log( "onWindowStateChanged (Window), state: " + windowState );
    }

    Dial {
        id: dial
        x: 181
        y: 38
        stepSize: 1
        to: 255
        onValueChanged: backend.elemVal = value
    }
}

An advantage compared to the other answer does not use the old connection style that can generate problems, since its validation is done at run time and not at compile time. In addition, the code is dependent on the QML structure.

I also assume that you want to send a frame when an elemVal changes, so the ideal is to make a connection between the signal between elemValChanged and sendCanFrame():

BackEnd::BackEnd(QObject *parent) : QObject(parent)
{
    connect(this, &Backend::elemValChanged, this, &Backend::sendCanFrame);
}

2. Create a QML Type

Sometimes it is necessary to start certain resources after creating the object, in those cases you can use Component.onCompleted in QML or use QQmlParserStatus, in this case I will use the second method.

*.h

class BackEnd : public QObject, public QQmlParserStatus
{
    Q_OBJECT
    Q_INTERFACES(QQmlParserStatus)
    Q_PROPERTY(int elemVal READ getElemVal WRITE setElemVal NOTIFY elemValChanged)
public:
    explicit BackEnd(QObject *parent = nullptr);
    void classBegin();
    void componentComplete();
    //elemVal
    int getElemVal();
    void setElemVal(const int &elemVal);
signals:
    void elemValChanged();

public slots:
    void sendCanFrame();
private:
    //can
    void run();
    void oneShotConnectCan();
    QCanBusDevice *m_canDevice = nullptr;
    int m_elemVal;
};

*.cpp

BackEnd::BackEnd(QObject *parent) : QObject(parent)
{
    connect(this, &Backend::elemValChanged, this, &Backend::sendCanFrame);
}

void BackEnd::classBegin(){}
void BackEnd::componentComplete()
{
    oneShotConnectCan(); //creating CAN device
    run()
}

//elemVal get set
int BackEnd::getElemVal()
{
    return m_elemVal;
}

void BackEnd::setElemVal(const int &elemVal)
{
    if(elemVal == m_elemVal)
        return;

    m_elemVal = elemVal;
    emit elemValChanged();
    qDebug() << "elemVal is: " << m_elemVal;
}
//end of elemVal get set

// ... 
// CAN Bus initialization
// ...

void BackEnd::sendCanFrame()
{
    quint32 frameid = 131;
    QByteArray payload;
    payload[0] = 0x04;
    payload[1] = 0x03;
    payload[2] = m_elemVal;

    QCanBusFrame testFrame(frameid, payload);
    testFrame.setFrameType(QCanBusFrame::DataFrame);

    m_canDevice->writeFrame(testFrame);
    if (m_canDevice->writeFrame(testFrame)) {
    qDebug() << "test frame: " << testFrame.toString();
    }
    else {
        qFatal("Write failed");
    }
}

main.cpp

static void registerTypes()
{
    qmlRegisterType<BackEnd>("io.qt.examples.backend", 1, 0, "BackEnd");
}

Q_COREAPP_STARTUP_FUNCTION(registerTypes)

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

*.qml

Window {
    id: window
    objectName: "window"
    visible: true
    visibility: Window.FullScreen

    onWindowStateChanged: {
        console.log( "onWindowStateChanged (Window), state: " + windowState );
    }

    BackEnd{
        id: backend
        elemVal : dial.value
    }

    Dial {
        id: dial
        x: 181
        y: 38
        stepSize: 1
        to: 255
        Component.onCompleted: value = backend.elemVal
    }
}

Upvotes: 2

air-dex
air-dex

Reputation: 4178

  • Create an invokable method that you invoke once your QML Window is instanciated
  • Invoke the method you use to send messages when elemval changes:

backend.h:

#ifndef BACKEND_H
#define BACKEND_H

#include <QObject>
#include <QCanBusDevice>

class BackEnd : public QObject
{
    Q_OBJECT

    public:
        explicit BackEnd(QObject *parent = nullptr);

        // elemVal
        Q_PROPERTY(int elemVal READ getElemVal WRITE setElemVal NOTIFY elemValChanged)
        int getElemVal();
        void setElemVal(const int &elemVal);
        int m_elemVal;

        // The method for your first message
        Q_INVOKABLE void sendFirstMessage();

        //can
        void run();
        void oneShotConnectCan();
        QCanBusDevice *m_canDevice = nullptr;

    signals:
        void elemValChanged();

    public slots:
        void sendCanFrame();
};

backend.cpp:

// Your actual backend.cpp here.

void Backend::sendFirstMessage() {
    oneShotConnectCan(); //creating CAN device
    run(); //sending the CAN message
}

main.qml:

Window {
    id: window
    objectName: "window"
    visible: true
    visibility: Window.FullScreen

    onWindowStateChanged: {
        console.log( "onWindowStateChanged (Window), state: " + windowState );
    }

    BackEnd{
        id: backend
        elemVal: dial.value
        onElemValChanged: backend.sendCanFrame()    // You should code this on the C++ side.
    }

    Dial {
        id: dial
        x: 181
        y: 38
        stepSize: 1
        to: 255
    }

    // Wiil be executed when window instanciation has just ended.
    Component.onCompleted: backend.sendFirstMessage();
}

For further information about Component.onCompleted, please have a look at this: https://doc.qt.io/qt-5/qml-qtqml-component.html#completed-signal.

Avoid manipulating QML from the C++ side. Decouple backend and frontend as much as you can (that's why I have downvoted both eyllanesc and Romha Korev answers).

Upvotes: -1

Related Questions