QT QML Model from C++ - Issue with Map Rendering - c++

I am building an application to create and edit gpx files on a map. I want to be able to render a line and set of markers from a single model.
Each GPX point has a co-ordinate, elevation and a time. I am aiming to create a model that can be used to show it on the map and also show the elevation profile.
I have modified the answer to this question QT QmlMap PolyLine not updated properly when underlying model changes in order to base my model on a structure of GPX points.
main.c
#include "mainwindow.h"
#include <QApplication>
#include <QQmlApplicationEngine>
#include "mapmarker.h"
#include <QQmlContext>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
//Added for model registration
QQmlApplicationEngine engine;
qmlRegisterType<MarkerModel>("MarkerModel", 1, 0, "MarkerModel");
engine.load(QUrl(QStringLiteral("qrc:/mainWindow.qml")));
return app.exec();
}
mainWindow.qml
import QtQuick 2.3
import QtQuick.Window 2.3
import QtLocation 5.9
import MarkerModel 1.0
ApplicationWindow {
id: appWindow
width: 1512
height: 1512
visible: true
x:100
y:100
MarkerModel{
id: markerModel
}
Plugin {
id: mapPlugin
name: "osm"
}
Map {
id: mapView
anchors.fill: parent
plugin: mapPlugin
MapItemView {
model: markerModel
delegate: routeMarkers
}
MapPolyline {
line.width: 5
path: markerModel.path
}
Component {
id: routeMarkers
MapCircle {
radius: 10
center: positionRole
}
}
MouseArea {
anchors.fill: parent
onClicked: {
var coord = parent.toCoordinate(Qt.point(mouse.x,mouse.y))
markerModel.addMarker(coord)
}
}
}
}
mapmarker.h
#ifndef MAPMARKER_H
#define MAPMARKER_H
#include <QAbstractListModel>
#include <QGeoCoordinate>
#include <QDebug>
#include <QDate>
struct gpxCoordinate {
QGeoCoordinate latlon;
float ele;
QDateTime time;
};
class MarkerModel : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(QVariantList path READ path NOTIFY pathChanged)
public:
enum MarkerRoles{positionRole = Qt::UserRole, pathRole};
MarkerModel(QObject *parent=nullptr): QAbstractListModel(parent)
{
connect(this, &QAbstractListModel::rowsInserted, this, &MarkerModel::pathChanged);
connect(this, &QAbstractListModel::rowsRemoved, this, &MarkerModel::pathChanged);
connect(this, &QAbstractListModel::dataChanged, this, &MarkerModel::pathChanged);
connect(this, &QAbstractListModel::modelReset, this, &MarkerModel::pathChanged);
connect(this, &QAbstractListModel::rowsMoved, this, &MarkerModel::pathChanged);
}
Q_INVOKABLE void addMarker(const QGeoCoordinate &coordinate, float elevation = 0, QDateTime dateTime = QDateTime::currentDateTime()) {
gpxCoordinate item;
item.latlon = coordinate;
item.ele = elevation;
item.time = dateTime;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_coordinates.append(item);
endInsertRows();
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
if(parent.isValid()) return 0;
return m_coordinates.count();
}
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
if(row + count > m_coordinates.count() || row < 0)
return false;
beginRemoveRows(parent, row, row+count-1);
for(int i = 0; i < count; ++i)
m_coordinates.removeAt(row + i);
endRemoveRows();
return true;
}
bool removeRow(int row, const QModelIndex &parent = QModelIndex()) {
return removeRows(row, 1, parent);
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (index.row() < 0 || index.row() >= m_coordinates.count())
return QVariant();
if(role == Qt::DisplayRole)
return QVariant::fromValue(index.row());
else if(role == MarkerModel::positionRole){
return QVariant::fromValue(m_coordinates[index.row()].latlon);
}
return QVariant();
}
QHash<int, QByteArray> roleNames() const override{
QHash<int, QByteArray> roles;
roles[positionRole] = "positionRole";
return roles;
}
QVariantList path() const{
QVariantList path;
for(const gpxCoordinate & coord: m_coordinates){
path << QVariant::fromValue(coord.latlon);
}
return path;
}
signals:
void pathChanged();
private:
QVector<gpxCoordinate> m_coordinates;
};
#endif // MARKERMODEL_H
I think I have made a really basic mistake somewhere as I can click on the map and draw a polyline but the MapCircles are not rendering. I see the error:-
Unable to assign [undefined] to QGeoCoordinate
When I first click on the map.
Have I misunderstood how models/roles work in Qt QML?

