How to create chart in QML with series data exposed from c++ - c++

I am filling QLineSeries data on c++ side and QML chart is supposed to consume them (and update as they change). Data producer is connected to newData which are added to the series data, and that should trigger repaint of the chart.
Previously, the LineSeries were manipulated in QML but now I don't know how to make the c++ QLineSeries instances accessible to QML.
// ...
#include<QtCharts/QLineSeries>
using namespace QtCharts;
/* class holding all data to be displayed as properties */
class UiData: public QObject{
Q_OBJECT
Q_PROPERTY(QLineSeries *xy READ getXy NOTIFY xyChanged);
QLineSeries* getXy(){return &xy; }
signals:
void xyChanged();
public slots:
void newData(float x, float y){
xy.append(x,y);
emit xyChanged();
}
private:
QLineSeries xy;
}
int main(int argc, char* argv[]){
QApplication app(argc,argv);
QQmlApplicationEngine engine;
UiData uiData;
/* make the instance accessiblt from QML */
engine.rootContext()->setContextProperty("uiData",&uiData);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
// ...
return qApp->exec();
}
main.qml:
ChartView{
ValueAxis{
id: _axisX;
}
ValueAxis{
id: _axisY;
}
/*
BEFORE:
data in QML: easy, plus had JS slot calling _xySeries.append(...)
*/
LineSeries{
id: _xySeries;
axisX: _axisX;
axisY: _axisY;
}
/*
NOW:
how to use an already existing LineSeries instance (uiData.xy),
and only change a few attributes, like axisX and axisY?
Something like this:
LineSeries(uiData.xy) {
axisX: _axisX;
axisY: _axisY;
}
or like this:
component.onCompleted:{
// where is something like ChartView.addSeries?
// (there is ChartView.removeSeries already)
uiData.xy.axisX=_axisX;
uiData.xy.axisY=_axisY;
addSeries(uiData.xy);
}
*/
};
Is there a good solution to this?
In an extreme case, I can also create the whole ChartView in c++ (but then again, how to insert it into the QML?).

It can be said that LineSeries is a QLineSeries + other functionalities, so ChartView uses those other functionalities.
What you must do is access the LineSeries in UiData, for this you must use the memory position.
class UiData: public QObject{
Q_OBJECT
Q_PROPERTY(QLineSeries* xy READ xy WRITE setXy NOTIFY xyChanged)
public:
...
QLineSeries *xy() const
{
return mXy;
}
void setXy(QLineSeries *xy)
{
if(mXy == xy) return;
if(mXy)
if(mXy->parent() == this) // is owner
delete mXy;
mXy = xy;
emit xyChanged();
}
public slots:
void newData(qreal x, qreal y){
if(mXy){
mXy->append(x, y);
}
}
...
signals:
void xyChanged();
...
private:
QLineSeries *mXy;
};
*.qml
ChartView{
ValueAxis{
id: _axisX;
}
ValueAxis{
id: _axisY;
}
LineSeries{
id: _xySeries;
axisX: _axisX;
axisY: _axisY;
}
}
Component.onCompleted: uiData.xy = _xySeries // <----
As you can see, a QLineSeries is not being created in C ++, but the pointer serves only to reference LineSeries.
The complete example can be found in the following link.

Related

Unable to populate ComboBox model with QVariantList

