How to expose property of QObject pointer to QML - qt

I am giving very brief (and partial) description of my class to show my problem. Basically I have setup two properties.
class Fruit : public QObject
{
Q_OBJECT
....
public:
Q_PROPERTY( int price READ getPrice NOTIFY priceChanged)
Q_PROPERTY(Fruit * fruit READ fruit WRITE setFruit NOTIFY fruitChanged)
}
In my QML if I access the price property it works well and good. But if I access the fruit property which obviously returns Fruit and then try to use its price property, that doesn't work. Is this not supposed to work this way?
Text {
id: myText
anchors.centerIn: parent
text: basket.price // shows correctly
//text: basket.fruit.price // doesn't show
}
The 2nd one returns Fruit which also is a property and it has price property but it doesn't seem to access that property? Should this work?
Updated
I am including my source code. I created a new demo with HardwareComponent, it just makes more sense this way. I tried to make it work based on answer I received but no luck.
HardwareComponent class is the base class for Computer and CPU.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
class HardwareComponent : public QObject
{
Q_OBJECT
public:
HardwareComponent() : m_price(0) {}
virtual int price() = 0;
virtual Q_INVOKABLE void add(HardwareComponent * item) { m_CPU = item; }
HardwareComponent * getCPU() const { return m_CPU; }
Q_SLOT virtual void setPrice(int arg)
{
if (m_price == arg) return;
m_price = arg;
emit priceChanged(arg);
}
Q_SIGNAL void priceChanged(int arg);
protected:
Q_PROPERTY(HardwareComponent * cpu READ getCPU);
Q_PROPERTY(int price READ price WRITE setPrice NOTIFY priceChanged)
HardwareComponent * m_CPU;
int m_price;
};
class Computer : public HardwareComponent
{
Q_OBJECT
public:
Computer() { m_price = 500; }
int price() { return m_price; }
void setprice(int arg) { m_price = arg; }
};
class CPU : public HardwareComponent
{
Q_OBJECT
public:
CPU() { m_price = 100; }
int price() { return m_price; }
void setprice(int arg) { m_price = arg; }
};
int main(int argc, char *argv[])
{
HardwareComponent * computer = new Computer;
CPU * cpu = new CPU;
computer->add( cpu );
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
engine.rootContext()->setContextProperty("computer", computer);
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
width: 360
height: 360
Text {
anchors.centerIn: parent
text: computer.price // works
//text: computer.cpu.price // doesn't work, doesn't show any value
}
}
This is the complete source code for all my project files.
When I run it I get this output:
Starting C:\Users\User\Documents\My Qt
Projects\build-hardware-Desktop_Qt_5_4_0_MSVC2010_OpenGL_32bit-Debug\debug\hardware.exe...
QML debugging is enabled. Only use this in a safe environment.
qrc:/MainForm.ui.qml:20: ReferenceError: computer is not defined
Even though it gives a warning at line 20 (computer.price) line, it still works and shows the price of computer (=500). If change it computer.cpu.price, the same warning is reported but the price no longer shows - it doesn't seem to work.
The problem is since price is a virtual property, it works! but if I use this property on another hardware component inside the computer component, it doesn't work! The code/answer posted by Mido gives me hope there could be a solution to this, it looks quite close! I hope I can make this work.