I have tracked this down to a build issue. I had some additional paths in my .pro file and was including some libraries that were not being used (spatialite) removing these fixed the issue completely.
I will leave the question up as it may be useful to anyone wanting to do something similar.

Related

QML access C++ model from parent component in nested object

I have a C++ list model, where I can read data properly
model.h
class Animal
{
public:
Animal(const QString &type, const QString &size);
//![0]
QString type() const;
QString size() const;
private:
QString m_type;
QString m_size;
//![1]
};
class AnimalModel : public QAbstractListModel
{
Q_OBJECT
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
//![1]
void addAnimal(const Animal &animal);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Animal> m_animals;
//![2]
};
model.cpp
#include "model.h"
Animal::Animal(const QString &type, const QString &size)
: m_type(type), m_size(size)
{
}
QString Animal::type() const
{
return m_type;
}
QString Animal::size() const
{
return m_size;
}
AnimalModel::AnimalModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void AnimalModel::addAnimal(const Animal &animal)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_animals << animal;
endInsertRows();
}
int AnimalModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_animals.count();
}
QVariant AnimalModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_animals.count())
return QVariant();
const Animal &animal = m_animals[index.row()];
if (role == TypeRole)
return animal.type();
else if (role == SizeRole)
return animal.size();
return QVariant();
}
//![0]
QHash<int, QByteArray> AnimalModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
//![0]
main.cpp
#include "model.h"
#include <QGuiApplication>
#include <qqmlengine.h>
#include <qqmlcontext.h>
#include <qqml.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
//![0]
int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);
AnimalModel model;
model.addAnimal(Animal("Wolf", "Medium"));
model.addAnimal(Animal("Polar bear", "Large"));
model.addAnimal(Animal("Quoll", "Small"));
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setInitialProperties({{"model", QVariant::fromValue(&model)}});
//![0]
view.setSource(QUrl("qrc:view.qml"));
view.show();
return app.exec();
}
If I want to add a parent component to my listview, model is not accessible anymore because its visibility it's only from parent object. How can I expose the model then?
import QtQuick 2.0
import QtQuick.Window 2.15
//![0]
Window{
id: root
// how to catch here the model?
visible: true
width: 640
height: 480
ListView {
anchors.fill: parent
model: root.model // does not work
delegate: Text {
required property string type
required property string size
text: "Animal: " + type + ", " + size
}
}
}
Ok, that's really funny bug.
First of all, according to the Qt docs the property definition is:
[default] [required] [readonly] property <propertyType> <propertyName>
as you see the keyword property is mandatory.
The another issue is setInitialProperties says: Note: You can only use this function to initialize top-level properties.. So moving the ListView from the root item (and the model property too) makes this property inaccessible.
and one more issue is that the ListView already has a property named model, the attempt to redefine is incorrect. So you have to add some root item with the property model and that will solve the issue. Sure you will need to bind the ListView.model to this property
And the small example :)
Item {
id: root
width: 600
height: 400
property var model
ListView {
model: root.model
delegate: Text {
text: "Animal: " + type + ", " + size
}
}
}
Window can't be a root item since you use QQuickView.
Create an alias property in the root element with the same name of view.setInitialProperties allow to everything to work
main.cpp
view.setInitialProperties({{"mylistmodel", QVariant::fromValue(&model)}});
view.qml
import QtQuick 2.0
import QtQuick.Window 2.15
Window{
id: root
property alias mylistmodel : listView.model
visible: true
width: 640
height: 480
ListView {
id: listView
anchors.fill: parent
required model
delegate: Text {
required property string type
required property string size
text: "Animal: " + type + ", " + size
}
}
}