From QML:
AudioInfoCpp is the C++ class's object. Aim to to get data from C++ and fill it in the model of the combobox.
property ListModel comboModel: cbItems
AudioInfoCpp
{
id: audioInfoCpp
Component.onCompleted:
{
fetchInputDevices()
comboModel.append(inputDevices1)
console.log(inputDevices1)
}
}
ComboBox
{
id: comboBoxM;
width: 100
model: audioInfoCpp.inputDevices1;
textRole: "langDescription";
}
From .h
#ifndef AUDIOINFO_H
#define AUDIOINFO_H
#include <QAudioDeviceInfo>
#include <QList>
#include <QVariantList>
class AudioInfo : public QObject
{
Q_OBJECT
protected:
QAudioDeviceInfo objQAudioDeviceInfo;
public:
AudioInfo();
Q_PROPERTY(QVariantList inputDevices1 READ inputDevices1 WRITE setInputDevices1 NOTIFY inputDevices1Changed)
Q_INVOKABLE void fetchInputDevices();
QVariantList inputDevices1() const
{
return m_inputDevices1;
}
public slots:
void setInputDevices1(QVariantList inputDevices1)
{
if (m_inputDevices1 == inputDevices1)
return;
m_inputDevices1 = inputDevices1;
emit inputDevices1Changed(m_inputDevices1);
}
signals:
void inputDevices1Changed(QVariantList inputDevices1);
private:
QVariantList m_inputDevices1;
};
#endif // AUDIOINFO_H
.cpp :
void AudioInfo::fetchInputDevices()
{
QList<QAudioDeviceInfo> devices1 = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
if (devices1.empty())
{
qDebug() << "No audio input devices";
}
QVariantList tt;
for (const QAudioDeviceInfo& device : devices1)
{
tt.append(device.deviceName());
}
setInputDevices1( tt );
}
In QML as well as CPP I can see the data printed from the list but I am not able to populate the combobox. There are no errors.
Please guide.
The problem comes from this line:
textRole: "langDescription"
That means it will look through each element in your model for the langDescription field. But you are populating your QVariantList with QStrings:
QVariantList tt;
for (const QAudioDeviceInfo& device : devices1)
{
tt.append(device.deviceName());
}
QStrings don't have a langDescription field, so nothing gets displayed.
To solve this try completely removing the textRole line and allow QML to automatically figure out how to display the names correctly.

Adding/ Removing markers in quickwidget openstreet map