There were just a couple things wrong, related specifically to QML:
The context properties must be set before the engine loads the qml file.
You have not registered the HardwareComponent type. See Defining QML Types from C++ for more information.
Apart from the QML side of things, it appears that you wish the hardware to be a composite with a tree structure. Since a QObject is already a composite, you can leverage this to your advantage. The tree of hardware items can be a tree of objects. The QObject notifies the parents when the children are added or removed: it's thus easy to keep the price current as the children are added and removed.
Each component has a unitPrice: this is the price of the component in isolation. The price is a total of the component's unit price, and the prices of its children.
Instead of traversing the entire tree to obtain the total price, you could cache it and only update it when the unit price changes, or the child prices change. Similarly, the current CPU could be cached, you could enforce a single CPU at any given time, etc. The example is functional, but only a minimal sketch.
I've also shown how to change the CPU types in the Computer. There are notifications for both the price changes and cpu changes. The QML UI reacts appropriately when the cpu property gets changed, as well as when any price property changes.
main.cpp
#include <QGuiApplication>
#include <QtQml>
class HardwareComponent : public QObject {
Q_OBJECT
Q_PROPERTY(QString category MEMBER m_category READ category CONSTANT)
Q_PROPERTY(HardwareComponent * cpu READ cpu WRITE setCpu NOTIFY cpuChanged)
Q_PROPERTY(int price READ price NOTIFY priceChanged)
Q_PROPERTY(int unitPrice MEMBER m_unitPrice READ unitPrice WRITE setUnitPrice NOTIFY unitPriceChanged)
QString m_category;
int m_unitPrice;
bool event(QEvent * ev) Q_DECL_OVERRIDE {
if (ev->type() != QEvent::ChildAdded && ev->type() != QEvent::ChildRemoved)
return QObject::event(ev);
auto childEvent = static_cast<QChildEvent*>(ev);
auto child = qobject_cast<HardwareComponent*>(childEvent->child());
if (! child) return QObject::event(ev);
if (childEvent->added())
connect(child, &HardwareComponent::priceChanged,
this, &HardwareComponent::priceChanged, Qt::UniqueConnection);
else
disconnect(child, &HardwareComponent::priceChanged,
this, &HardwareComponent::priceChanged);
emit priceChanged(price());
if (child->category() == "CPU") emit cpuChanged(cpu());
return QObject::event(ev);
}
public:
HardwareComponent(int price, QString category = QString(), QObject * parent = 0) :
QObject(parent), m_category(category), m_unitPrice(price) {}
HardwareComponent * cpu() const {
for (auto child : findChildren<HardwareComponent*>())
if (child->category() == "CPU") return child;
return 0;
}
Q_INVOKABLE void setCpu(HardwareComponent * newCpu) {
Q_ASSERT(!newCpu || newCpu->category() == "CPU");
auto oldCpu = cpu();
if (oldCpu == newCpu) return;
if (oldCpu) oldCpu->setParent(0);
if (newCpu) newCpu->setParent(this);
emit cpuChanged(newCpu);
}
Q_SIGNAL void cpuChanged(HardwareComponent *);
virtual int price() const {
int total = unitPrice();
for (auto child : findChildren<HardwareComponent*>(QString(), Qt::FindDirectChildrenOnly))
total += child->price();
return total;
}
Q_SIGNAL void priceChanged(int);
int unitPrice() const { return m_unitPrice; }
void setUnitPrice(int unitPrice) {
if (m_unitPrice == unitPrice) return;
m_unitPrice = unitPrice;
emit unitPriceChanged(m_unitPrice);
emit priceChanged(this->price());
}
Q_SIGNAL void unitPriceChanged(int);
QString category() const { return m_category; }
};
struct Computer : public HardwareComponent {
Computer() : HardwareComponent(400) {}
};
class FluctuatingPriceComponent : public HardwareComponent {
QTimer m_timer;
int m_basePrice;
public:
FluctuatingPriceComponent(int basePrice, const QString & category = QString(), QObject * parent = 0) :
HardwareComponent(basePrice, category, parent),
m_basePrice(basePrice) {
m_timer.start(250);
connect(&m_timer, &QTimer::timeout, [this]{
setUnitPrice(m_basePrice + qrand()*20.0/RAND_MAX - 10);
});
}
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Computer computer;
HardwareComponent memoryBay(40, "Memory Bay", &computer);
HardwareComponent memoryStick(60, "Memory Stick", &memoryBay);
FluctuatingPriceComponent cpu1(100, "CPU", &computer);
HardwareComponent cpu2(200, "CPU");
qmlRegisterUncreatableType<HardwareComponent>("bar.foo", 1, 0, "HardwareComponent", "");
engine.rootContext()->setContextProperty("computer", &computer);
engine.rootContext()->setContextProperty("cpu1", &cpu1);
engine.rootContext()->setContextProperty("cpu2", &cpu2);
engine.load(QUrl("qrc:/main.qml"));
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Window 2.0
Window {
visible: true
Column {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
Text {
text: "Computer price: " + computer.price
}
Text {
text: "CPU price: " + (computer.cpu ? computer.cpu.price : "N/A")
}
Button {
text: "Use CPU 1";
onClicked: { computer.setCpu(cpu1) }
}
Button {
text: "Use CPU 2";
onClicked: { computer.setCpu(cpu2) }
}
Button {
text: "Use no CPU";
onClicked: { computer.setCpu(undefined) }
}
}
}

I created a Fruit class like in your example and it's working fine. I created a new instance of Fruit and I can get the price value. Here's my code:
fruit.h
#ifndef FRUIT_H
#define FRUIT_H
#include <QObject>
#include <QQuickView>
class Fruit : public QObject
{
Q_OBJECT
Q_PROPERTY( int price READ getPrice WRITE setPrice NOTIFY priceChanged)
Q_PROPERTY(Fruit * fruit READ fruit WRITE setFruit NOTIFY fruitChanged)
public:
Fruit();
~Fruit();
Fruit *fruit();
void setFruit(Fruit * value);
int getPrice();
void setPrice(int value);
signals:
void fruitChanged();
void priceChanged(int price);
private:
int _price;
Fruit *_fruit;
};
#endif // FRUIT_H
fruit.cpp
#include "fruit.h"
Fruit::Fruit() :
_price(1),_fruit(this)
{
}
Fruit::~Fruit()
{
if(_fruit)
{
delete _fruit;
_fruit = NULL;
}
}
Fruit *Fruit::fruit()
{
//Uncomment this line to test the set
//_fruit = new Fruit;
return _fruit;
}
void Fruit::setFruit(Fruit *value)
{
_fruit = value;
emit fruitChanged();
}
int Fruit::getPrice()
{
return _price;
}
void Fruit::setPrice(int value)
{
_price = value;
emit priceChanged(_price);
}
main.cpp
#include <QGuiApplication>
#include <QQuickView>
#include <QQmlContext>
#include "fruit.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
view.rootContext()->setContextProperty("basket", new Fruit);
view.setSource(QStringLiteral("qml/main.qml"));
view.show();
return app.exec();
}
main.qml
import QtQuick 2.2
Rectangle {
width:800
height: 480
property var obj
color: "yellow"
Text{
text: basket.fruit.price
font.pixelSize: 20
}
Component.onCompleted: {
basket.price = 20
console.log(basket.fruit.price)
}
}

