PDA

View Full Version : modify a QML Text from C++



neda
13th February 2016, 11:20
Hi,
I see data in application output of Qt Creator, but I can not bind this data to text control.
Please guide me.
Thanks

main.cpp:

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

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

MySerialPort iSerialPort;

QThread * iTH = new QThread;
iSerialPort.moveToThread(iTH);
iSerialPort.openSerialPort();
iTH->start();

return app.exec();
}



myserialport.cpp:


void MySerialPort::readData()
{
QByteArray data = serial->readAll();

qDebug() << data;

QQmlEngine engine;
QQmlComponent component(&engine, QUrl("qrc:MyItem.qml"));

QObject *object = component.create();

object->setProperty("text1Text",data);

}


MyItem.qml:

import QtQuick 2.0
import QtQuick.Controls 1.2

Item {
id: item1
width: 400
height: 400
property alias text1Text: text1.text

Text {
id: text1
width: 400
height: 29
color: "red"
text: "This text should change..."
font.pixelSize: 12
}

}


main.qml:

import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick 2.3

ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")

MyItem{

}
}

anda_skoa
13th February 2016, 11:52
MySerialPort::readData() runs in a worker thread, it can't instantiate UI elements.

The cleanest way would be to have a model for the strings and use a Repeater in your main.qml to create MyItem instances for each model entry.

The model would need a slot that takes a string and appends it to its data.

You would then connect this slot to a signal in your serial port class which is emitted when it has received a new string.

Cheers,
_

neda
14th February 2016, 06:27
MySerialPort::readData() runs in a worker thread, it can't instantiate UI elements.

The cleanest way would be to have a model for the strings and use a Repeater in your main.qml to create MyItem instances for each model entry.

The model would need a slot that takes a string and appends it to its data.

You would then connect this slot to a signal in your serial port class which is emitted when it has received a new string.

Cheers,
_

Thanks for your reply.
Please guide me with simple code.

I change my code but I don't see any change to text control.
main.cpp:

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

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

MySerialPort iSerialPort;
QThread* thread = new QThread(app);
iSerialPort.moveToThread(thread);
thread->start();
iSerialPort.myText= engine.rootObjects().at(0)->findChild<QObject*>("text1Text");
iSerialPort.openSerialPort();

return app.exec();
}


serialport.cpp:

void MySerialPort::readData()
{
QByteArray data = serial->readAll();

qDebug() << data;


myText->setProperty("text", data);
}

myserialport.h

#ifndef MYSERIALPORT_H
#define MYSERIALPORT_H
#include <QtSerialPort/QtSerialPort>
#include <QObject>
#include <QApplication>
#include <QQmlApplicationEngine>

class MySerialPort: public QSerialPort
{
Q_OBJECT
public:
MySerialPort();
QObject *myText;

public slots:
void openSerialPort();
void closeSerialPort();

void writeData(const QByteArray &data);
void readData();

void handleError(QSerialPort::SerialPortError error);

private:
void showStatusMessage(const QString &message);

QSerialPort *serial;

};

#endif // MYSERIALPORT_H


main.qml:


ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")


Text {
id: text1Text
objectName: text1Text
width: 400
height: 29
color: "red"
text: "This text should change..."
font.pixelSize: 12
}

}

anda_skoa
14th February 2016, 12:03
That still tries to have the thread interact with the GUI, i.e. you still need the mediator object.

But if you only want to show a single text that is updated with the newest data, then this can be done with a simple property.

So

1) Add a signal to MySerialPort that emits the data in readData()

2) Create a QObject derived class that has a matching slot which updates a QString property, something like this


class SerialData : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text NOTIFY textChanged)

public slots:
void setText(const QString &text)
{
if (text == m_text) return;
m_text = text;
emit textChanged();
}

signals:
void textChanged();

private:
QString m_text;
};


3) Set an instance of that as a context property on the QQmlEngine's rootContext

4) Connect the serial port signal to the slot

5) Use the "text" property in QML as the value of the Text element

Cheers,
_

neda
15th February 2016, 07:19
My problem is solved.
Thanks

main.cpp:

#include <QApplication>
#include <QQmlApplicationEngine>
#include <myserialport.h>
#include <QQmlContext>

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

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

MyDisplay myDisplay;
myDisplay.setText("neda");
engine.rootContext()->setContextProperty("myDisplay", &myDisplay);

MySerialPort iSerialPort;
iSerialPort.setDisplay(&myDisplay);
iSerialPort.openSerialPort();

return app.exec();
}



mydisplay.h:

#include <QObject>

