Reputation: 43
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
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:
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);
}
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
Reputation: 4178
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