I'm using QQuickwidget in my QT5 gui. I've loaded an openstreet map on it by adding qml files accordingly.
By adding a function in the qml file, I call it through the cpp file by using QMetaObject::invokeMethod. This function adds markers at certain coordinates. This is because I want to control my markers through c++ code itself.
My problem: I am able to load the map and the markers successfully, but I'm not able to remove the markers. My aim is to show the path of a moving object on a map. Therefore, using a timer, I want to update it's position periodically. Every t_samp seconds, I want to remove the marker and add it somewhere else.
the 'item' created in the addMarker code is added to the map by using map.addMapItem(item). By appending map.removeMapItem(item) to the function, the marker disappears. But, the problem is, I can't seem to be able to access 'item' outside the function. Therefore, I cant use map.removeMapItem since I cant input the marker.
I also tried making the function output 'item', so that I can then use it to add/remove the marker. Unfortunately, I dont know the data type of item, and hence cant receive it.
mapview.qml:
import QtQuick 2.12
import QtLocation 5.12
import QtPositioning 5.12
Item {
id: window
Plugin
{
id: mapPlugin
name:"osm"
}
function addMarker(latitude, longitude)
{
var component= Qt.createComponent("qrc:///qml/marker.qml")
var item= component.createObject(window, {coordinate: QtPositioning.coordinate(latitude,longitude)})
map.addMapItem(item)
}
Map
{
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(15.4561,73.8021);
zoomLevel: 14
}
}
marker.qml:
import QtQuick 2.12
import QtLocation 5.12
import QtPositioning 5.12
MapQuickItem
{
id: marker
anchorPoint.x: marker.width / 4
anchorPoint.y: marker.height
sourceItem: Image
{
id: icon
source: "qrc:///images/mapmark.png"
sourceSize.width: 50
sourceSize.height: 50
}
}
mainwindow.cpp: (only relevant snippet)
QObject* target= qobject_cast<QObject*>(ui->quickWidget->rootObject());
QString functionName= "addMarker";
QMetaObject::invokeMethod(target,functionName.toUtf8().constData(), Qt::AutoConnection, Q_ARG(QVariant, 15.4561), Q_ARG(QVariant,73.8021));
Instead of exporting the QML marker to C++, it is best to export a QObject from C++ to QML, and since you want to handle several markers you must use a model.
Explanation of the approach:
The MVC pattern is Qt's natural way of handling a lot of information, and for this it implements views such as MapItemView, and models that can be created based on QAbstractXXXModel. So only the responsibility is to specialize the classes for the objective, such as implementing the logic of just keeping n elements and if there is a new element, remove the oldest one.
Why is it better to export a QObject to QML? The life cycle of the objects in QML are handled by QML, so in your case you could access the markers at a given time QML could delete it so that the pointer in C ++ would append non-reserved memory. Another advantage is that the Q_PROPERTY are recognized in QML and the data type is known by QML and C ++, unlike if you export a QML object to C ++ since only the properties of QObject or QQuickItem will be used. Also when exported using setContextProperty the QObject is global. The disadvantage is that more code is added. For more details read Interacting with QML from C++.
markermodel.h
#ifndef MARKERMODEL_H
#define MARKERMODEL_H
#include <QAbstractListModel>
#include <QGeoCoordinate>
class MarkerModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QGeoCoordinate current READ current NOTIFY currentChanged)
public:
enum MarkerRoles{
PositionRole = Qt::UserRole + 1000,
};
explicit MarkerModel(QObject *parent = nullptr);
void moveMarker(const QGeoCoordinate & coordinate);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
int maxMarkers() const;
void setMaxMarkers(int maxMarkers=0);
QGeoCoordinate current() const;
signals:
void currentChanged();
private:
void insert(int row, const QGeoCoordinate & coordinate);
void removeLastMarker();
QList<QGeoCoordinate> m_markers;
QGeoCoordinate m_current;
int m_maxMarkers;
};
#endif // MARKERMODEL_H
markermodel.cpp
#include "markermodel.h"
MarkerModel::MarkerModel(QObject *parent)
: QAbstractListModel(parent),
m_maxMarkers(0)
{
}
void MarkerModel::moveMarker(const QGeoCoordinate &coordinate)
{
QGeoCoordinate last = m_current;
m_current = coordinate;
Q_EMIT currentChanged();
if(!last.isValid())
return;
if(m_maxMarkers == 0){
insert(0, last);
return;
}
if(rowCount() >= m_maxMarkers){
while (rowCount() >= m_maxMarkers)
removeLastMarker();
removeLastMarker();
}
insert(0, last);
}
int MarkerModel::maxMarkers() const
{
return m_maxMarkers;
}
void MarkerModel::setMaxMarkers(int maxMarkers)
{
m_maxMarkers = maxMarkers > 1 ? maxMarkers: 0;
}
QGeoCoordinate MarkerModel::current() const
{
return m_current;
}
int MarkerModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_markers.count();
}
QVariant MarkerModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if(role == PositionRole)
return QVariant::fromValue(m_markers[index.row()]);
return QVariant();
}
QHash<int, QByteArray> MarkerModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[PositionRole] = "position";
return roles;
}
void MarkerModel::insert(int row, const QGeoCoordinate & coordinate)
{
beginInsertRows(QModelIndex(), row, row);
m_markers.insert(row, coordinate);
endInsertRows();
}
void MarkerModel::removeLastMarker()
{
if(m_markers.isEmpty())
return;
beginRemoveRows(QModelIndex(), rowCount()-1, rowCount()-1);
m_markers.removeLast();
endRemoveRows();
}
mainwindow.h
// ...
MarkerModel marker_model;
// ...
mainwindow.cpp
// ...
ui->quickWidget->rootContext()->setContextProperty("marker_model", &marker_model);
ui->quickWidget->setSource(QUrl("qrc:/mapview.qml"));
// ...
main.qml
// ...
Map{
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(15.4561,73.8021);
zoomLevel: 14
Marker{
coordinate: marker_model.current
}
MapItemView{
model: marker_model
delegate: Marker{
coordinate: model.position
}
}
}
// ...
The complete example is here

Read C++ QVector of structs in QML