Custom model in c ++ doesnt update ListView on QML side

I'm building GUI to control my ADC, connected to the STM32 board. Communications are handled by UART. Everything works fine. The problem is to properly handling my custom channel structure as a model object to be able to present data in different ways depending on the delegate object.
I have tried a few different solutions, none of them works. The case looks trivial however is a little different than presented examples on the forum or in tutorials on the web because I'm trying to update the data model from c++ side, not from qml. That forces me to invoke setData() override method to be invoked on c++ side and propagate dataChange() signal into qml. Let's present some code.
That's how the app presents the data models after initialization. This data are provided in SensorList constructor. No matter what I tried, I can not change those values on qml side. Even when I confirmed using debugger that vector of my channels in sensorList is changing when data are received from sensor. This looks like a problem with communication between C++ and QML.
My data handler.
ADS8588h.h
#ifndef CHANNEL_H
#define CHANNEL_H
#include <QObject>
#include <QString>
class AdsChannel {
Q_GADGET
Q_PROPERTY(QString name READ name )
Q_PROPERTY(double value READ value )
public:
AdsChannel(const QString& channelName, const double value)
: mName{channelName}, mValue{value} {}
AdsChannel() = default;
AdsChannel(const AdsChannel& rhs) = default;
AdsChannel& operator=(const AdsChannel& rhs) = default;
void setName(const QString& name) { mName = name;}
void setValue(const double value) { mValue = value; }
const QString name() const { return mName; }
double value() const { return mValue; }
private:
QString mName;
double mValue;
};
Q_DECLARE_METATYPE(AdsChannel);
#endif // CHANNEL_H
SensorList.h which collects list of channels
#ifndef SENSORLIST_H
#define SENSORLIST_H
#include "AdsChannel.h"
class SensorList : public QObject
{
Q_OBJECT
public:
explicit SensorList(QObject *parent = nullptr)
{
mItems.push_back(AdsChannel("CH1",5.0));
mItems.push_back(AdsChannel("CH2",55.0));
mItems.push_back(AdsChannel("CH3",0.0));
mItems.push_back(AdsChannel("CH4",0.0));
mItems.push_back(AdsChannel("CH5",0.0));
mItems.push_back(AdsChannel("CH6",0.0));
mItems.push_back(AdsChannel("CH7",0.0));
mItems.push_back(AdsChannel("CH8",0.0));
}
QVector<AdsChannel> items() const
{
return mItems;
}
bool setItemAt(int index, const AdsChannel &item)
{
if (index < 0 || index >= mItems.size())
return false;
const AdsChannel &oldItem = mItems.at(index);
if (item.name() == oldItem.name() && item.value() == oldItem.value())
return false;
mItems[index] = item;
return true;
}
private:
QVector<AdsChannel> mItems;
};
#endif // SENSORLIST_H
SensorDataModel.h
#ifndef SENSORDATAMODEL_H
#define SENSORDATAMODEL_H
#include <QAbstractListModel>
#include <QStandardItemModel>
#include "AdsChannel.h"
#include "SensorList.h"
class SensorList;
class SensorDataModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(SensorList *list READ list WRITE setList)
public:
explicit SensorDataModel(QObject *parent = nullptr);
enum {
NameRole = Qt::UserRole +1 ,
ValueRole
};
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = ValueRole) const override;
// Editable:
bool setData(const QModelIndex &index, const QVariant &value,
const int role = ValueRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual QHash<int, QByteArray> roleNames() const override;
SensorList *list() const;
void setList(SensorList *list);
private:
SensorList *mList;
};
#endif // SENSORDATAMODEL_H
SensorDataModel.cpp
#include "SensorDataModel.h"
#include "SensorList.h"
SensorDataModel::SensorDataModel(QObject *parent)
: QAbstractListModel(parent)
, mList(new SensorList)
{
}
int SensorDataModel::rowCount(const QModelIndex &parent) const
{
// For list models only the root node (an invalid parent) should return the list's size. For all
// other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
if (parent.isValid() || !mList)
return 0;
return mList->items().size();
}
QVariant SensorDataModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || !mList)
return QVariant();
const AdsChannel item = mList->items().at(index.row());
switch (role) {
case NameRole:
return QVariant(item.name());
case ValueRole:
return QVariant(item.value());
}
return QVariant();
}
bool SensorDataModel::setData(const QModelIndex &indexModel, const QVariant &value,const int role)
{
// indexModel is empty as this method is invocable from c++ side. Index which is given in dataChange() signal is calculated
// based on index data which are updated
//When data arrived from a sensor this how this method is invoked.
//mData.setData(QModelIndex(),QVariant::fromValue<AdsChannel>(temp),SensorDataModel::ValueRole);
if (!mList)
return false;
auto newElement = value.value<AdsChannel>();
const auto listElements = mList->items();
AdsChannel item;
int indexFinder = 0;
for(indexFinder ; indexFinder < rowCount(); ++indexFinder)
{
if(newElement.name() == listElements.at(indexFinder).name()) {
item = listElements.at(indexFinder);
break;
}
}
switch (role) {
case NameRole:
item.setName(newElement.name());
break;
case ValueRole:
item.setValue(newElement.value());
break;
}
if (mList->setItemAt(indexFinder, item)) {
emit dataChanged (this->index(indexFinder,0), this->index(indexFinder,0), QVector<int>() << role);
//This signal is emited but never reached on QML side
return true;
}
return false;
}
Qt::ItemFlags SensorDataModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable;
}
QHash<int, QByteArray> SensorDataModel::roleNames() const
{
QHash<int, QByteArray> names;
names[NameRole] = "channelName";
names[ValueRole] = "value";
return names;
}
SensorList *SensorDataModel::list() const
{
return mList;
}
void SensorDataModel::setList(SensorList *list)
{
// I never use a setList() as my list is initialized throw constructor.
beginResetModel();
if (mList)
mList->disconnect(this);
mList = list;
endResetModel();
}
main.cpp
#include "ads8588h.h"
#include "SensorList.h"
#include "SensorDataModel.h"
int main(int argc, char *argv[]) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
ADS8588H driver ;
qmlRegisterType<ADS8588H>("AdsDriver", 1, 0, "AdsDriver"); // Used to some other features
qmlRegisterType<SensorDataModel>("SensorModel", 1, 0, "SensorDataModel");
qmlRegisterUncreatableType<SensorList>("SensorModel", 1, 0, "SensorList",
QStringLiteral("SensorList should not be created in QML"));
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("sensorList"), driver.getSensor()->list());
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, &app,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
QML code using simple TEXT label to eliminate some potential bug using CANVAS, requestPaint() signals and other.
import QtQuick 2.0
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3
import SensorModel 1.0
Item {
id:root
anchors.fill: parent
ListView {
implicitWidth: root.width * 0.8
implicitHeight: width
x:root.width/2 - width/2
y:170
id: customList
focus: true
spacing: 4
snapMode: ListView.SnapOneItem
highlightRangeMode: ListView.StrictlyEnforceRange
highlightMoveDuration: 250
orientation: ListView.Vertical
boundsBehavior: Flickable.StopAtBounds
model: SensorDataModel{
list: sensorList
onDataChanged: console.log("ss") //<-- This signal never appear
}
delegate: Item{
width: parent.width
height: width * 0.2
// ChannelProgressBar{
// id:licznik
// anchors.fill: parent
// labelSuffix: suffix
// labelChannel: model.channelName
// progressValue: model.value
// }
Row{
spacing: 20
Text{
id:label1
font.pixelSize: 40
text: model.channelName
}
Text{
id:label2
font.pixelSize: 40
text: model.value
}
}
}
}
}
So my question is What im doing wrong that qml did not updated data even when dataChanged() signal is emitted.

