I recently started with QML and I'm searching for a way to give data from c++ to my ChartView in QML.
I would prefer a solution where I can send a QMap with something like Q_PROPERTY so it updates automatically.
I've searched and found that you can do a function where you then can use 'append()' to added values to the chart. But I seem to be unable to send some kind of list to the QML...
QML file:
ChartView {
theme: ChartView.ChartThemeQt
antialiasing: true
DateTimeAxis {
id: dateTimeAxisX
}
ValueAxis{
id: valueAxisY
min: 0
max: 15
titleText: "Voltage (V)"
}
LineSeries {
id: voltageSeries
axisX: dateTimeAxisX
axisY: valueAxisY
name: "Battery Voltage"
}
}
robot.h:
class Robot: public QObject
{
...
Q_PROPERTY(QMap<int, double> list_battery_voltages READ getList_battery_voltages NOTIFY listBatteryVoltagesChanged)
...
}
main.cpp:
int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QApplication app(argc, argv);
QQmlApplicationEngine engine;
// Load custom class inside engine
QScopedPointer<Robot> robot(new Robot(app.applicationDirPath() + "/robot_settings.ini"));
engine.rootContext()->setContextProperty("robot", robot.data());
QQmlComponent component(&engine, QUrl(QLatin1String("qrc:/main.qml")));
component.create();
return app.exec();
}
Are there elegant solutions that I'm overseeing?
I have solved it by using QVariantMap. It is not the most ellegant solution but it works :)
I have a javascript function in my QML file that fills the lineseries.
This function is connected to my robot signal that will be send when new data have arrived.
robot.h:
class Robot: public QObject
{
...
Q_PROPERTY(QVariantMap list_battery_voltages READ getList_battery_voltages NOTIFY listBatteryVoltagesChanged)
...
}
QML:
ChartView {
...
Connections {
target: robot
onMapBatteryVoltagesChanged: insertVoltages()
}
Component.onCompleted: {
insertVoltages()
}
...
function insertVoltages() {
var voltages_map = robot.map_battery_voltages
for (var prop in voltages_map) {
voltageSeries.append(prop, voltages_map[prop])
}
}
...
}
As I remember Javascript in QML operates with C++ array using QVariantList and QVariantMap only. They are both not good for you as I see. So I advice you to store your map in C++ in a way you like and provide some convenient way to access the values. For example, using singleton:
mysingleton.h
class MySingleton : public QObject
{
Q_OBJECT
public:
explicit MySingleton(QObject *parent = nullptr);
Q_INVOKABLE int count();
Q_INVOKABLE double getX(int index);
Q_INVOKABLE double getY(int index);
private:
QList<QPair<double, double>> m_map;
};
mysingleton.cpp
MySingleton::MySingleton(QObject *parent) : QObject(parent)
{
QRandomGenerator *generator = QRandomGenerator::global();
int count = generator->generate() % 10;
double xAccumulated = 0;
for(int i = 0;i < count;i ++)
{
double x = generator->generateDouble() / (double)count;
xAccumulated += x;
double y = generator->generateDouble();
m_map.append(QPair<double, double>(xAccumulated, y));
}
}
int MySingleton::count()
{
return m_map.count();
}
double MySingleton::getX(int index)
{
return m_map.at(index).first;
}
double MySingleton::getY(int index)
{
return m_map.at(index).second;
}
registering (main.cpp):
static QObject *my_singleton_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
return new MySingleton(engine);
}
main() {
qmlRegisterSingletonType<MySingleton>("Qt.MyTest", 1, 0, "MySingleton", my_singleton_provider);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
}
And so now you can use this in QML:
import QtQuick 2.9
import QtCharts 2.1
import Qt.MyTest 1.0
ChartView {
anchors.fill: parent
LineSeries {
id: series
Component.onCompleted: {
for(var i = 0;i < MySingleton.count();i ++) {
series.append(MySingleton.getX(i), MySingleton.getY(i));
}
}
}
}
Using this way could give you some additional advantages like data change signal etc.
Related
I've got a struct with two fields, for example:
struct testStruct
{
Q_GADGET
Q_PROPERTY(QString text MEMBER m_text);
Q_PROPERTY(QString value MEMBER m_value);
public:
QString m_text;
QString m_value;
};
There is a QList<testStruct> m_testStructs member of my "AppEngine" class exposed to QML via
Q_PROPERTY(QList<testStruct> testStructs READ testStructs NOTIFY testStructsChanged).
It is filled like that:
testStruct newStruct1, newStruct2;
newStruct1.m_text = "text1";
newStruct1.m_value = "value1";
newStruct2.m_text = "text2";
newStruct2.m_value = "value2";
m_testStructs << newStruct1 << newStruct2;
So I want to see "text" members in ComboBox list and use "value" members in further operations.
In fact QML ComboBox popup shows me the list of objects names when I set ComboBox's "textRole" property to "text" and "valueRole" to "value", but it does nothing for "currentText" or "currentValue" properties when I click the item, only "currentIndex" changes. Also "displayText" remains blank.
This is what I get in console when clicking those items:
qml: currentIndex: 0; currentText: ; currentValue: undefined
qml: currentIndex: 1; currentText: ; currentValue: undefined
So I see that ComboBox gets members of struct, but doesn't want to work with them. What should I do to make "currentText" and "currentValue" members of ComboBox work as they should?
Here are all the needed files:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "appengine.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(u"qrc:/qml_testComboBoxStruct/main.qml"_qs);
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
//exposing AppEngine class to QML
AppEngine appEngineObj;
QQmlContext *context = engine.rootContext();
context->setContextProperty("AppEngine", &appEngineObj);
engine.load(url);
return app.exec();
}
my custom class header AppEngine.h
#ifndef APPENGINE_H
#define APPENGINE_H
#include <QObject>
#include <QDebug>
struct testStruct
{
Q_GADGET
Q_PROPERTY(QString text MEMBER m_text);
Q_PROPERTY(QString value MEMBER m_value);
public:
QString m_text;
QString m_value;
};
Q_DECLARE_METATYPE(testStruct)
class AppEngine : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<testStruct> testStructs READ testStructs NOTIFY testStructsChanged);
public:
explicit AppEngine(QObject *parent = nullptr);
QList<testStruct> testStructs();
private:
QList<testStruct> m_testStructs;
signals:
void testStructsChanged();
};
#endif // APPENGINE_H
my custom class cpp file AppEngine.cpp
#include "appengine.h"
AppEngine::AppEngine(QObject *parent)
: QObject{parent}
{
testStruct newStruct1, newStruct2;
newStruct1.m_text = "text1";
newStruct1.m_value = "value1";
newStruct2.m_text = "text2";
newStruct2.m_value = "value2";
m_testStructs << newStruct1 << newStruct2;
qDebug() << "m_testStructs.length():" << m_testStructs.length();
}
QList<testStruct> AppEngine::testStructs()
{
qDebug() << "testStructs()";
return m_testStructs;
}
main.qml
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("C++ struct to QML ComboBox")
ComboBox
{
anchors.centerIn: parent
width: 180
height: 30
id: comboBoxID
textRole: "text"
valueRole: "value"
model: AppEngine.testStructs
onActivated:
{
console.log('currentIndex:', currentIndex, '; currentText:', currentText, ';currentValue:', currentValue);
}
}
}
As I checked in the main.qml model property cant find and understand as you show it is undefined.
qml: currentIndex: 0; currentText: ; currentValue: undefined
qml: currentIndex: 1; currentText: ; currentValue: undefined
from ListView::model property
The model provides the set of data that is used to create the items in
the view. Models can be created directly in QML using ListModel,
ObjectModel, or provided by C++ model classes. If a C++ model class is
used, it must be a subclass of QAbstractItemModel or a simple list.
For example, you can have this :
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("C++ struct to QML ComboBox")
ComboBox
{
id: comboBoxID
anchors.centerIn: parent
width: 180
height: 30
textRole: "text"
valueRole: "value"
model: ListModel {
id : model
ListElement { text: "text1" ; value : "value1" }
ListElement { text: "text2" ; value : "value2" }
ListElement { text: "text3" ; value : "value3" }
ListElement { text: "text4" ; value : "value4" }
}
onActivated:
{
console.log('currentIndex:', currentIndex, '; currentText:', currentText, '; currentValue:', currentValue);
}
}
}
Because you use QML ListModel if you want to define your model from C++ it must be a subclass of QAbstractItemModel or a simple list.
updated :
you need to use QStandardItemModel which inherits from QAbstractItemModel you cant inherit from the abstract interface because of that I use QStandardItemModel
in appengine.h:
#ifndef APPENGINE_H
#define APPENGINE_H
#include <QObject>
#include <QDebug>
#include <QStandardItemModel>
struct testStruct: public QStandardItemModel
{
Q_OBJECT
Q_PROPERTY(QString text MEMBER m_text);
Q_PROPERTY(QString value MEMBER m_value);
public:
QString m_text;
QString m_value;
};
Q_DECLARE_METATYPE(testStruct)
class AppEngine : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<testStruct *> testStructs READ testStructs NOTIFY testStructsChanged);
public:
explicit AppEngine(QObject *parent = nullptr);
QList<testStruct *> testStructs();
private:
QList<testStruct *> m_testStructs;
signals:
void testStructsChanged();
};
#endif // APPENGINE_H
In appengine.cpp
#include "appengine.h"
AppEngine::AppEngine(QObject *parent)
: QObject{parent}
{
testStruct *newStruct1 = new testStruct;
testStruct *newStruct2 = new testStruct;
newStruct1->m_text = "text1";
newStruct1->m_value = "value1";
newStruct2->m_text = "text2";
newStruct2->m_value = "value2";
m_testStructs << newStruct1 << newStruct2;
qDebug() << "m_testStructs.length():" << m_testStructs.length();
}
QList<testStruct *> AppEngine::testStructs()
{
qDebug() << "testStructs()";
return m_testStructs;
}
I got this basic project:
main.qml
Rectangle{
Text {
id: text1
objectName: "showStr"
property string _textField: "passive"
text: _textField
}
Button{
id: _button
signal buttClick(string str)
anchors.top: text1.bottom
text: "Button"
onClicked:
{
_button.buttClick("state: active")
}
}
}
myClass.cpp
class myClass : public QObject{
Q_OBJECT
public:
explicit myClass(QObject *parent = nullptr): QObject(parent) {}
public slots:
void setTextProperty(const QString& s) {this->str = s;}
void getStrToQObj()
{
//TODO: set updated str into qml
}
signals:
void strChanged(const QString& time);
private:
QString str;
};
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlComponent component(&engine,
QUrl(QLatin1String("../myProj/qml/main.qml")));
QObject* item = component.create();
QObject* showTime = item->findChild<QObject*>("showTime");
QObject* butt = item->findChild<QObject*>("start");
myClass mc(item);
QObject::connect(butt, SIGNAL(buttClick(QString)),
&mc, SLOT(setTextProperty(QString)));
return app.exec();
}
Connection of Qml signal buttClick to setTextProperty(const QString& s) works fine and myClass::str is changed.The question is how to connect Text property _textField to update every time, when myClass::str is changed?
Do not export objects from QML to C++, instead do the opposite: export the C ++ objects to QML since this way the connection is simple and there is no need to use the old connection syntax:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
myClass mc;
engine.rootContext()->setContextProperty("mc", &mc);
QQmlComponent component(&engine,
QUrl(QLatin1String("../myProj/qml/main.qml")));
QObject* item = component.create();
return app.exec();
}
Rectangle {
Text {
id: text1
text: "passive"
}
Button {
id: _button
anchors.top: text1.bottom
text: "Button"
onClicked: {
mc.setTextProperty("state: active");
}
}
Connections {
function onTimeChanged(text) {
text1.text = text;
}
target: mc
}
}
It is old syntax of connection, but according to separations logiv from GUI, and for ones who do not know JS well,turns out you can call parent() method to you class because it is inherit QObject class. This return to you Qobject* you put in constructor of your class, and then you can findChild and work with it:
void myClass::f(){
//do fancy stuff with your myClass::str
emit activityChanged(this->parent())
}
void getStrToQObj(QObject* qo)
{
auto tmp = qo->findChild<QObject*>("showTime");
tmp->setProperty("_textField", this->str);
}
And connect this in main.cpp like:
QObject::connect(&mc, SIGNAL(strChanged(QObject*)),
&mc, SLOT(getStrToQObj(QObject*)));
I need to push a qml page to stackview from other qml page.
So i am using c++-qml interacting for this problem.
my c++ code finds page item, but does not find stackView.
main.cpp:
int main(int argc, char *argv[])
{
...
QQmlEngine engine;
QQmlComponent component(&engine, QUrl("qrc:/qml/main.qml"));
QObject* object = component.create();
Settings::defineObject(object);
return app.exec();
}
settings.h:
class Settings : public QObject
{
Q_OBJECT
private:
static QObject* object;
public:
...
Q_INVOKABLE void push(QString fileName);
static void defineObject(QObject* obj);
};
settings.cpp:
QObject* Settings::object = nullptr;
...
void Settings::push(QString fileName)
{
if (Settings::object == nullptr) QCoreApplication::exit(-3);
QObject* page = Settings::object->findChild<QObject*>("page");
if (!page) QCoreApplication::exit(-4);
QObject* stackView = page->findChild<QObject*>("stackView");
if (!stackView) QCoreApplication::exit(-5);
QMetaObject::invokeMethod(stackView, "push", Q_ARG(QString, fileName));
}
void Settings::defineObject(QObject* obj)
{
object = obj;
}
main.qml:
ApplicationWindow {
...
Page {
id: page
...
StackView {
id: stackView
initialItem: "Home.qml"
anchors.fill: parent
}
}
}
My program always exits -5 code.
I have a small problem. Can someone show me how can I update a qml text from c++. I have an example using threads but I don't want to apply this method because I don't know how to set a
parameter in run() function.To fully understand me here is my code.In the main function when I start the thread I want to put my custom text or a string variable that has a text.
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
class Thread : public QThread
{
Q_OBJECT
public:
Thread(QObject *parent=nullptr);
~Thread() override;
Q_SLOT void stop();
Q_SIGNAL bool textChanged(const QString & text);
protected:
void run() override;
};
#endif // THREAD_H
thread.c
#include "thread.h"
#include <QDebug>
Thread::Thread(QObject *parent):
QThread(parent)
{
}
Thread::~Thread()
{
}
void Thread::stop()
{
requestInterruption();
wait();
}
void Thread::run() //here I want to specify a QString variable such that in main function to call the function with my text, and not specified the text from here
{
int i=0;
while(!isInterruptionRequested()){
QString text;
text = QString("I changed the text"); // I don't want to declare from here the text.
Q_EMIT textChanged(text);
QThread::msleep(20);
qDebug() <<i++;
}
}
main.cpp
...
Thread thread;
QQmlApplicationEngine engine;
QObject::connect(&app, &QGuiApplication::aboutToQuit, &thread, &Thread::stop);
thread.start();
engine.rootContext()->setContextProperty("thread", &thread);
engine.load(QUrl("qrc:/main.qml"));
thread.stop();
...
main.qml
.....
Text {
objectName: "myLabel"
id: txt
width: 200
height: 29
color: "#faf9f9"
text: qsTr("Text")
font.pixelSize: 12
}
Connections{
target: thread
onTextChanged: txt.text = text
}
.....
You can send data from C++ to qml using signals, like this:
//main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
// Class init
YourClass yourObject;
// Embedding C++ Objects into QML with Context Properties
QQmlContext* ctx = engine.rootContext();
ctx->setContextProperty("yourObject", &yourObject);
return app.exec();
}
//main.qml
import QtQuick 2.6
Window {
id: mainWindow
Connections {
target: gapi
onSignalData: {
console.log("Data: " + data)
textToChange.text = "Changed to: " + data
}
}
Text {
id: textToChange
text: "beforeChange"
}
}
//yourClass.h
class YourClass : public QObject
{
signals:
// Signal from YourClass to QML
void signalData(QString data);
}
//yourClass.cpp
emit signalData("Hello QML"); // Signal from GAPI to QML
This page https://felgo.com/cross-platform-development/how-to-expose-a-qt-cpp-class-with-signals-and-slots-to-qml have a complete tutorial about "How to Expose a Qt C++ Class with Signals and Slots to QML"
I seem to be totally lost in the declaration of forms in QML. I have a C++ object with properly set Q_PROPERTies, I have access to an object of that class in the QML, and I want to use sliders in the QML. How exactly do I make the value of the Slider update the information of the properties in the object, and vise-versa?
If it's anything like regular QML, then the following approaches will work.
Context property
Use an explicit Binding or use Slider's valueChanged signal:
#include <QGuiApplication>
#include <QtQml>
class Object : public QObject
{
Q_OBJECT
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged)
public:
explicit Object(QObject *parent = 0) :
QObject(parent),
mValue(0)
{
}
qreal value() const {
return mValue;
}
void setValue(qreal value) {
if (value != mValue) {
mValue = value;
emit valueChanged();
}
}
signals:
qreal valueChanged();
private:
qreal mValue;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
Object object;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("object", &object);
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.3
import QtQuick.Controls 1.2
ApplicationWindow {
width: 400
height: 400
visible: true
Binding {
target: object
property: "value"
value: slider.value
}
Slider {
id: slider
// You can also react to the valueChanged signal of Slider:
//onValueChanged: object.value = value
}
}
Registered QML Type
Use a simple binding:
#include <QGuiApplication>
#include <QtQml>
class Object : public QObject
{
Q_OBJECT
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged)
public:
explicit Object(QObject *parent = 0) :
QObject(parent),
mValue(0)
{
}
qreal value() const {
return mValue;
}
void setValue(qreal value) {
if (value != mValue) {
mValue = value;
emit valueChanged();
}
}
signals:
qreal valueChanged();
private:
qreal mValue;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<Object>("Test", 1, 0, "Object");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.3
import QtQuick.Controls 1.2
import Test 1.0
ApplicationWindow {
width: 400
height: 400
visible: true
Object {
id: object
value: slider.value
onValueChanged: print(value)
}
Slider {
id: slider
}
}