In my C++ class I have
struct trackPoint {
QString lat;
QString lon;
QString elevation;
};
QVector<trackPoint> trackPoints;
In QML I want to access this as a multi-dimensional array of lon,lat pairs
[[0,1],[1,1],[2,1]]
Is this possible using the Q_Property mechanism? As I am pretty sure that structs cannot be exposed to QML?
I've tied:-
Q_PROPERTY(QVector<trackPoint> trackPoints READ gpx)
With a method:-
QVector<trackPoint> GPXFileIO::gpx() const {
return trackPoints;
}
But this gives me the error:-
QMetaProperty::read: Unable to handle unregistered datatype 'QVector<trackPoint>' for property 'GPXFileIO::trackPoints'
A simple way to expose a struct to QML is using Q_GADGET with Q_PROPERTY so we can get each element of the structure, they will not be part of an array. On the other hand QVector is supporting a number of elements with QString, int, QUrl, etc. but not for new types, in which case QVariantList should be used.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QVector>
struct TrackPoint {
Q_GADGET
Q_PROPERTY(qreal lat MEMBER lat)
Q_PROPERTY(qreal lon MEMBER lon)
Q_PROPERTY(qreal elevation MEMBER elevation)
public:
qreal lat;
qreal lon;
qreal elevation;
};
class TrackClass: public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList trackpoints READ gpx)
public:
TrackClass(QObject *parent=nullptr):QObject(parent){
trackPoints << TrackPoint{10, 10, 10} << TrackPoint{11, 11, 11};
}
QVariantList gpx() const{
QVariantList l;
for(const TrackPoint & p: trackPoints){
l << QVariant::fromValue(p);
}
return l;
}
private:
QVector<TrackPoint> trackPoints;
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
TrackClass track;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("track", &track);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Component.onCompleted: {
for( var i in track.trackpoints){
var p = track.trackpoints[i];
console.log("lat: ", p.lat, "lon: ", p.lon, "elevation: ", p.elevation)
}
}
}
Output:
qml: lat: 10 lon: 10 elevation: 10
qml: lat: 11 lon: 11 elevation: 11
Without adding the complexity of gadgets, I find pretty straight forward the usage of QVariantList of QVariantMap, as Qvariant.
This is how I do it:
Q_PROPERTY(QVariant trackpoints READ gpx NOTIFY gpxChanged)
QVariant TrackClass::gpx() const
{
QVariantList itemsList;
for(const TrackPoint &p : trackPoints)
{
QVariantMap itemMap;
itemMap.insert("lat", p.lat);
itemMap.insert("lon", p.lon);
itemMap.insert("elevation", p.elevation);
itemsList.append(itemMap);
}
return QVariant::fromValue(itemsList);
}
Then in QML you can use trackpoints as model and access item fields by name.
It is a good practice to also add a NOTIFY signal, to be called when your QVector changes.

QML: Refresh issue while receivinig C++ signals passed from C++

Hi i have bee playing recently with Qt for mobile development. I started simple using all the code in C++ (given that I have more experience with it) and now I'm starting to use QML. In my app I have a QQuickWidget in the ui where I display a map. The goal is simple to center the map every time the phone emits the coordinates.
This is how I set up my QML
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
and this is my QML file
Rectangle {
width: 300
height: 300
visible: true
Plugin {
id: osmPlugin
name: "esri"
// specify plugin parameters if necessary
// PluginParameter {
// name:
// value:
// }
}
Map {
id: map
anchors.fill: parent
plugin: osmPlugin
center: QtPositioning.coordinate(51.0, -114.0)
zoomLevel: 10
activeMapType: map.supportedMapTypes[1] // This selects the type of map to display
}
I declared a class to handle the sending of the coordinates to the QML and setup a connection on the QML for receiving the signal
#ifndef COORDUPDATE_H
#define COORDUPDATE_H
#include <QObject>
#include <QVariant>
class coordUpdate : public QObject
{
Q_OBJECT
public:
explicit coordUpdate(QObject *parent = nullptr);
// Q_INVOKABLE void sendupd(double xcoord, double ycoord);
private:
signals:
void sendCoord(QVariant xcoord,QVariant ycoord);
private slots:
public slots:
void sendupd(double xcoord, double ycoord);
};
#endif // COORDUPDATE_H
#include "coordupdate.h"
#include <QDebug>
coordUpdate::coordUpdate(QObject *parent) :
QObject(parent)
{
}
void coordUpdate::sendupd(double xcoord,double ycoord){
emit sendCoord(xcoord,ycoord);
qDebug() << "signal emitted";
}
The problem I'm having is that if I call
void MainWindow::setGPSLocation(QGeoPositionInfo geoPositionInfo)
{
statusBar()->showMessage("Updating position...",1000);
QString text="Location=unknown";
if (geoPositionInfo.isValid())
{
// get the current location coordinates
QGeoCoordinate geoCoordinate = geoPositionInfo.coordinate();
// transform coordinates to lat/lon
qreal latitude = geoCoordinate.latitude();
qreal longitude = geoCoordinate.longitude();
qreal altitude = geoCoordinate.altitude();
double lon = longitude;
double lat = latitude;
//on position updated we also need to record the data
coordUpdate upd;
ui->quickWidget->rootContext()->setContextProperty("updcoord",&upd);
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
upd.sendupd(longitude,latitude);
qDebug() << "reading coordinates" << longitude << latitude;
}
}
Then the coordinates get updated but the whole thing gets refreshed. Is there a better way to update the information sent to the QML file? I know I would better developing in QML exclusively but I very interested in the use of QQuickWiget for the time being.
I'm not asking how to connect a signal from C++ to QML. The code does that. What I was asking was how to properly update the map with the information passed. I already read the documentation. Unfortunately not much of the documentation focus on QQuickWidgets but rather have a main class instantiated with QQmlEngine.
As described here http://doc.qt.io/qt-5/qtqml-cppintegration-overview.html, you have basically three ways to integrate C++ with QML code :
Expose your C++ class to QML ( properties, signals, slots..) and register it as an instanciable QML type
Same as 1 but register it as a singleton type so it is instantiated by the QML engine, not explicitly in the QML code
Same as 1 but make it available to QML via context properties for example, in this case, you create the instance of your class on the C++ side of your application
Solved it by moving the Connections inside the Map and redefining in the mainwindow.cpp and mainwindow.h the way the QML is instantiated. That way the map is refreshed properly every time the QML captures the signal.
Added to mainwindow.cpp
private:
coordUpdate *upd;
Changed
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->startU->setDisabled(true);
setupLogging();
setupGPS();
coordUpdate upd;
ui->quickWidget->rootContext()->setContextProperty("updcoord",upd);
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
}
to
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->startU->setDisabled(true);
setupLogging();
setupGPS();
upd = new coordUpdate;
ui->quickWidget->rootContext()->setContextProperty("updcoord",upd);
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
}
and in the QML
Rectangle {
width: 300
height: 300
visible: true
Plugin {
id: osmPlugin
name: "esri"
}
Map {
id: map
anchors.fill: parent
plugin: osmPlugin
// center: QtPositioning.coordinate(y, x)
zoomLevel: 15
activeMapType: map.supportedMapTypes[1] // This selects thetype of mapa to display
MapCircle {
id:circle
center: QtPositioning.coordinate(y, x)
radius: 50
color: 'red'
}
// Create connections with c++
Connections // Define actions for custom slots
{
id:cppConnection
target: updcoord
onSendCoord: {
// To access signal parameter, name the parameter
console.log("signal received",ycoord,xcoord)
circle.center = QtPositioning.coordinate(ycoord,xcoord)
}
}
}
}