QT QmlMap PolyLine not updated properly when underlying model changes

I'm trying to connect several GeoCoordinates with a polyline in my map. The Coordinates are stored in an model (QAbstractListModel) class, where I can modify (remove and add) the Coords in C++.
Simply displaying markers for every coordinate works fine, but when I connect them with a Polyline and delete some existing coordinates from my model, the Polyline does not get updated.
map.qml
import QtQuick 2.0
import QtLocation 5.6
import QtPositioning 5.6
Item {
width: 512
height: 512
visible: true
id: mainWindow
Map { //our map
anchors.fill: parent
id: map
plugin: mapPlugin
zoomLevel: 14
focus: true
Plugin {
id: mapPlugin
name: "osm"
}
MapItemView {
model: markerModel
delegate: mapcomponent
}
MapItemView {
model: markerModel
delegate: MapPolyline {
antialiasing: true
line.color: "darkBlue"
line.width: 3
path: pathRole
}
}
Component {
id: mapcomponent
MapQuickItem{
id: marker
coordinate: positionRole
sourceItem: Image{
id: markerimage
width: 20
height: 30
source: "qrc:/marker.png"
}
anchorPoint.x: markerimage.width / 2
anchorPoint.y: markerimage.height
}
}
MouseArea {
anchors.fill: parent
onClicked: {
var coord = parent.toCoordinate(Qt.point(mouse.x,mouse.y))
markerModel.addMarker(coord)
}
}
}
}
markermodel.h which stores the coordinates
class MarkerModel : public QAbstractListModel {
Q_OBJECT
public:
using QAbstractListModel::QAbstractListModel;
enum MarkerRoles{positionRole = Qt::UserRole, pathRole};
Q_INVOKABLE void addMarker(const QGeoCoordinate &coordinate) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_coordinates.append(coordinate);
endInsertRows();
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
Q_UNUSED(parent)
return m_coordinates.count();
}
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
if(row + count > m_coordinates.count() || row < 0)
return false;
beginRemoveRows(parent, row, row+count-1);
for(int i = 0; i < count; ++i)
m_coordinates.removeAt(row + i);
endRemoveRows();
return true;
}
bool removeRow(int row, const QModelIndex &parent = QModelIndex()) {
return removeRows(row, 1, parent);
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (index.row() < 0 || index.row() >= m_coordinates.count())
return QVariant();
if(role == Qt::DisplayRole)
return QVariant::fromValue(index.row());
if(role == MarkerModel::positionRole)
return QVariant::fromValue(m_coordinates[index.row()]);
if(role == MarkerModel::pathRole) {
QVariantList coordlist;
for(auto &geocoord : m_coordinates)
coordlist << QVariant::fromValue(geocoord);
return coordlist;
}
return QVariant();
}
QHash<int, QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[positionRole] = "positionRole";
roles[pathRole] = "pathRole";
return roles;
}
private:
QList<QGeoCoordinate> m_coordinates;
};
A gif showing my problem:
Did I miss something when deleting rows in my model? Is there a way to force qml to render the polyline with completly new data from the model?
Having a pathRole doesn't make much sense here. This role is the same for all the rows of your model.
You display n MapPolyline where n is the number of coordinates, you only need one for your entire model.
I would advise you to remove the pathRole and just expose a path property in your model, and emit its notify signal in addMarker and removeRows.
As they indicate your code has a logic error, in my answer I will show the implementation since #GrecKo already explained the correct logic:
markermodel.h
#ifndef MARKERMODEL_H
#define MARKERMODEL_H
#include <QAbstractListModel>
#include <QGeoCoordinate>
class MarkerModel : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(QVariantList path READ path NOTIFY pathChanged)
public:
enum MarkerRoles{positionRole = Qt::UserRole, pathRole};
MarkerModel(QObject *parent=nullptr): QAbstractListModel(parent)
{
connect(this, &QAbstractListModel::rowsInserted, this, &MarkerModel::pathChanged);
connect(this, &QAbstractListModel::rowsRemoved, this, &MarkerModel::pathChanged);
connect(this, &QAbstractListModel::dataChanged, this, &MarkerModel::pathChanged);
connect(this, &QAbstractListModel::modelReset, this, &MarkerModel::pathChanged);
connect(this, &QAbstractListModel::rowsMoved, this, &MarkerModel::pathChanged);
}
Q_INVOKABLE void addMarker(const QGeoCoordinate &coordinate) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_coordinates.append(coordinate);
endInsertRows();
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
if(parent.isValid()) return 0;
return m_coordinates.count();
}
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
if(row + count > m_coordinates.count() || row < 0)
return false;
beginRemoveRows(parent, row, row+count-1);
for(int i = 0; i < count; ++i)
m_coordinates.removeAt(row + i);
endRemoveRows();
return true;
}
bool removeRow(int row, const QModelIndex &parent = QModelIndex()) {
return removeRows(row, 1, parent);
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (index.row() < 0 || index.row() >= m_coordinates.count())
return QVariant();
if(role == Qt::DisplayRole)
return QVariant::fromValue(index.row());
else if(role == MarkerModel::positionRole)
return QVariant::fromValue(m_coordinates[index.row()]);
return QVariant();
}
QHash<int, QByteArray> roleNames() const override{
QHash<int, QByteArray> roles;
roles[positionRole] = "positionRole";
return roles;
}
QVariantList path() const{
QVariantList path;
for(const QGeoCoordinate & coord: m_coordinates){
path << QVariant::fromValue(coord);
}
return path;
}
signals:
void pathChanged();
private:
QList<QGeoCoordinate> m_coordinates;
};
#endif // MARKERMODEL_H
*.qml
import QtQuick 2.0
import QtLocation 5.6
import QtPositioning 5.6
Item {
width: 512
height: 512
visible: true
id: mainWindow
Map { //our map
anchors.fill: parent
id: map
plugin: mapPlugin
zoomLevel: 14
focus: true
Plugin {
id: mapPlugin
name: "osm"
}
MapItemView {
model: markerModel
delegate: mapcomponent
}
MapPolyline{
antialiasing: true
line.color: "darkBlue"
line.width: 3
path: markerModel.path
}
Component {
id: mapcomponent
MapQuickItem{
id: marker
coordinate: positionRole
sourceItem: Image{
id: markerimage
width: 20
height: 30
source: "qrc:/marker.png"
}
anchorPoint.x: markerimage.width / 2
anchorPoint.y: markerimage.height
}
}
MouseArea {
anchors.fill: parent
onClicked: {
var coord = parent.toCoordinate(Qt.point(mouse.x,mouse.y))
markerModel.addMarker(coord)
}
}
}
}