Related

How can a QThread send a signal from its own thread with an enum as an argument for consumption in QML?

In the following code, if the signal errorHappened is emitted from the main thread it works without problem. However if it is emitted from the QThread thread it fails with the following error:
QObject::connect: Cannot queue arguments of type 'ErrorCode'
(Make sure 'ErrorCode' is registered using qRegisterMetaType().)
Is there a way that the signal can be successfully emitted from the QThread thread? If so, how?
Full code in this Gist
MyClass.h
#import <QThread>
#import <atomic>
class MyClass : public QThread
{
Q_OBJECT
public:
explicit MyClass(QObject *parent = Q_NULLPTR);
virtual ~MyClass() override;
enum ErrorCode {
ErrorA,
ErrorB,
ErrorC
};
Q_ENUM(ErrorCode)
signals:
void errorHappened(ErrorCode errorCode);
public slots:
void mainThreadError();
void otherThreadError();
private:
std::atomic<bool> m_running;
std::atomic<bool> m_signalStop;
std::atomic<bool> m_signalError;
void run() override;
void stop();
};
MyClass.cpp
#include "MyClass.h"
MyClass::MyClass(QObject *parent)
: QThread(parent)
{
start();
}
MyClass::~MyClass()
{
stop();
}
void MyClass::mainThreadError()
{
emit errorHappened(ErrorCode::ErrorA);
}
void MyClass::otherThreadError()
{
m_signalError = true;
}
void MyClass::run()
{
m_running = true;
while (!m_signalStop) {
if (m_signalError) {
emit errorHappened(ErrorCode::ErrorA);
m_signalError = false;
}
msleep(1);
}
m_running = false;
m_signalStop = false;
}
void MyClass::stop()
{
if (m_running) {
m_signalStop = true;
wait();
}
}
main.cpp
#include <QGuiApplication>
#include <QQuickView>
#include "MyClass.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView *view = new QQuickView();
qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClass");
view->setSource((QUrl(QStringLiteral("qrc:/main.qml"))));
view->create();
view->show();
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Controls 2.5
import MyClass 1.0
Rectangle {
id: root
width: 800
height: 600
focus: true
MyClass {
id: tester
onErrorHappened: {
var s
switch (errorCode) {
case MyClass.ErrorA:
s = "Error A happened"
break
}
console.log(s)
}
}
Row {
spacing: 30
Button {
id: mainThreadButton
enabled: !tester.testRunning
text: "Test on main thread"
onClicked: tester.mainThreadError()
}
Button {
id: otherThreadButton
enabled: !tester.testRunning
text: "Test on other thread"
onClicked: tester.otherThreadError()
}
}
}
qmlRegisterType makes the QObject (MyClass) class accessible in QML (Q_PROPERTY, Q_ENUM, Q_SIGNAL, Q_SLOT, Q_INVOKABLE, etc.) but does not allow the transmission of data between threads, for this case it must be registered using qRegisterMetaType<MyClass::ErrorCode>("ErrorCode"):
main.cpp
#include <QGuiApplication>
#include <QQuickView>
#include "MyClass.h"
static void registerTypes(){
qRegisterMetaType<MyClass::ErrorCode>("ErrorCode");
qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClass");
}
Q_COREAPP_STARTUP_FUNCTION(registerTypes)
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
view.setSource((QUrl(QStringLiteral("qrc:/main.qml"))));
view.show();
return app.exec();
}
your type "ErrorCode" defined only in thread (not in main)
For user defined types you can use (read this https://doc.qt.io/qt-5/qmetatype.html)
Q_DECLARE_METATYPE(ErrorCode);
Use standard types for arguments (int,QMap,QString....)

Receiving c++ signals with arguments in QML QList<QPair>

I'm trying to figure out how to get QList> from C++ signal in QML, i'm only getting either QVariant(RecordList, ) or QVariant(QList, ). Tried with different supported sequence type and they work perfectly (QList. I'll appreciate if somebody can help me to understand my error. Kind regards.
jsonreaderasync.h
typedef QPair<qreal,qreal>Record;
typedef QList<Record>RecordList;
class JsonReaderAsync : public QObject
{
Q_OBJECT
public:
explicit JsonReaderAsync(QObject *parent = nullptr);
Q_INVOKABLE void start(const QString& fileName);
signals:
void started();
//void finished(QList<qreal> record);
void finished(RecordList record);
//void finished(QList<Record> record);
};
jsonreaderasync.cpp
#include <QDebug>
#include "jsonreaderasync.h"
Q_DECLARE_METATYPE(Record)
Q_DECLARE_METATYPE(RecordList)
//Q_DECLARE_METATYPE(QList<Record>)
JsonReaderAsync::JsonReaderAsync(QObject *parent) : QObject(parent)
{
qRegisterMetaType<Record>("Record");
qRegisterMetaType<RecordList>("RecordList"); // qml prints QVariant(RecordList, )
//qRegisterMetaType<QList<Record>>("QList<Record>"); //qml prints QVariant(QList<Record>, )
}
void JsonReaderAsync::start(const QString &fileName)
{
QList<Record> record;
record.append(qMakePair(1,1));
record.append(qMakePair(1,2));
record.append(qMakePair(1,3));
// QList<qreal> foo;
// foo.append(1);
// foo.append(1);
// foo.append(1);
emit finished(record);
}
main.cpp
#include "jsonreaderasync.h"
typedef QPair<qreal,qreal>Record;
typedef QList<QPair<qreal,qreal>>RecordList;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
JsonReaderAsync* dataReaderAsync = new JsonReaderAsync();
engine->rootContext()->setContextProperty("JsonReaderAsync", dataReaderAsync);
engine->load(QUrl(QStringLiteral("main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.13
import QtQuick.Window 2.13
import QtQuick.Controls 2.12
import QtCharts 2.3
Window {
id: appWindow
visible: true
minimumWidth : 400
minimumHeight: 400
Connections
{
target: JsonReaderAsync
onStarted:{
console.log("onStarted")
}
onFinished:{
console.log("onFinished")
console.log(record)
console.log(record[0])
}
}
Button {
width : 40
height: 40
anchors.centerIn: parent
onClicked: {
JsonReaderAsync.start("")
}
}
}
In QML, the meta-type system is the only way that QML engine can access C++ structures from a QML environment.
That is, only Predefined C++ Types and custom objects that have Q_PROPERTY declarations could access from QML environment.
Here's my recommend (simplest) modification:
jsonreaderasync.h
#include <QObject>
#include <QVariant>
class Record : public QPair<qreal, qreal> {
Q_GADGET
Q_PROPERTY(qreal first MEMBER first CONSTANT FINAL)
Q_PROPERTY(qreal second MEMBER second CONSTANT FINAL)
public:
Record() = default;
Record(qreal a, qreal b) : QPair<qreal, qreal>(a, b) {}
};
class JsonReaderAsync : public QObject
{
Q_OBJECT
public:
explicit JsonReaderAsync(QObject *parent = nullptr);
Q_INVOKABLE void start(const QString& fileName);
signals:
void started();
void finished(QVariantList record); // RecordList
};
jsonreaderasync.cpp
#include <QDebug>
#include "jsonreaderasync.h"
JsonReaderAsync::JsonReaderAsync(QObject *parent) : QObject(parent)
{
qRegisterMetaType<Record>("Record");
}
void JsonReaderAsync::start(const QString &fileName)
{
QVariantList record;
record.append(QVariant::fromValue(Record(1,1)));
record.append(QVariant::fromValue(Record(1,2)));
record.append(QVariant::fromValue(Record(1,3)));
emit finished(record);
}
Now you can access records from QML:
onFinished: {
for (var i = 0; i < record.length; ++i)
console.log(record[i].first + "->" + record[i].second);
}
Note that converting between C++ objects and QML objects does incur a bit overhead, if these codes are performance sensitive, please consider using C++ Data Models.
While #GPBeta solution works, I wanted more flexibility for Qml supported pairs. I tried to work with templates, but Q_GADGET doesn't support it. There might be a smart wrapper (Template + QVariant) solution, I guess... Nonetheless, here is my approach to the problem:
class PairQml {
Q_GADGET
Q_PROPERTY(QVariant first MEMBER first CONSTANT FINAL)
Q_PROPERTY(QVariant second MEMBER second CONSTANT FINAL)
public:
PairQml() = default;
PairQml(QVariant f, QVariant s): first(f), second(s) {}
QVariant first;
QVariant second;
};
Don't forget to register: qRegisterMetaType<PairQml>("PairQml");

QML Slot not displaying double value from serial input

I'm struggling to get my serial input "analogread2" converted to a double, to display in QML.
Ive added the Q_property, the root context in the main.cpp, and I can add the property in the qml, but I cant get it to display in text format.
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
SerialPort serialport;
//engine.rootContext()->setContextProperty("serialport", &serialport);
qmlRegisterType<SerialPort>("SerialPortlib", 1, 0, "SerialPort");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")))
header.
Q_PROPERTY(double newValue MEMBER m_oil_pressure_volt WRITE set_oil_pressure_volt NOTIFY oil_pressure_volt_Changed )
qml
Window {
visible: true
width: 640
height: 480
id: gauge
SerialPort{
// what would I put in here to display the text value (double)
}
}
any help is much appreciated.
To do the test I am assuming that you are sending the data from the arduino in one of the following ways:
Serial.println(data);
Serial.print(data);
Assuming the above has been implemented the serialport class, this has a function called openDefault that scans the devices that are connected through serial and searches for the word "Arduino" within description or manufacturer and if one finds it connects to it.
The complete code can be found at: https://github.com/eyllanesc/stackoverflow/tree/master/Arduino-QML
serialport.h
#ifndef SERIALPORT_H
#define SERIALPORT_H
#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
class SerialPort : public QObject
{
Q_OBJECT
Q_PROPERTY(double oil_pressure_volt READ get_oil_pressure_volt WRITE set_oil_pressure_volt NOTIFY oil_pressure_volt_Changed )
public:
SerialPort(QObject *parent = 0);
~SerialPort();
double get_oil_pressure_volt() const;
void set_oil_pressure_volt(double newValue);
public slots:
void onReadData();
signals:
void oil_pressure_volt_Changed(double newValue);
private:
QSerialPort *arduino;
double mOil_pressure_volt;
QSerialPortInfo portInfo;
void openDefault();
};
serialport.cpp
#include "serialport.h"
#include <QDebug>
SerialPort::SerialPort(QObject *parent):QObject(parent)
{
arduino = new QSerialPort(this);
connect(arduino, &QSerialPort::readyRead, this, &SerialPort::onReadData);
openDefault();
}
SerialPort::~SerialPort()
{
delete arduino;
}
void SerialPort::set_oil_pressure_volt(double newValue)
{
if (mOil_pressure_volt == newValue)
return;
mOil_pressure_volt = newValue;
emit oil_pressure_volt_Changed(mOil_pressure_volt);
}
void SerialPort::onReadData()
{
if(arduino->bytesAvailable()>0){
QByteArray data = arduino->readAll();
QString value = QString(data).trimmed();
qDebug()<< value;
bool ok;
double val = value.toDouble(&ok);
if(ok)
set_oil_pressure_volt(val);
}
}
void SerialPort::openDefault()
{
for(auto info: QSerialPortInfo::availablePorts()){
qDebug()<<info.portName()<<info.description()<<info.manufacturer();
if(!info.isBusy() && (info.description().contains("Arduino") || info.manufacturer().contains("Arduino"))){
portInfo = info;
break;
}
}
if(portInfo.isNull()){
return;
}
arduino->setPortName(portInfo.portName());
arduino->setBaudRate(QSerialPort::Baud115200);
arduino->setDataBits(QSerialPort::Data8);
arduino->setParity(QSerialPort::NoParity);
arduino->setStopBits(QSerialPort::OneStop);
arduino->setFlowControl(QSerialPort::NoFlowControl);
if(arduino->open(QSerialPort::ReadWrite))
qDebug()<<"Connected to "<< portInfo.manufacturer()<< " on " << portInfo.portName();
else
qCritical()<<"Serial Port error: " << arduino->errorString();
}
double SerialPort::get_oil_pressure_volt() const
{
return mOil_pressure_volt;
}
Example:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "serialport.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<SerialPort>("SerialPortLib", 1, 0, "SerialPort");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import SerialPortLib 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Test")
SerialPort{
onOil_pressure_voltChanged: {
tx.text = "%1".arg(oil_pressure_volt);
}
}
Text {
id: tx
anchors.fill: parent
font.family: "Helvetica"
font.pointSize: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}

How to display QML view( with scroll) to load and display multiline Text around 10 million lines from file [duplicate]

I have a Qt application and I'd like to show some log. I use a TextArea. However, if the log is large or the events come too fast the GUI can't draw Textarea fast enough.
I have analyzed this problem with Qt Creator (QML Profiler) and if the log is large it takes 300 ms to draw the GUI. I use this software on a Raspberry Pi2.
Any ideas how to solve this? Should I use other QML controls? Thanks.
QML code:
TextArea {
text: appHandler.rawCommunication
readOnly: true
}
C++ code:
Q_PROPERTY(QString rawCommunication READ rawCommunication WRITE setrawCommunication NOTIFY rawCommunicationChanged)
void setrawCommunication(QString val)
{
val.append("\n");
val.append(m_rawCommunication);
m_rawCommunication = val;
emit rawCommunicationChanged(m_rawCommunication);
}
Use a view, like ListView. They instantiate their delegates as needed, based on which data the view says it needs to show depending on the position the user is at in the list. This means that they perform much better for visualising large amounts of data than items like TextArea, which in your case is going to keep a massive, ever-growing string in memory.
Your delegate could then be a TextArea, so you'd have one editable block of text per log line. However, if you don't need styling, I'd recommend going with something a bit lighter, like TextEdit. Taking it one step further: if you don't need editable text, use plain old Text. Switching to these might not make much of a difference, but if you're still seeing slowness (and have lots of delegates visible at a time), it's worth a try.
I tried the ListView suggestion but it has several drawbacks:
No easy way to keep the view positioned at the bottom when new output is added
No selection across lines/delegates
So I ended up using a cached TextArea, updating once every second:
TextArea {
id: outputArea_text
wrapMode: TextArea.Wrap
readOnly: true
font.family: "Ubuntu Mono, times"
function appendText(text){
logCache += text + "\n";
update_timer.start();
}
property string logCache: ""
Timer {
id: update_timer
// Update every second
interval: 1000
running: false
repeat: false
onTriggered: {
outputArea_text.append(outputArea_text.logCache);
outputArea_text.logCache = "";
}
}
Component.onCompleted: {
my_signal.connect(outputArea_text.appendText)
}
}
try this approach:
create a c++ logger class
append all logs to this class
and print them using some action, example a button click
this will solve your performance issue
Example of code:
Logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QQmlContext>
#include <QObject>
#include <QStringList>
#include <QQmlEngine>
#include <QString>
#include <QtCore>
#include <QDebug>
class Logger : public QObject
{
Q_OBJECT
public:
explicit Logger(QObject *parent = 0);
~Logger();
Q_INVOKABLE QStringList *getLogStream();
Q_INVOKABLE void printLogStream();
Q_INVOKABLE void appendLog(QString log);
Q_INVOKABLE void log(QString log="");
Q_INVOKABLE void log(QString fileName, QString log);
signals:
public slots:
private:
QStringList* stringStream_;
};
#endif // LOGGER_H
Logger.cpp
#include "logger.h"
Logger::Logger(QObject *parent) :
QObject(parent),
stringStream_(new QStringList)
{
}
~Logger(){
if(stringStream_ != NULL)
{
delete stringStream_;
stringStream_ = NULL;
}
}
QStringList* Logger::getLogStream(){
return stringStream_;
}
void Logger::printLogStream()
{
QStringListIterator itr(*stringStream_);
while (itr.hasNext())
qDebug()<< itr.next()<<"\n";
}
void Logger::appendLog(QString log){
stringStream_->push_back(log) ;
}
void Logger::log(QString fileName,QString log)
{
#ifdef ENABLElogs
fileName.push_front(" [");
if(!fileName.contains(".qml"))
{
fileName.append(".qml]:");
}
qDebug()<<fileName<<log;
#else
Q_UNUSED(log);
Q_UNUSED(fileName);
#endif
}
void Logger::log(QString log)
{
#ifdef ENABLElogs
qDebug()<<log;
#else
Q_UNUSED(log);
#endif
}
main.cpp
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include "logger.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QtQuick2ApplicationViewer *viewer = new QtQuick2ApplicationViewer;
Logger* stream = new Logger;
viewer->rootContext()->setContextProperty("Stream",stream);
viewer->setMainQmlFile(QStringLiteral("qml/project/main.qml"));
viewer->showExpanded();
return app.exec();
}
main.qml
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 800
height: 480
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
Component.onCompleted: Stream.appendLog("Text object is completed")
}
Column{
x:300
Button{
text:"append"
onClicked: {
Stream.appendLog("MouseArea object clicked")
}
Component.onCompleted: Stream.appendLog("Button object is completed")
}
Button{
text:"logger"
onClicked: {
Stream.printLogStream()
}
Component.onCompleted: Stream.appendLog("Button logger object is completed")
}
}
TextArea{
text:"blablabla"
Component.onCompleted: Stream.appendLog("TextArea object is completed")
}
Component.onCompleted: Stream.appendLog("the main object is completed")
}
project.pro
#add this line
# comment it, run qmake and recompile to disable logs
DEFINES += ENABLElogs
using this approch you can stop all logs with a single line change when you want to release your soft
However, I have included complete code, using "QAbstractListModel" for a Logging heavy data to QML
listmodel.h
#ifndef LISTMODEL_H
#define LISTMODEL_H
#include <QAbstractListModel>
class ListModel: public QAbstractListModel
{
Q_OBJECT
public:
ListModel();
// Q_PROPERTY(QStringList logs READ name WRITE nameChanged)
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
Q_INVOKABLE QVariant activate(int i);
private:
QStringList m_list;
};
#endif // LISTMODEL_H
listmodel.cpp
#include "listmodel.h"
#include <QFile>
#include <QHash>
ListModel::ListModel()
{
QFile file("/home/ashif/LogFile");
if(!file.open(QIODevice::ReadOnly))
{
qDebug( "Log file open failed" );
}
bool isContinue = true;
do
{
if(file.atEnd())
{
isContinue = false;
}
m_list.append(file.readLine());
}
while( isContinue);
}
int ListModel::rowCount(const QModelIndex & parent ) const
{
return m_list.count();
}
QVariant ListModel::data(const QModelIndex & index, int role ) const
{
if(!index.isValid()) {
return QVariant("temp");
}
return m_list.value(index.row());
}
QVariant ListModel::activate(int i)
{
return m_list[i];
}
main.qml
import QtQuick 2.3
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
visible: true
ListView
{
width: 200; height: 250
anchors.centerIn: parent
model:mylistModel
delegate: Text
{
text:mylistModel.activate(index)
}
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include "logger.h"
#include "listmodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Logger myLogger;
ListModel listModel;
engine.rootContext()->setContextProperty("mylistModel", &listModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}

Can a QDeclarativeListProperty be accessed directly, instead of as a model?

I'm trying to use a QDeclarativeListProperty in order to manage a list of parameters, mostly for the purposes of displaying them in a ListView. However, I would also like to be able to directly access the parameters in QML from the QDeclarativeListProperty so that I can display/modify individual parameters on different screens.
My class is called ParameterClass, for which I've created a QList:
class SystemData : public QObject
{
Q_OBJECT
Q_PROPERTY(QDeclarativeListProperty<ParameterClass> parameters READ parameters CONSTANT)
QDeclarativeListProperty<ParameterClass> parameters();
...
QList<ParameterClass *> m_parameterList;
}
I've also registered the ParameterClass class and set up an instance of my SystemData as a property, which I know is necessary.
m_context->setContextProperty("SystemData", m_pSystemData);
qmlRegisterType<ParameterClass>();
Now, what I want to do within QML is something like this:
Rectangle {
id: frame
property variant parameter: SystemData.parameters[5]
...
}
I'm just not getting it to work: I keep getting back [undefined]. Am I wasting my time, or am I missing something?
Edit:
I've changed things to use the suggestion from ... . Here are some selections from my updated code.
main.cpp:
#include <QApplication>
#include <QSplashScreen>
#include <QLocale>
#include <QLibraryInfo>
#include <QDeclarativeView>
#include <QDeclarativeContext>
#include <QDeclarativeEngine>
#include <QObject>
#include <QDeclarativeListProperty>
#include "systemdata.h"
#include "parameterclass.h"
static const QString contentPath = "qrc:/qml/qml/pk_ui/";
static const QString filename(contentPath + "main.qml");
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDeclarativeView mainView;
SystemData* systemData = SystemData::getInstance();
QThread thread;
UpdateWorker updateWorker;
QObject::connect((const QObject*)systemData, SIGNAL(startWork()),
(const QObject*)&updateWorker, SLOT(doWork()));
updateWorker.moveToThread(&thread);
thread.start();
systemData->startUpdates();
QFont defaultFont;
defaultFont.setFamily("Sans Serif");
QApplication::setFont(defaultFont);
// Register types to be available in QML
qmlRegisterType<ParameterClass>();
qmlRegisterUncreatableType<SystemEnum>("SystemEnums", 1, 0, "SystemEnum", QString());
mainView.engine()->rootContext()->setContextProperty("SystemData", systemData);
// Set view optimizations not already done for QDeclarativeView
mainView.setResizeMode(QDeclarativeView::SizeRootObjectToView);
mainView.setAttribute(Qt::WA_OpaquePaintEvent);
mainView.setAttribute(Qt::WA_NoSystemBackground);
mainView.setSource(QUrl(filename));
mainView.show();
return app.exec();
}
The ParameterClass looks like this:
class ParameterClass : public QObject
{
Q_OBJECT
Q_PROPERTY(int type READ get_type NOTIFY typeChanged)
Q_PROPERTY(bool enabled READ get_ParameterEnabled WRITE set_ParameterEnabled NOTIFY enabledChanged)
Q_PROPERTY(int groupID READ get_GroupID WRITE set_GroupID NOTIFY groupIDChanged)
Q_PROPERTY(int unitID READ get_UnitID WRITE set_UnitID NOTIFY unitIDChanged)
Q_PROPERTY(int securityLevel READ get_SecurityLevel WRITE set_SecurityLevel NOTIFY securityLevelChanged)
Q_PROPERTY(QString parameterName READ get_ParameterName NOTIFY parameterNameChanged)
Q_PROPERTY(QString shortDescription READ get_ShortDescription NOTIFY shortDescriptionChanged)
Q_PROPERTY(int currentValue READ get_CV WRITE set_valueptrvalue NOTIFY currentValueChanged)
Q_PROPERTY(int lowerBound READ get_LB NOTIFY lowerBoundChanged)
Q_PROPERTY(int upperBound READ get_UB NOTIFY upperBoundChanged)
public:
struct ValueTypes
{
enum
{
IntegerType,
StringType,
StringListType
};
};
ParameterClass(QObject *parent = 0);
int get_type();
bool get_ParameterEnabled();
int get_GroupID();
int get_UnitID();
int get_SecurityLevel();
QString get_ParameterName();
QString get_ShortDescription();
int get_CV() { return *CurrentValuePtr; }
int get_LB() { return *LowerBoundPtr; }
int get_UB() { return *UpperBoundPtr; }
void set_ParameterEnabled(bool InParameterEnabled);
void set_GroupID(int InGroupID);
void set_UnitID(int InUnitID);
void set_SecurityLevel(int InSecurityLevel);
signals:
void typeChanged();
void enabledChanged();
void groupIDChanged();
void unitIDChanged();
void securityLevelChanged();
void parameterNameChanged();
void shortDescriptionChanged();
private:
int type;
bool ParameterEnabled;
int GroupID;
int UnitID;
int SecruityLevel;
QString ParameterName;
QString ShortDescription;
int * CurrentValuePtr;
int * LowerBoundPtr;
int * UpperBoundPtr;
};
And my QML file:
Rectangle {
id: frame
property int val: SystemData.parameters[4].currentValue
...
}
It looks like I'm still getting an undefined value in this case. I'm trying to debug now, so that I can provide more information.
It's very much possible. The key is to make sure you register the QML type and set the context property before setting the source on your QDeclarativeView.
Here's a working example -
main.cpp:
#include <QApplication>
#include <QtDeclarative>
class MyPropertyObject : public QObject {
Q_OBJECT
Q_PROPERTY(int value READ value CONSTANT)
public:
MyPropertyObject(int value = -1) : m_value(value) { }
int value() const {
return m_value;
}
private:
int m_value;
};
class MyObject : public QObject {
Q_OBJECT
Q_PROPERTY(QDeclarativeListProperty<MyPropertyObject> props READ props CONSTANT)
public:
MyObject() {
m_props.append(new MyPropertyObject(55));
m_props.append(new MyPropertyObject(44));
m_props.append(new MyPropertyObject(33));
}
QDeclarativeListProperty<MyPropertyObject> props() {
return QDeclarativeListProperty<MyPropertyObject>(this, m_props);
}
private:
QList<MyPropertyObject *> m_props;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QDeclarativeView view;
view.engine()->rootContext()->setContextProperty(QLatin1String("tester"), new MyObject);
qmlRegisterType<MyPropertyObject>();
view.setSource(QUrl("qrc:///qml/main.qml"));
view.setResizeMode(QDeclarativeView::SizeRootObjectToView);
view.resize(300, 300);
view.show();
return a.exec();
}
#include "main.moc"
main.qml:
import QtQuick 1.1
Rectangle {
property variant foo: tester.props[2].value
Text {
anchors.centerIn: parent
text: parent.foo
}
}
Note: read the docs for the QDeclarativeListProperty constructors. The one I'm using for this example is not the preferred one, but is good for quick prototypes.

Resources