3
votes

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;
            }
        }
    }

}
1
We need to have complete code to your model, too. Ideally, you should have all of the C++ code in a single main.cpp file, and add it to this question. That code should contain the entire code of the model, as well as a main() that sets up the QML, the model, and runs the application.Kuba hasn't forgotten Monica
Thank you for your reply. Actually, I wanted to show the issue with the less code as possible that's the reason why I used a QML model instead of my C++ model as I have the same problem with the QML model : we can see that the model is changed when pressing the mouse (one can also use a Connection element to demonstrate dataChanged signals is sent ). Anyhow, I am adding the complete code using the C++ model in a new comment.Guillaume Charbonnier
I have now editing my question with a complete example.Guillaume Charbonnier
I have reported this issue in the bug report : bugreports.qt.io/browse/QTBUG-47366Guillaume Charbonnier

1 Answers

0
votes

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