#ifndef MYDISPLAY_H
#define MYDISPLAY_H

class MyDisplay : public QObject
{
Q_OBJECT
Q_PROPERTY(QString newText READ getText WRITE setText NOTIFY textChanged)

public:
MyDisplay();

MyDisplay(QString);

Q_INVOKABLE QString getText() const;
public slots:
void setText(QString text);

signals:
void textChanged(QString);

private:
QString newText;
};

#endif // MYDISPLAY_H


mydisplay.cpp:

#include "mydisplay.h"
#include "qstring.h"
MyDisplay::MyDisplay()
{
newText = "";
}

MyDisplay::MyDisplay(QString text)
{
newText = text;
}

QString MyDisplay::getText() const
{
return newText;
}

void MyDisplay::setText(QString text)
{
if (text != newText)
{
newText = text;
emit textChanged(text);
}
}


serialport.cpp:

void MySerialPort::readData()
{

QByteArray data = serial->readAll();


qDebug() << data;

QString dataString=data;

myDisplay->setText(dataString);
}

void MySerialPort::setDisplay(MyDisplay * m_display)
{
myDisplay = m_display;
}


main.qml:

ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")

Text {
id: text1Text
//objectName: text1Text
width: 400
height: 29
color: "red"
text: myDisplay.newText
font.pixelSize: 12
}
}

myserialport.h

class MySerialPort: public QSerialPort
{
Q_OBJECT
public:
MySerialPort();

public slots:
void openSerialPort();
void closeSerialPort();
void setDisplay(MyDisplay * m_display);
void writeData(const QByteArray &data);
void readData();

void handleError(QSerialPort::SerialPortError error);

private:
void showStatusMessage(const QString &message);
MyDisplay * myDisplay;
QSerialPort *serial;

};

anda_skoa
15th February 2016, 09:32
My problem is solved.
Thanks

I am surprised that this works.

In any case you should not call MyDisplay::setText() from the secondary thread, at least not without proper locking.
See my original suggestion of using a signal/slot connection.

Cheers,
_

neda
16th February 2016, 05:44
> I am surprised that this works.

Why? Which part of the code is illogical?

> In any case you should not call MyDisplay::setText()
Why?

> from the secondary thread
secondary thread? I did not use thread.

> at least not without proper locking.

Please guide me, I do not know about proper locking.

> See my original suggestion of using a signal/slot connection.

> 1) Add a signal to MySerialPort that emits the data in readData()


connect(serial, SIGNAL(error(QSerialPort::SerialPortError)), this,
SLOT(handleError(QSerialPort::SerialPortError)));


MySerialPort::MySerialPort()
{
serial = new QSerialPort(this);

connect(serial, SIGNAL(readyRead()), this, SLOT(readData()));
connect(serial, SIGNAL(error(QSerialPort::SerialPortError)), this,
SLOT(handleError(QSerialPort::SerialPortError)));

//connect(socket, SIGNAL(setText(QString)), this, SLOT(readData()));

}

> 2) Create a QObject derived class that has a matching slot which updates a QString property

mydisplay.h:

#include <QObject>

#ifndef MYDISPLAY_H
#define MYDISPLAY_H

class MyDisplay : public QObject
{
Q_OBJECT
Q_PROPERTY(QString newText READ getText WRITE setText NOTIFY textChanged)

public:
MyDisplay();

MyDisplay(QString);

Q_INVOKABLE QString getText() const;

public slots:
void setText(QString text);

signals:
void textChanged(QString);

private:
QString newText;
};

#endif // MYDISPLAY_H


> Set an instance of that as a context property on the QQmlEngine's rootContext


engine.rootContext()->setContextProperty("myDisplay", &myDisplay);

> 5) Use the "text" property in QML as the value of the Text element


Text {
id: text1Text
width: 400
height: 29
color: "red"
text: myDisplay.newText
font.pixelSize: 12
}

I am new in Qt, please guide me to write better code.
Thank you for your kindness.

anda_skoa
16th February 2016, 09:04
> I am surprised that this works.
Why? Which part of the code is illogical?

You load the QML file before setting the context property but the QML code accesses the context property.
I would have expect that to cause the QML file to fail loading.

Usually one would set the context property before loading.



> In any case you should not call MyDisplay::setText()
Why?

It is OK now, I didn't see you had removed the usage of QThread.



> from the secondary thread
secondary thread? I did not use thread.

Right, you had thread usage earlier, didn't see that this changed.

That also makes the signal/slot approach unnecessary.

Since you are not using threading anymore you could even add the property to the MySerialPort class and set its instance as the context property.

Cheers,
_