I have an QAbstractListModel object that maintains a list of items to show on a map. The position of these items changes every few seconds and it is easy to calculate a pretty accurate position 60 seconds in the future. What I am trying to do is to set the item's position when it gets a new position (that part works well) and then to immediately move the item toward the calculated future position.
The code without animation looks like this and it works fine:
Component {
id: drawTarget
MapQuickItem {
id: marker
coordinate: data.coords
sourceItem: Item {
id: item
...
The data object has a property which returns the estimated position of the item 60 seconds in the future, so I tried this:
Component {
id: drawTarget
MapQuickItem {
id: marker
coordinate: data.coords
CoordinateAnimation {
id:anim
property: "coordinate"
}
onCoordinateChanged: {
anim.stop()
anim.from = data.coords
anim.to = data.coordsIn60sec
anim.duration = 60000
anim.start()
}
sourceItem: Item {
id: item
...
But although the object's position is updated properly at each position update, the animation toward the future estimated position doesn't work at all.
How would one go about doing something like this?
In its code, it makes a binding coordinate: data.coords that states that "coordinate" takes the value of "coords" but at the same time says that "coordinate" depends on the animation, isn't it contradictory? Well, it is contradictory.
The idea is not to do the binding coordinate: data.coords but to update the property only through the animation.
The following code is a workable example:
main.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import QtLocation 5.6
import QtPositioning 5.6
Window {
visible: true
width: 640
height: 480
Plugin {
id: mapPlugin
name: "osm"
}
Map {
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 10
MapItemView{
model: datamodel
delegate: MapQuickItem{
id: item
// begin configuration
property var position: model.position
property var nextposition: model.nextposition
onPositionChanged: restart();
onNextpositionChanged: restart();
function restart(){
anim.stop()
anim.from = position
anim.to = nextposition
anim.start()
}
CoordinateAnimation {
id: anim
target: item
duration: 60 * 1000
property: "coordinate"
}
// end of configuration
anchorPoint.x: rect.width/2
anchorPoint.y: rect.height/2
sourceItem: Rectangle{
id: rect
color: "green"
width: 10
height: 10
}
}
}
}
}
datamodel.h
#ifndef DATAMODEL_H
#define DATAMODEL_H
#include <QAbstractListModel>
#include <QGeoCoordinate>
#include <QTimer>
#include <random>
#include <QDebug>
struct Data{
QGeoCoordinate position;
QGeoCoordinate nextposition;
};
static QGeoCoordinate osloposition(59.91, 10.75); // Oslo;
class DataModel : public QAbstractListModel
{
Q_OBJECT
QList<Data> m_datas;
public:
enum PositionRoles {
PositionRole = Qt::UserRole + 1,
NextPositionRole
};
explicit DataModel(QObject *parent = nullptr)
: QAbstractListModel(parent)
{
init();
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override{
return parent.isValid() ? 0: m_datas.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override{
if (!index.isValid() || index.row() < 0 || index.row() >= m_datas.count())
return QVariant();
const Data &data = m_datas[index.row()];
if (role == PositionRole)
return QVariant::fromValue(data.position);
else if (role == NextPositionRole)
return QVariant::fromValue(data.nextposition);
return QVariant();
}
QHash<int, QByteArray> roleNames() const override{
QHash<int, QByteArray> roles;
roles[PositionRole] = "position";
roles[NextPositionRole] = "nextposition";
return roles;
}
private:
void init(){
for(int i=0; i< 10; ++i){
Data data;
data.position = osloposition;;
data.nextposition = data.position;
m_datas << data;
}
QTimer *timer = new QTimer(this);
QObject::connect(timer, &QTimer::timeout, this, &DataModel::updateData);
timer->start(60 * 1000);
updateData();
}
void updateData(){
qDebug() << __PRETTY_FUNCTION__;
static std::default_random_engine e;
static std::uniform_real_distribution<> dis(-.1, .1);
for(int i=0; i < m_datas.count(); ++i){
Data & data = m_datas[i];
QModelIndex ix = index(i);
data.position = data.nextposition;
data.nextposition = QGeoCoordinate(osloposition.latitude() + dis(e),
osloposition.longitude() + dis(e));
Q_EMIT dataChanged(ix, ix, {PositionRole, NextPositionRole});
}
}
};
#endif // DATAMODEL_H
In the following link is the complete example
Related
I have a database with the coordinates of the airports and I need to display them with points on the map (QtLocation).
With the QSqlQueryModel I can easily populate and show a TableView, but I have no idea how to create MapQuickItems.
class SqlModel : public QSqlQueryModel
{
Q_OBJECT
public:
enum Roles {
LatitudeRole = Qt::UserRole + 1,
LongitudeRole = Qt::UserRole + 2
};
explicit SqlModel(QObject *parent = nullptr) : QSqlQueryModel(parent) {}
QVariant data(const QModelIndex &index, int role) const override
{
int columnId = role - Qt::UserRole - 1;
QModelIndex modelIndex = this->index(index.row(), columnId);
return QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
}
protected:
QHash<int, QByteArray> roleNames() const override {
QHash<int, QByteArray> roles;
roles[LatitudeRole] = "latitude";
roles[LongitudeRole] = "longitude";
return roles;
}
};
in main.cpp:
//...
SqlModel *model = new SqlModel;
model->setQuery("SELECT air_latitude, air_longitude FROM tab_airports");
engine.rootContext()->setContextProperty("myModel", model);
//...
Using the SqlQueryModel of my previous answer that allows to obtain the data through the roles that have the same name of the fields and transforming it to QCoordinate using QtPositioning.coordinate in a MapItemView that is delegated to the MapQuickItem the following is obtained:
#include <QtGui>
#include <QtSql>
#include <QtQml>
class SqlQueryModel : public QSqlQueryModel {
Q_OBJECT
public:
using QSqlQueryModel::QSqlQueryModel;
QHash<int, QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
for (int i = 0; i < record().count(); i++) {
roles.insert(Qt::UserRole + i + 1, record().fieldName(i).toUtf8());
}
return roles;
}
QVariant data(const QModelIndex &index, int role) const {
QVariant value;
if (index.isValid()) {
if (role < Qt::UserRole) {
value = QSqlQueryModel::data(index, role);
} else {
int columnIdx = role - Qt::UserRole - 1;
QModelIndex modelIndex = this->index(index.row(), columnIdx);
value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
}
}
return value;
}
};
static bool createConnection(const QString &path) {
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(path);
if (!db.open()) {
qDebug() << "Cannot open database\n"
"Unable to establish a database connection.\n"
"This example needs SQLite support. Please read "
"the Qt SQL driver documentation for information how "
"to build it.\n\n"
"Click Cancel to exit.";
return false;
}
return true;
}
static void createData(){
QSqlQuery query;
query.exec("CREATE TABLE IF NOT EXISTS tab_airports(air_latitude REAL, air_longitude REAL)");
for(int i=0; i<10; i++){
query.prepare("INSERT INTO tab_airports(air_latitude, air_longitude) VALUES (?, ?)");
double lat = 59.91 + .02 * (QRandomGenerator::global()->generateDouble() - .5);
double lng = 10.75 + .02 * (QRandomGenerator::global()->generateDouble() - .5);
query.addBindValue(lat);
query.addBindValue(lng);
query.exec();
}
}
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
if (!createConnection(":memory:"))
return -1;
createData();
SqlQueryModel model;
model.setQuery("SELECT air_latitude, air_longitude FROM tab_airports");
engine.rootContext()->setContextProperty("airport_model", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtLocation 5.11
import QtPositioning 5.11
ApplicationWindow {
id: root
visible: true
width: 640
height: 480
Plugin {
id: mapPlugin
name: "osm"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MapItemView{
id: view
model: airport_model
delegate: MapQuickItem{
coordinate: QtPositioning.coordinate(model.air_latitude, model.air_longitude)
anchorPoint.x: image.width/2
anchorPoint.y: image.height
sourceItem: Image {
id: image
source: "http://maps.gstatic.com/mapfiles/ridefinder-images/mm_20_red.png"
}
}
}
}
}
I have a task of showing big amount of data in realtime (as close to that as possible) with UI update every 10 to 100ms (best to worst values), I've managed to create a test model generating random numbers and populating the table view with new set of random values with a Timer. I tried to set different time intervals from 1 to 100 ms, and I can see the timer fires and the new set of data is created, but the UI updates strictly in 1 second every time despite the timer interval value.
Can you guide me on how to handle UI updates in less than 1 second.
I tried different amount of data from tables of 50x1000 to just 50x50. Every time I get UI update rate of 1 second.
It is based on the "Game Of Life" Qt example, so some of the elements are just not used and are obsolete, but as soon as they are "disabled" I think they don't make any influence on the rest of the code and the problem itself.
You can see that in nextStep() method I invoke timestamp logging to console and I can see in the output that the method is invoked according to the timer, but the UI only updates visually every second.
main.qml
ApplicationWindow {
id: root
visible: true
width: 760
height: 810
minimumWidth: 475
minimumHeight: 300
color: "#09102B"
title: qsTr("Conway’s Game of Life")
//! [tableview]
TableView {
id: tableView
anchors.fill: parent
rowSpacing: 1
columnSpacing: 1
ScrollBar.horizontal: ScrollBar {}
ScrollBar.vertical: ScrollBar {}
delegate: Rectangle {
id: cell
implicitWidth: 45
implicitHeight: 15
color: model.value > 100 ? "#f3f3f4" : "#b5b7bf"
Label {
width: parent.width
height: parent.height
text: model.value
}
}
//! [tableview]
//! [model]
model: GameOfLifeModel {
id: gameOfLifeModel
}
//! [model]
//! [scroll]
contentX: 0;
contentY: 0;
//! [scroll]
}
footer: Rectangle {
signal nextStep
id: footer
height: 50
color: "#F3F3F4"
RowLayout {
anchors.centerIn: parent
//! [next]
Button {
text: qsTr("Next")
onClicked: gameOfLifeModel.nextStep()
}
//! [next]
Item {
width: 50
}
Button {
text: timer.running ? "❙❙" : "▶️"
onClicked: timer.running = !timer.running
}
}
FpsItem {
id: fpsItem
anchors.left: parent
color: "black"
}
Timer {
id: timer
interval: 10
running: true
repeat: true
onTriggered: gameOfLifeModel.nextStep()
}
}
}
gameoflifemodel.cpp
GameOfLifeModel::GameOfLifeModel(QObject *parent)
: QAbstractTableModel(parent) {}
//! [modelsize]
int GameOfLifeModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return height;
}
int GameOfLifeModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return width;
}
//! [modelsize]
//! [read]
QVariant GameOfLifeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || role != CellRole)
return QVariant();
return m_state[index.column()][index.row()];
}
//! [read]
//! [write / not used]
bool GameOfLifeModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (role != CellRole || data(index, role) == value)
return false;
m_state[index.column()][index.row()] = value.toBool();
emit dataChanged(index, index, {role});
return true;
}
//! [write]
Qt::ItemFlags GameOfLifeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable;
}
//! [update]
void GameOfLifeModel::nextStep()
{
srand(time(NULL));
qDebug() << QTime::currentTime().toString("yyyy/MM/dd hh:mm:ss,zzz");
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
m_state[j][i] = (rand() % 1000) + 1;
}
}
emit dataChanged(index(0, 0), index(height - 1, width - 1), {CellRole});
}
//! [update]
gameoflifemodel.h
//! [modelclass]
class GameOfLifeModel : public QAbstractTableModel
{
Q_OBJECT
Q_ENUMS(Roles)
public:
enum Roles {
CellRole
};
QHash<int, QByteArray> roleNames() const override {
return {
{ CellRole, "value" }
};
}
explicit GameOfLifeModel(QObject *parent = nullptr);
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;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
Q_INVOKABLE void nextStep();
private:
static constexpr int width = 50;
static constexpr int height = 50;
static constexpr int size = width * height;
template <class T, size_t ROW, size_t COL>
using NativeMatrix = T[ROW][COL];
NativeMatrix<int, height, width> m_state;
};
//! [modelclass]
Okay, the problem is solved. The issue appeared to be in the random number generation process, not in the table view.
In the code I used
srand(time(NULL))
The time(NULL) returns seconds and not milliseconds, thus I was getting the same numbers and they were updated only after 1 second and visually it was similar to updating the UI only every second. In order to solve this I switched to using this to generate random numbers and everything worked well:
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
/* using nano-seconds instead of seconds */
srand((time_t)ts.tv_nsec);
I have a model that have a list of MarkerItem (which is a struct).
struct MarkerItem{
enum marker_state{
marker_observation = 0,
marker_important,
marker_redundant,
marker_deleted
};
MarkerItem(const QPointF& pos, marker_state state, const QDateTime& when, const QString& label);
const QPointF& position() const;
QGeoCoordinate coordinate() const;
const QString& label() const;
marker_state state() const;
void change_state(marker_state state);
private:
QPointF _position;
marker_state _state;
QString _label;
QDateTime _when;
};
class MarkerModel : public QAbstractListModel{
Q_OBJECT
Q_PROPERTY(QGeoRoute* route READ route NOTIFY routeChanged)
public:
enum MarkerRoles {
PositionRole = Qt::UserRole + 1,
StateRole,
LabelRole
};
explicit MarkerModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
public:
QHash<int, QByteArray> roleNames() const;
private:
QList<MarkerItem*> _markers;
public:
void addMarker(MarkerItem* marker);
public:
QGeoRoute* route() const;
signals:
void routeChanged();
};
void MarkerModel::addMarker(MarkerItem *marker){
beginInsertRows(QModelIndex(), rowCount(), rowCount());
_markers.push_back(marker);
qWarning() << rowCount();
endInsertRows();
}
In my QML I have
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(22.5726, 88.3639)
zoomLevel: 14
MapItemView {
model: markerModel
delegate: markerDelegate
}
Component {
id: markerDelegate
MapQuickItem{
anchorPoint: Qt.point(2.5, 2.5)
coordinate: QtPositioning.coordinate(position.x, position.y)
zoomLevel: 0
sourceItem: Rectangle{
width: settings.marker_size;
height: settings.marker_size;
radius: settings.marker_size/2;
color: settings.marker_colors[status]
border.color: "white"
border.width: 1
}
}
}
Component{
id: polyline
MapPolyline {
line.color: black
line.width: 2
path: []
}
}
}
I am passing this model to QML view
_model->addMarker(new MarkerItem(QPointF(22.5868f, 88.4149f), MarkerItem::marker_observation, QDateTime::currentDateTime(), "1"));
_model->addMarker(new MarkerItem(QPointF(22.5391f, 88.3958f), MarkerItem::marker_observation, QDateTime::currentDateTime(), "2"));
qRegisterMetaType<MarkerModel*>("MarkerModel");
QWidget* container = QWidget::createWindowContainer(_view, this);
container->setFocusPolicy(Qt::TabFocus);
_view->engine()->rootContext()->setContextProperty("markerModel", _model);
_view->setSource(QUrl("qrc:///main.qml"));
QVBoxLayout* layout = new QVBoxLayout;
setLayout(layout);
layout->addWidget(container);
_root = _view->rootObject();
The first two points that are added before setting the model to QML context appears in the view. However when I am adding some new points (based on user input from Toolbar Action) with the addMarker function It adds the markers in the model, but view does not update
All the codes in the project is uploaded on the gist
The problem can be said that it is in your code or is in your data, what happens is that the data you are giving back is in the geographic coordinates.
If you want to keep your data you only have to change the following:
coordinate: QtPositioning.coordinate(position.y, position.x)
If you do not want to change your code, exchange the coordinates in your data.
I chose the first option and moving the map a bit I got the following:
I am using the QML MapItemView component with a C++ QAbstractListModel-based model. The MapItemView is working fine when the model is reset, or whenever a new item is added or an existing item is removed. However, the MapItemView is not reflecting changes to already added items.
I have first experienced this issue with Qt 5.4 but I still face it after updating to Qt 5.5
The following example shows the issue with 2 different models : a C++ model based on QAbstractListModel and a QML ListModel.
It is possible to switch from one model to another, pressing the top-right button:
When the QML model is used, clicking in the map will add a new element and modify the first element.
The C++ model used a QTimer to modify its content every seconds.
The MapItemView is not showing the model changes whatever the model type is. When switching from one model to another, one can see that the MapView gets updated.
I am probably missing something very obvious but I don't see what it is. Thank you in advance for your help.
The main.cpp code :
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "playermodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
PlayerModel playerModel;
engine.rootContext()->setContextProperty("playerModel", &playerModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
The C++ model header (playermodel.h) :
#ifndef PLAYERMODEL_H
#define PLAYERMODEL_H
#include <QObject>
#include <QAbstractListModel>
#include <QGeoPositionInfoSource>
#include <QTimer>
#include <QDebug>
struct PlayerData
{
PlayerData(){ }
PlayerData(int _Azimuth, double lat, double lng){
Azimuth = _Azimuth;
Latitude = lat;
Longitude = lng;
}
int Azimuth = -1;
double Latitude = 0.;
double Longitude = 0.;
QVariant getRole(int role) const;
enum Roles{
RoleAzimuth = Qt::UserRole + 1,
RoleLatitude,
RoleLongitude
};
};
class PlayerModel : public QAbstractListModel
{
Q_OBJECT
public:
PlayerModel();
~PlayerModel();
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const;
Q_INVOKABLE Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
virtual bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole);
bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex());
void resetModel();
void updateModel();
public slots:
void testUpdateModel();
protected:
QHash<int, QByteArray> roleNames() const;
private:
QTimer m_timer;
QVector< PlayerData> m_lstValues ;
};
#endif // PLAYERMODEL_H
The C++ model (playermodel.cpp)
#include "playermodel.h"
QVariant PlayerData::getRole(int role) const
{
switch (role)
{
case Roles::RoleAzimuth:
return Azimuth;
case Roles::RoleLatitude:
return Latitude;
case Roles::RoleLongitude:
return Longitude;
default:
return QVariant();
}
}
PlayerModel::PlayerModel()
{
resetModel();
connect(&m_timer, SIGNAL(timeout()), this, SLOT(testUpdateModel()));
m_timer.start(1000);
}
PlayerModel::~PlayerModel()
{
}
void PlayerModel::testUpdateModel()
{
updateModel();
}
int PlayerModel::rowCount(const QModelIndex & parent) const
{
Q_UNUSED(parent);
return m_lstValues.size();
}
QVariant PlayerModel::data( const QModelIndex & index, int role ) const
{
if ( (index.row() < 0) || (index.row() >= rowCount()) )
return QVariant();
return m_lstValues[ index.row()].getRole( role);
}
void PlayerModel::resetModel()
{
qDebug() << "Reset players model";
beginResetModel();
m_lstValues.clear();
//populate with dummy value
m_lstValues.push_back( PlayerData( 10, 47.1, -1.6 ));
m_lstValues.push_back( PlayerData( 20, 47.2, -1.6 ));
m_lstValues.push_back( PlayerData( 30, 47.1, -1.5 ));
m_lstValues.push_back( PlayerData( 40, 47.2, -1.5 ));
endResetModel();
}
void PlayerModel::updateModel()
{
qDebug() << "update players model upon timeout";
//change the Azimuth of every model items
int row = 0;
for (PlayerData player : m_lstValues)
{
setData( index(row), (player.Azimuth + 1) % 360, PlayerData::RoleAzimuth);
row++;
}
//qDebug() << "First element azimuth is now : " << data( index(0),PlayerData::RoleAzimuth).toInt() << "°";
}
bool PlayerModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
if ( (index.row() < 0) || (index.row() >= rowCount()) ) return false;
PlayerData& player = m_lstValues[ index.row() ];
switch (role)
{
case PlayerData::RoleAzimuth:
player.Azimuth = value.toInt();
break;
case PlayerData::RoleLatitude:
player.Latitude = value.toDouble();
break;
case PlayerData::RoleLongitude:
player.Longitude = value.toDouble();
break;
}
emit dataChanged(index, index );//, QVector<int>( role));
return true;
}
bool PlayerModel::removeRows(int row, int count, const QModelIndex & parent)
{
Q_UNUSED(count);
Q_UNUSED(parent);
beginRemoveRows(QModelIndex(), row, row);
m_lstValues.remove( row);
endRemoveRows();
return true;
}
QHash<int, QByteArray> PlayerModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[PlayerData::Roles::RoleAzimuth] = "Azimuth";
roles[PlayerData::Roles::RoleLatitude] = "Latitude";
roles[PlayerData::Roles::RoleLongitude] = "Longitude";
return roles;
}
Qt::ItemFlags PlayerModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}
and finally the QML file :
import QtQuick 2.4
import QtQuick.Window 2.2
import QtLocation 5.3
import QtPositioning 5.0
Window {
id:mainWnd
visible: true
width : 1024
height:768
property bool useQMLModel: true
Map {
id: map
anchors.fill: parent
anchors.margins: 50
plugin: Plugin{ name:"osm";}
center: QtPositioning.coordinate(47.1, -1.6)
zoomLevel: map.maximumZoomLevel
MapItemView{
id:mapItemView
model: mainWnd.useQMLModel ? qmlModel : playerModel
delegate: MapQuickItem {
//anchorPoint:
id:delegateMQI
rotation: model.Azimuth
sourceItem: Rectangle{
id:defaultDelegate
width:32
height:32
radius:16
opacity: 0.6
rotation:Azimuth
color:"blue"
Text{
text: Azimuth
anchors.centerIn : parent
}
}
coordinate: QtPositioning.coordinate(Latitude,Longitude)
}
}
MouseArea{
anchors.fill: parent
enabled : useQMLModel
//preventStealing: true
propagateComposedEvents: true
onClicked:
{
//Modify an item
var newAzim = Math.random()*360;
qmlModel.setProperty(0, "Azimuth", newAzim);
//Check modification
console.log("Azim:" + qmlModel.get(0).Azimuth );
qmlModel.setProperty(0, "Color", "blue");
//add a new item
qmlModel.append({"Latitude": 47.05 + Math.random() *0.2, "Longitude":-1.75 + Math.random() *0.3, "Azimuth":0, "Color":"red"})
console.log("Nb item:" + qmlModel.count );
map.update();
map.fitViewportToMapItems();
mouse.accepted = false
}
}
}
Connections{
target:mapItemView.model
onDataChanged:{
if (useQMLModel)
console.log("dataChanged signal Azim:" + qmlModel.get(0).Azimuth );
else
console.log("dataChanged signal Azim:" + playerModel.data( topLeft, 0x0101) );
}
}
ListModel{
id:qmlModel
ListElement {
Latitude: 47.1
Longitude: -1.6
Azimuth: 10.0
}
}
Rectangle{
anchors.top : parent.top
anchors.left : parent.left
width : 400
height : 300
radius: 10
color:"grey"
ListView{
id:lstView
model:mapItemView.model
anchors.fill:parent
delegate: Text{
width:parent.width
height:50
verticalAlignment: TextInput.AlignVCenter
fontSizeMode : Text.Fit
font.pixelSize: 42
minimumPixelSize: 5
text: "Latitude : " + Latitude + " - Longitude :" + Longitude + " - Azimuth : " + Azimuth
}
}
}
Rectangle{
anchors.right : parent.right
anchors.top : parent.top
radius : 10
color : "red"
width : 200
height : 50
Text{
anchors.centerIn: parent
text:"switch model"
}
MouseArea{
anchors.fill: parent
onClicked:{
mainWnd.useQMLModel = !mainWnd.useQMLModel;
}
}
}
}
Just in case that someone faces the same issue reported by the post's author, the problem was solved in Qt 5.6.0
Note that this is fixed by changeset Ib92252d18c2229bc6d43e11362b8f13cdb48f315 (https://codereview.qt-project.org/#/c/123660/ ) already merged in the 5.6 branch
I have a custom model which derives from QAbstractListModel which is exposed to QML. I need to support operations to add new items and remove existing items. While insertion operation works without any problems, removal operation causes the application to crash while calling endRemoveRows() function.
void GPageModel::addNewPage()
{
if(m_pageList.count()<9)
{
beginInsertRows(QModelIndex(),rowCount(),rowCount());
GPage * page = new GPage();
QQmlEngine::setObjectOwnership(page,QQmlEngine::CppOwnership);
page->setParent(this);
page->setNumber(m_pageList.count());
page->setName("Page " + QString::number(m_pageList.count()+1));
m_pageList.append(page);
endInsertRows();
}
}
void GPageModel::removePage(const int index)
{
if(index>=0 && index<m_pageList.count())
{
beginRemoveRows(QModelIndex(),index,index);
qDebug()<<QString("beginRemoveRows(QModelIndex(),%1,%1)").arg(index);
GPage * page = m_pageList.at(index);
m_pageList.removeAt(index);
delete page;
endRemoveRows();
}
}
The class GPage derives from QObject. I am struck trying to figure out what is causing the app to crash while trying to call endRemoveRows(). I get "ASSERT failure in QList::at: "index out of range"" when endRemoveRows() is called.How do I remove the rows from a QAbstracListModel? Is there any other way?
I am using Qt 5.1.0 on a Windows 7 64 bit machine.
The code below works fine for me. Your problem is probably elsewhere. This is for Qt 5 due to use of Qt Quick Controls.
There are two views accessing the same model, this visually confirms that the model emits proper signals to inform the views of the changes. The page additions and removals are done via the standard insertRows and removeRows methods, exported through Q_INVOKABLE. There's no need for any custom methods on this model, so far. The Q_INVOKABLE is a workaround for some missing functionality for the interface between QML and QAbstractItemModel.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QAbstractListModel>
#include <QQmlContext>
#include <QtQml>
class GPage : public QObject {
Q_OBJECT
Q_PROPERTY(QString name NOTIFY nameChanged MEMBER m_name)
Q_PROPERTY(int number NOTIFY numberChanged MEMBER m_number)
QString m_name;
int m_number;
public:
GPage(QObject * parent = 0) : QObject(parent), m_number(0) {}
GPage(QString name, int number, QObject * parent = 0) :
QObject(parent), m_name(name), m_number(number) {}
Q_SIGNAL void nameChanged(const QString &);
Q_SIGNAL void numberChanged(int);
};
class PageModel : public QAbstractListModel {
Q_OBJECT
QList<GPage*> m_pageList;
public:
PageModel(QObject * parent = 0) : QAbstractListModel(parent) {}
~PageModel() { qDeleteAll(m_pageList); }
int rowCount(const QModelIndex &) const Q_DECL_OVERRIDE {
return m_pageList.count();
}
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE {
if (role == Qt::DisplayRole || role == Qt::EditRole) {
return QVariant::fromValue<QObject*>(m_pageList.at(index.row()));
}
return QVariant();
}
bool setData(const QModelIndex &index, const QVariant &value, int role) Q_DECL_OVERRIDE {
Q_UNUSED(role);
GPage* page = value.value<GPage*>();
if (!page) return false;
if (page == m_pageList.at(index.row())) return true;
delete m_pageList.at(index.row());
m_pageList[index.row()] = page;
QVector<int> roles;
roles << role;
emit dataChanged(index, index, roles);
return true;
}
Q_INVOKABLE bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE {
Q_UNUSED(parent);
beginInsertRows(QModelIndex(), row, row + count - 1);
for (int i = row; i < row + count; ++ i) {
QString const name = QString("Page %1").arg(i + 1);
GPage * page = new GPage(name, i + 1, this);
m_pageList.insert(i, page);
QQmlEngine::setObjectOwnership(page, QQmlEngine::CppOwnership);
}
endInsertRows();
return true;
}
Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE {
Q_UNUSED(parent);
beginRemoveRows(QModelIndex(), row, row + count - 1);
while (count--) delete m_pageList.takeAt(row);
endRemoveRows();
return true;
}
};
int main(int argc, char *argv[])
{
PageModel model1;
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
model1.insertRows(0, 1);
engine.rootContext()->setContextProperty("model1", &model1);
qmlRegisterType<GPage>();
engine.load(QUrl("qrc:/main.qml"));
QObject *topLevel = engine.rootObjects().value(0);
QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
window->show();
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.0
import QtQml.Models 2.1
import QtQuick.Controls 1.0
ApplicationWindow {
width: 300; height: 300
Row {
width: parent.width
anchors.top: parent.top
anchors.bottom: column.top
Component {
id: commonDelegate
Rectangle {
width: view.width
implicitHeight: editor.implicitHeight + 10
color: "transparent"
border.color: "red"
border.width: 2
radius: 5
TextInput {
id: editor
anchors.margins: 1.5 * parent.border.width
anchors.fill: parent
text: edit.name // "edit" role of the model, to break the binding loop
onTextChanged: {
display.name = text;
model.display = display
}
}
}
}
ListView {
id: view
width: parent.width / 2
height: parent.height
model: DelegateModel {
id: delegateModel1
model: model1
delegate: commonDelegate
}
spacing: 2
}
ListView {
width: parent.width / 2
height: parent.height
model: DelegateModel {
model: model1
delegate: commonDelegate
}
spacing: 2
}
}
Column {
id: column;
anchors.bottom: parent.bottom
Row {
Button {
text: "Add Page";
onClicked: model1.insertRows(delegateModel1.count, 1)
}
Button {
text: "Remove Page";
onClicked: model1.removeRows(pageNo.value - 1, 1)
}
SpinBox {
id: pageNo
minimumValue: 1
maximumValue: delegateModel1.count;
}
}
}
}
main.qrc
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>