TableView and QAbstracTableModel when calls QQmlApplicationEngine from another class

I am trying to make the model QAbstractTableModel in cpp and connect to qml.
This code works well.
MyModel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractTableModel>
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
explicit MyModel(QObject *parent = nullptr);
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Animal> m_animals;
};
#endif // MYMODEL_H
MyModel.cpp
#include "MyModel.h"
#include <QDebug>
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent)
{
qDebug() << __FUNCTION__;
addAnimal(Animal("Wolf", "Medium"));
addAnimal(Animal("Polar bear", "Large"));
addAnimal(Animal("Quoll", "Small"));
}
int MyModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_animals.size();
}
int MyModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 2;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
qDebug() << __FUNCTION__ << index.row() << index.column() << role;
if (!index.isValid())
return QVariant();
const Animal &animal = m_animals[index.row()];
switch (role) {
case TypeRole:
return animal.type();
case SizeRole:
return animal.size();
default:
break;
}
return QVariant();
}
void MyModel::addAnimal(const Animal &animal)
{
qDebug() << __FUNCTION__;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_animals << animal;
endInsertRows();
}
QHash<int, QByteArray> MyModel::roleNames() const
{
qDebug() << __FUNCTION__;
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MyModel model;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main_test.qml
import QtQuick 2.0
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: main_view
width: 250
height: 600
visible: true
ListView {
id: list_view
width: 200; height: 250
model: myModel
delegate: Text { text: "Animal Test: " + type + ", " + size }
}
TableView {
id: table_view
objectName: "tableView"
width: 250; height: 250
anchors.top: list_view.bottom
model: myModel
TableViewColumn {
id: type_col
role: "type"
title: "Type"
width: 100
}
TableViewColumn {
id: size_col
role: "size"
title: "Size"
width: 100
}
}
}
It looks like this
But, if I change the main.cpp a little bit, the list view dispaly as normal, but not the table view.
main.cpp
#include "MainView.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MainView mainView;
return app.exec();
}
MainView.h
#ifndef MAINVIEW_H
#define MAINVIEW_H
#include <QObject>
#include <QQmlApplicationEngine>
class MainView: public QObject
{
Q_OBJECT
public:
explicit MainView(QObject *parent=nullptr);
void initializeView();
private:
QQmlApplicationEngine m_engine;
};
#endif // MAINVIEW_H
MainView.cpp
#include "MainView.h"
#include "MyModel.h"
#include <QQmlContext>
MainView::MainView(QObject *parent)
: QObject(parent)
{
initializeView();
}
void MainView::initializeView()
{
MyModel model;
m_engine.rootContext()->setContextProperty("myModel", &model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
It looks like this.
I really don't understand why this happens. What the difference between ListView and TableView in the second case? And how to fix it, i.e. make data display in the second case? Thank in advance.
The problem is in initializeView, model is a local variable in that scope, and every local variable is deleted from the memory when the function finishes executing. In the first case model will be eliminated when the application is closed, in the second case it will be eliminated when initializeView ends that is when the window is displayed, there are 2 possible solutions:
Create a pointer and to manage the memory we pass to MainView as a parent so that he removes it from memory (this last is a feature of the QObjects):
void MainView::initializeView()
{
MyModel *model = new MyModel(this);
m_engine.rootContext()->setContextProperty("myModel", model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
Make the model member of the class.
*.h
#ifndef MAINVIEW_H
#define MAINVIEW_H
#include <QObject>
#include <QQmlApplicationEngine>
class MainView: public QObject
{
Q_OBJECT
public:
explicit MainView(QObject *parent=nullptr);
void initializeView();
private:
QQmlApplicationEngine m_engine;
MyModel model{this};
};
#endif // MAINVIEW_H
*.cpp
...
void MainView::initializeView()
{
m_engine.rootContext()->setContextProperty("myModel", &model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
To understand the behavior I have added more points of impression:
MyModel::~MyModel()
{
qDebug()<<"destructor";
}
TableView {
...
Component.onCompleted: {
console.log("completed table")
if(!timer.running)
timer.running = true
}
}
ListView {
...
Component.onCompleted: {
console.log("completed list")
if(!timer.running)
timer.running = true
}
}
Timer {
id: timer
interval: 0;
onTriggered: console.log(myModel, table_view.model, list_view.model)
}
And I get the following:
MyModel
addAnimal
addAnimal
addAnimal
roleNames
data 0 0 257
data 0 0 258
data 1 0 257
data 1 0 258
data 2 0 257
data 2 0 258
roleNames
qml: completed list
data 0 0 257
data 0 0 258
qml: completed table
destructor
qml: null null null
We note that ListView manages to load the data, whereas TableView in the middle of the load is called the destructor.
Possible explanation: I think that the ListView stores the data making a copy of them and only updates when the model notifies, should also be notified when the model is deleted to clean the data, it seems that this is a bug. On the other hand, TableView, being at the moment of loading and deleting the model, is null, so it is notified and cleans the data.
Doing another test:
void MainView::initializeView()
{
MyModel *model = new MyModel;
m_engine.rootContext()->setContextProperty("myModel", model);
m_engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QTimer::singleShot(1000, model, &MyModel::deleteLater);
}
It is observed that both load the data correctly, and after a second the model is destroyed, but the one that is only notified is the TableView since it is the only one that cleans the data shown, and ListView does not.
my conclusion that it is a ListView bug.

Create Model for QML TreeView

I'm trying to use QML TreeView Model. The example from Qt doesn't include how to create the model. I read this post and tried to use the code from #Tarod but the result is not what I expected.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "animalmodel.h"
#include <qqmlcontext.h>
#include <qqml.h>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
AnimalModel model;
model.addAnimal("wolf", "Medium");
model.addAnimal("Bear", "Large");
QQmlApplicationEngine engine;
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
animalmodel.h
#ifndef ANIMALMODEL_H
#define ANIMALMODEL_H
#include <QStandardItemModel>
class AnimalModel : public QStandardItemModel
{
Q_OBJECT //The Q_OBJECT macro must appear in the private section of a class definition that declares its own signals and slots or that uses other services provided by Qt's meta-object system.
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
Q_INVOKABLE void addAnimal(const QString &type, const QString &size);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
};
#endif // ANIMALMODEL_H
animalmodel.cpp
#include "animalmodel.h"
AnimalModel::AnimalModel(QObject *parent)
: QStandardItemModel(parent)
{
}
void AnimalModel::addAnimal(const QString &type, const QString &size)
{
QStandardItem* entry = new QStandardItem();
entry->setData(type, TypeRole);
auto childEntry = new QStandardItem();
childEntry->setData(size, SizeRole);
entry->appendRow(childEntry);
appendRow(entry);
}
QVariant AnimalModel::data(const QModelIndex & index, int role) const {
QStandardItem *myItem = itemFromIndex(index);
if (role == TypeRole)
return myItem->data(TypeRole);
else if (role == SizeRole) {
if (myItem->child(0) != 0)
{
return myItem->child(0)->data(SizeRole);
}
}
return QVariant();
}
QHash<int, QByteArray> AnimalModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
menuBar: MenuBar {
Menu {
title: qsTr("&File")
MenuItem {
text: qsTr("&Open")
onTriggered: messageDialog.show(qsTr("Open Action Triggered"));
}
MenuItem {
text: qsTr("&Exit")
onTriggered: Qt.quit();
}
}
}
TreeView {
anchors.fill: parent
model: myModel
TableViewColumn {
title: "Name"
role: "type"
width: 300
}
TableViewColumn {
title: "Size"
role: "size"
width: 300
}
}
}
What I got is something like this:
Result
What I want to have is the animal size as a child of animal type.
Model sub-classing is one of the worst minefields in Qt. The advice is always to have it go through the model test (https://wiki.qt.io/Model_Test) to see if everything was implemented correctly.
On the other hand, in 90% of the cases you do not need to subclass a model at all as the default models provided by Qt work quite well. What I'd do is just use QStandardItemModel using, on the C++ side, only the QAbstractItemModel interface (i.e. force yourself to use QAbstractItemModel* model = new QStandardItemModel(/*parent*/);) this way, if in the future you feel like you really need to reimplement the model (for efficiency) you'll just need to change 1 line in your existing code.
In your case:
void AnimalModel::addAnimal(const QString &type, const QString &size)
{
if(columnCount()==0) insertColumn(0); // make sure there is at least 1 column
insertRow(rowCount()); // add a row to the root
const QModelIndex addedIdx = index(rowCount()-1,0);
setData(addedIdx, type, TypeRole); // set the root data
insertRow(rowCount(addedIdx),addedIdx ); // add 1 row ...
insertColumn(0,addedIdx ); // ... and 1 column to the added root row
setData(index(0,0,addedIdx), size, SizeRole); // set the data to the child
}