Qt 5.2 QObject::connect: Cannot connect (null)::

I'm a noob - not good with QML or C++ yet, but getting there. I seem to have hit a stumbling block I can't get past. I'm receiving the following error in my attempt to run a build and I'm not sure what I missed in my code...
**QObject::connect: Cannot connect (null)::buttonClicked_enable() to TLA_Funcs::sys_enable()
**
I've looked through the other versions of the question here and it seems that I have my code correct, but I still get the error. Can someone take a peek? Here's the relevant sample of my code (the rest is too long and I left out the guts of the function - too long).
QML Code:
Rectangle {
id: main
width: 800
height: 600
color: "#abcfe9"
border.width: 4
border.color: "#000000"
signal buttonClicked_enable()
Button {
id: enable
x: 628
y: 55
text: "ENABLE"
onClicked:buttonClicked_enable()
}
//....
}
My class header:
#ifndef TLA_FUNCS_H
#define TLA_FUNCS_H
#include <QObject>
class TLA_Funcs : public QObject
{
Q_OBJECT
public:
explicit TLA_Funcs(QObject *parent = 0);
signals:
public slots:
Q_INVOKABLE void sys_enable(){return ;}
private:
};
#endif
And in my main.cpp file:
#include "TLA_Funcs.h"
TLA_Funcs::TLA_Funcs(QObject *parent) :
QObject(parent)
{
}
int main (int argc, char*argv[]) {
QGuiApplication app(argc, argv);
QQuickView *view = new QQuickView(QUrl("main.qml"));
view->show();
QQuickItem *item = view->rootObject();
TLA_Funcs *funcs = new TLA_Funcs();
QObject::connect(item, SIGNAL(buttonClicked_enable()), funcs, SLOT(sys_enable()));
}
I've defined the signals in the parent rectangle, and in the button code I've tried using:
onClicked:main.buttonClicked_enable()
as well as:
onClicked: {
buttonClicked_enable()
TLA_Funcs.sys_enable()
}
That didn't help either. I also tried defining the functions under "signal" in the class, but that made more of a mess. Can someone at least point me in the right direction, and keep in mind, I'm still a noob... Thanks All!!!
Problem Solved: there was an error in the .pro file. I copied the code into a new project and it worked correctly 100%. Thanks all for the help!