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)
}
}
}
}
Related
I know there are a lot of different questions on stackoverflow and other forums about dynamically filling a combobox from c++, but out of all those questions i cant find an answer that i need. Currently i am fetching a list from my database in C++ and store that in my CompanyList class. My CompanyModel class uses that class and communicates to my qml ui. In my QML editor i set the model to CompanyModel.list and the textRole to the value i want from the struct.
The problem that im facing is that i am not getting any errors, but my combobox is still empty. I cant find the problem so i hope someone can look over the mistake i might have made.
My Company Struct
struct CompanyStruct {
int id;
QString name;
};
My Company List
CompanyList::CompanyList(QObject *parent) : QObject(parent)
{
appendItem({-1, "test company"});
appendItem({-2, "test company 2"});
}
QVector<CompanyStruct> CompanyList::items() const
{
return mItems;
}
bool CompanyList::setItemAt(int index, const CompanyStruct &item)
{
if (index < 0 || index >= mItems.size())
return false;
const CompanyStruct &oldItem = mItems.at(index);
if (item.id == oldItem.id && item.name == oldItem.name)
return false;
mItems[index] = item;
return true;
}
void CompanyList::appendItem()
{
emit preItemAppended();
CompanyStruct company;
company.id = -1;
company.name = "This is a test company!";
mItems.append(company);
emit postItemAppended();
}
void CompanyList::appendItem(CompanyStruct item)
{
emit preItemAppended();
mItems.append(item);
emit postItemAppended();
}
My Company Model
#include "companymodel.h"
#include "companylist.h"
CompanyModel::CompanyModel(QObject *parent)
: QAbstractListModel(parent)
, mList(nullptr)
{
}
int CompanyModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid() || !mList)
return 0;
return mList->items().size();
}
QVariant CompanyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || !mList)
return QVariant();
const CompanyStruct item = mList->items().at(index.row());
switch (role) {
case IdRole:
return QVariant(item.id);
case NameRole:
return QVariant(item.name);
}
return QVariant();
}
bool CompanyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!mList)
return false;
CompanyStruct item = mList->items().at(index.row());
switch (role) {
case IdRole:
item.id = value.toInt();
break;
case NameRole:
item.name = value.toString();
break;
}
if (mList->setItemAt(index.row(), item)) {
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
return false;
}
Qt::ItemFlags CompanyModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable;
}
QHash<int, QByteArray> CompanyModel::roleNames() const
{
QHash<int, QByteArray> names;
names[IdRole] = "id";
names[NameRole] = "name";
return names;
}
CompanyList *CompanyModel::list() const
{
return mList;
}
void CompanyModel::setList(CompanyList *list)
{
beginResetModel();
if (mList)
mList->disconnect(this);
mList = list;
if (mList) {
connect(mList, &CompanyList::preItemAppended, this, [=]() {
const int index = mList->items().size();
beginInsertRows(QModelIndex(), index, index);
});
connect(mList, &CompanyList::postItemAppended, this, [=]() {
endInsertRows();
});
}
endResetModel();
}
My Main.cpp
qmlRegisterType<CompanyModel>("Company", 1,0, "CompanyModel");
qmlRegisterUncreatableType<CompanyList>("Company", 1,0, "CompanyList", "CompanyList should not be created in QML");
CompanyList companyList;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("companyList", &companyList);
My combobox in QML
import Company 1.0
ComboBox {
Layout.column: 1
Layout.columnSpan: 3
Layout.row: 4
id: cbSelectKlant
implicitWidth: parent.width*0.6
implicitHeight: parent.height*0.05
background: Rectangle {
color: "white"
border.color: "#6abc93"
border.width: 3
width: parent.width
height: parent.height
}
textRole: "name"
model: CompanyModel.list
}
Normally you will create your models in C++ and expose them to QML.
You don't even have to use qmlRegisterType if you derive your presentation data from QObject.
For the sake of simplicity I made header only code except main.cpp
//company.h file:
#ifndef COMPANY_H
#define COMPANY_H
#include <QObject>
class Company : public QObject
{
Q_OBJECT
Q_PROPERTY(int id READ id WRITE setId CONSTANT)
Q_PROPERTY(QString name READ name WRITE setName CONSTANT)
Q_PROPERTY(QString displayName READ displayName CONSTANT)
public:
Company(QObject *parent=nullptr) : QObject(parent)
{}
int id() const { return mID; }
void setId(const int id) { mID = id; }
QString name() const { return mName; }
void setName(const QString& name) { mName = name; }
QString displayName() { return "Company: " + mName + ", ID: " + QString::number(mID); }
private:
int mID;
QString mName;
};
#endif // COMPANY_H
//companylistmodel.h file:
#ifndef COMPANYLISTMODEL_H
#define COMPANYLISTMODEL_H
#include "company.h"
#include <QAbstractListModel>
#include <vector>
class CompanyListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles
{
CompanyRole = Qt::UserRole
};
CompanyListModel(QObject* parent = nullptr) : QAbstractListModel(parent)
{
mCompanies.push_back(new Company(this));
mCompanies.back()->setName("Google"); mCompanies.back()->setId(1);
mCompanies.push_back(new Company(this));
mCompanies.back()->setName("Microsoft"); mCompanies.back()->setId(2);
mCompanies.push_back(new Company(this));
mCompanies.back()->setName("CraftUnique"); mCompanies.back()->setId(3);
}
int rowCount(const QModelIndex& parent = QModelIndex()) const override { return mCompanies.size(); }
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { return QVariant::fromValue(mCompanies.at(index.row())); }
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override { return true; } // use property instead
virtual QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> names;
names[CompanyRole] = "companyRole";
return names;
}
private:
std::vector<Company*> mCompanies;
};
#endif // COMPANYLISTMODEL_H
// main.cpp file:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
#include "companylistmodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
CompanyListModel clm;
view.rootContext()->setContextProperty("clm", &clm);
view.setSource(QStringLiteral("qrc:/main.qml"));
view.show();
return app.exec();
}
//main.qml file:
import QtQuick 2.12
import QtQuick.Controls 2.12
Rectangle {
anchors.fill: parent
color: "white"
Component.onCompleted: { console.log(clm) }
ComboBox {
id: cbSelectKlant
implicitWidth: parent.width
implicitHeight: 30
model: clm
textRole: "companyRole.displayName"
contentItem: Label {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
text: cbSelectKlant.currentText
}
background: Rectangle {
color: "gray"
}
delegate: ItemDelegate {
implicitWidth: parent.width
implicitHeight: 30
contentItem: Label {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
text: companyRole.name + " (" + companyRole.id + ")"
}
}
}
}
You can even assign values from QML like: companyRole.name = "SomeCompany"
And this won't call the setData method of your model, but directly update the property itself.
The first part of the problem is that you are not initializing mList in the code given (which I think is a bit short of a MRE ).
Change the constructor to the following:
CompanyModel::CompanyModel(QObject *parent)
: QAbstractListModel(parent)
, mList(new CompanyList(this))
{
}
And you are also allowing the qml-developer in you to instantiate the CompanyModel in QML (first line of main.cpp), which you are kinda doing, but not actually (last line of the ComboBox QML code). This way the CompanyModel is not instantiated, but you are still trying to get the list property from the class-definition, which will be undefined (IIC), leading to an empty ComboBox without error.
This can be solved by instantiating it in QML:
import Company 1.0
ComboBox {
Layout.column: 1
Layout.columnSpan: 3
Layout.row: 4
id: cbSelectKlant
implicitWidth: parent.width*0.6
implicitHeight: parent.height*0.05
background: Rectangle {
color: "white"
border.color: "#6abc93"
border.width: 3
width: parent.width
height: parent.height
}
textRole: "name"
model: theModel.list
CompanyModel {
id: theModel
}
}
However, I doubt this is what you want, since you will not be able to control theModel from C++. Which is probably also why you are setting the rootContext property, but you are not actually using it (last line of main.cpp).
So, change the QML to this:
import Company 1.0
ComboBox {
Layout.column: 1
Layout.columnSpan: 3
Layout.row: 4
id: cbSelectKlant
implicitWidth: parent.width*0.6
implicitHeight: parent.height*0.05
background: Rectangle {
color: "white"
border.color: "#6abc93"
border.width: 3
width: parent.width
height: parent.height
}
textRole: "name"
model: companyList.list
}
As a last advise, also change the first line of main.cpp to:
qmlRegisterUncreatableType<CompanyModel>("Company", 1,0, "CompanyModel", "CompanyModel should not be created in QML");
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.
Maybe my question isn't clear but i hope that with the code my problem become more clear.
I have a created these two structs and i use them in FormModel class :
struct Tile
{
QString name;
QString color;
}; Q_DECLARE_METATYPE(Tile)
struct Form
{
QString nameForm;
Tile grid[9] ; // i want for each form 4 tile that in qml are 4 rectangles
}; Q_DECLARE_METATYPE(Form)
class FormModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit FormModel(QObject *parent = 0);
~FormModel();
enum dashBoardRoles {
NameForm=Qt::UserRole+1,
Grid,
};
int rowCount(const QModelIndex &parent=QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
Q_INVOKABLE bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE;
Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const;
signals:
public slots:
private:
QList<Form> dashboard;
};
This is data method of QAbstractListModel:
QVariant FormModel::data(const QModelIndex &index, int role) const
{
if(index.row()>0 && index.row() >= dashboard.count())
return QVariant();
Form dashTemp = dashboard[index.row()];
if(role== NameForm)
return dashTemp.nameForm;
if(role== Grid)
{
QVariant gridVect = QVariant::fromValue(dashTemp.grid);
return gridVect;
}
return QVariant();
}
QHash<int, QByteArray> FormModel::roleNames() const
{
QHash <int,QByteArray> roles;
roles [NameForm]="nameForm";
roles [Grid]="grid";
return roles;
}
this is the qml code:
Window {
id:app
visible: true
width: 480
height: 800
title: qsTr("Hello World")
ListView {
model: myForms // i have defined the name in main.cpp
anchors.fill: parent
delegate: ColumnLayout{
spacing: 30
// nome Form
Text{
text: nameForm
font.pointSize:20
color: "red"
font.capitalization: Font.Capitalize
}
// grigla mattonelle
GridView {
id:grid
width: 300; height: 300
cellWidth: 100 ; cellHeight: 100
model: 9
delegate: Item{
width : grid.cellWidth
height: grid.cellHeight
Rectangle{
anchors.centerIn: parent
width:parent.width-10
height: parent.height-10
color: grid[index].color
}
}
}
}
}
}
How i can get the color from the data of role Grid? that in qml i call with grid[index].color....
QML does not directly recognize the struct, for this you have to use Q_GADGET with Q_PROPERTY, it is also advisable to use QVariantList instead of an array since QML can cast it directly. All the above I have implemented it in the following code:
formmodel.h
#ifndef FORMMODEL_H
#define FORMMODEL_H
#include <QAbstractListModel>
struct Tile
{
Q_GADGET
Q_PROPERTY(QString name MEMBER name)
Q_PROPERTY(QString color MEMBER color)
public:
QString name;
QString color;
Tile(const QString& name="", const QString& color=""){
this->name = name;
this->color = color;
}
};
Q_DECLARE_METATYPE(Tile)
struct Form
{
Q_GADGET
Q_PROPERTY(QString nameForm MEMBER nameForm)
Q_PROPERTY(QVariantList grid MEMBER grid)
public:
Form(){
}
QString nameForm;
QVariantList grid;
};
Q_DECLARE_METATYPE(Form)
class FormModel : public QAbstractListModel
{
enum dashBoardRoles {
NameForm=Qt::UserRole+1,
Grid
};
public:
FormModel(QObject *parent = Q_NULLPTR);
QHash<int, QByteArray> roleNames() const;
int rowCount(const QModelIndex &parent=QModelIndex()) const;
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const;
private:
QList<Form> dashboard;
};
#endif // FORMMODEL_H
formmodel.cpp
#include "formmodel.h"
#include <QColor>
FormModel::FormModel(QObject *parent):QAbstractListModel(parent)
{
for(int i=0; i<10; i++){
Form form;
form.nameForm = QString("name %1").arg(i);
for(int j=0; j<9; j++){
QColor color(qrand() % 256, qrand() % 256, qrand() % 256);
Tile tile{QString("name %1 %2").arg(i).arg(j), color.name()};
form.grid<< QVariant::fromValue(tile);
}
dashboard<< form;
}
}
QHash<int, QByteArray> FormModel::roleNames() const
{
QHash <int,QByteArray> roles;
roles [NameForm]="nameForm";
roles [Grid]="grid";
return roles;
}
int FormModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return dashboard.count();
}
QVariant FormModel::data(const QModelIndex &index, int role) const
{
if(index.row()<0 && index.row() >= dashboard.count())
return QVariant();
Form dashTemp = dashboard[index.row()];
if(role== NameForm)
return dashTemp.nameForm;
else if(role== Grid)
return dashTemp.grid;
return QVariant();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
Window {
id:app
visible: true
width: 480
height: 800
title: qsTr("Hello World")
ListView {
model: myForms
anchors.fill: parent
delegate: ColumnLayout{
spacing: 30
Text{
text: nameForm
font.pointSize:20
color: "red"
font.capitalization: Font.Capitalize
}
GridView {
id:gr
width: 300; height: 300
cellWidth: 100 ; cellHeight: 100
model: grid // grid.length
delegate: Item{
width : gr.cellWidth
height: gr.cellHeight
Rectangle{
anchors.centerIn: parent
width:parent.width-10
height: parent.height-10
color: grid[index].color
Text {
id: name
anchors.fill: parent
text: grid[index].name
}
}
}
}
}
}
}
The complete example can be found in the following link.
I use qt quick 2.0. A gridview is bound to a c++ model as described in here. In the gridview's delegate, I use an image to show an icon. I try to use state property to change image source and bind state to the model.
The expected result would be on release the selected screen image should be changed to running icon.
The actual result it image doesn't change. If I use setName instead of setState in ScreenManager::setScreenState, it shows changed screen name correctly.
Is there any better solution?
Screen.h
class Screen
{
public:
Screen(QString name, int gridId, bool active = false);
QString name() const;
int gridId() const;
bool active() const;
QString state() const;
void setName(QString n);
void setActive(bool a);
void setState(QString s);
private:
QString m_name;
int m_gridId;
bool m_active;
QString m_state;
};
ScreenManager.h
class ScreenManager : public QAbstractListModel
{
Q_OBJECT
public:
enum ScreenRoles {
NameRole = Qt::UserRole + 1,
GridIDRole,
ActiveRole,
StateRole
};
ScreenManager();
void addScreen(const Screen& screen);
int rowCount(const QModelIndex& parent = QModelIndex()) const;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
QModelIndex getIndex(int row, int column = 0,
const QModelIndex &parent = QModelIndex()) const;
Q_INVOKABLE int getScreenGridId(int index);
Q_INVOKABLE bool getScreenActive(int index);
Q_INVOKABLE void swapScreens(int index1, int index2);
Q_INVOKABLE void setScreenState(int index, QString s);
Q_INVOKABLE QString getScreenState(int index);
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Screen> m_screens;
};
ScreenManager.cpp
#include "ScreenManager.h"
#include "Screen.h"
ScreenManager::ScreenManager()
{
int index = 0;
for (;index < 15; index++) {
addScreen(Screen(QString ("Screen%1").arg(index), index, false));
}
m_screens[2].setActive(true);
m_screens[6].setActive(true);
m_screens[7].setActive(true);
m_screens[8].setActive(true);
m_screens[12].setActive(true);
}
void ScreenManager::addScreen(const Screen& screen)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_screens.append(screen);
endInsertRows();
}
int ScreenManager::rowCount(const QModelIndex& parent) const {
Q_UNUSED(parent);
return m_screens.count();
}
QVariant ScreenManager::data(const QModelIndex& index, int role) const
{
if (index.row() < 0 || index.row() >= m_screens.count())
return QVariant();
const Screen& screen = m_screens[index.row()];
if (role == NameRole)
return screen.name();
else if (role == GridIDRole)
return screen.gridId();
else if (role == ActiveRole)
return screen.active();
else if (role == StateRole)
return screen.state();
return QVariant();
}
QModelIndex ScreenManager::getIndex(int row, int column,
const QModelIndex &parent) const
{
return hasIndex(row, column, parent) ?
createIndex(row, column, (void*)&m_screens[row])
: QModelIndex();
}
QHash<int, QByteArray> ScreenManager::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[GridIDRole] = "gridId";
roles[ActiveRole] = "active";
roles[StateRole] = "state";
return roles;
}
int ScreenManager::getScreenGridId(int index)
{
return m_screens.at(index).gridId();
}
bool ScreenManager::getScreenActive(int index)
{
return m_screens.at(index).active();
}
void ScreenManager::swapScreens(int index1, int index2)
{
int min = index1 < index2 ? index1 : index2;
int max = index1 > index2 ? index1 : index2;
m_screens.swap(index1, index2);
beginMoveRows(QModelIndex(), max, max, QModelIndex(), min);
endMoveRows();
if (max - min > 1) {
beginMoveRows(QModelIndex(), min + 1, min + 1, QModelIndex(), max + 1);
endMoveRows();
}
}
void ScreenManager::setScreenState(int index, QString s)
{
// if use setName, the grid view can show the changed screen name
m_screens[index].setState(s);
dataChanged(getIndex(0), getIndex(rowCount() - 1));
}
QString ScreenManager::getScreenState(int index)
{
return m_screens[index].state();
}
QML
GridView {
id: gridView
x: 82
y: 113
width: cellWidth * 5
height: cellHeight * 3
clip: true
anchors.bottom: parent.bottom
anchors.bottomMargin: 70
anchors.topMargin: 100
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
flickableDirection: Flickable.HorizontalAndVerticalFlick
cellWidth: 90; cellHeight: 90;
property bool ignoreMovementAnimation: true
MouseArea {
id: gridViewMouseArea
hoverEnabled: true
preventStealing : true
property int currentGridId: -1
property int preIndex
property int index: gridView.indexAt(mouseX, mouseY)
anchors.fill: parent
onPressAndHold: {
currentGridId = screenManager.getScreenGridId(index)
preIndex = index
gridView.ignoreMovementAnimation = false
}
onReleased: {
currentGridId = -1
screenManager.setScreenState(index, "running");
}
onPositionChanged: {
if (currentGridId != -1 && index != -1 && index != preIndex) {
if (screenManager.getScreenActive(index)) {
screenManager.swapScreens(preIndex, index)
preIndex = index
}
}
}
}
model: screenManager
delegate: Component {
Item {
id: gridViewDelegate
width: gridView.cellWidth; height: gridView.cellHeight
state: state
states: [
State {
name: "running"
PropertyChanges {
target: itemImage
source: "qrc:/res/image/screen_icon_running.png"
}
},
State {
name: "idle"
PropertyChanges {
target: itemImage
source: "qrc:/res/image/screen_icon_idle.png"
}
}
]
Image {
id: itemImage
parent: gridView
x: gridViewDelegate.x + 5
y: gridViewDelegate.y + 5
width: gridViewDelegate.width - 10
height: gridViewDelegate.height - 10;
fillMode: Image.PreserveAspectFit
smooth: true
source: "qrc:/res/image/screen_icon.png"
visible: active
Text {
text: name
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
anchors.fill: parent;
border.color: "grey"
border.width: 6
color: "transparent"; radius: 5
visible: itemImage.state === "active"
}
// specify the movement's animation for non-active screen icons
Behavior on x {
enabled: !gridView.ignoreMovementAnimation && itemImage.state !== "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
Behavior on y {
enabled: !gridView.ignoreMovementAnimation && itemImage.state !== "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
// specify the shaking animation for non-active screen icons when hold one icon
SequentialAnimation on rotation {
NumberAnimation { to: 2; duration: 60 }
NumberAnimation { to: -2; duration: 120 }
NumberAnimation { to: 0; duration: 60 }
running: gridViewMouseArea.currentGridId != -1 && itemImage.state !== "active"
loops: Animation.Infinite
alwaysRunToEnd: true
}
// specify the active screen's new position and size
states: State {
name: "active"
when: gridViewMouseArea.currentGridId == gridId
PropertyChanges {
target: itemImage
x: gridViewMouseArea.mouseX - width/2
y: gridViewMouseArea.mouseY - height/2
scale: 0.5
z: 10
}
}
// specify the scale speed for the active screen icon
transitions: Transition {
NumberAnimation { property: "scale"; duration: 200}
}
}
}
}
}
You have a naming problem, because state is also know for the state property in your item delegate.
After i changed:
roles[StateRole] = "statetest";
in you c++
and:
state: statetest
in your qml,
it works.
Or simply:
state: model.state
in your qml
I solved the problem by binding image source to another property in Screen, stateIamge, rather than binding it to state directly.
But I'm still interested to see why binding to state doesn't work.
I've got a QML-application containing a TableView with two columns. One of them is a CheckBox. Now I created a model derived from QAbstractTableModel. Reading data for the ordinary text-column already works but how do I sync the checked-property for my CheckBox with the model?
Currently I can't even set it checked via model. You find the relevant code below.
tablemodel.cpp
TableModel::TableModel(QObject *parent) :
QAbstractTableModel(parent)
{
list.append("test1");
list.append("test2");
}
int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return list.count();
}
int TableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2;
}
QVariant TableModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= list.count())
return QVariant();
if (role == NameRole)
return list[index.row()]
else if (role== EnabledRole){
//list is not QList<QString>, its a custom class saving a String and a boolean for the checked state
return list[index.row()].isEnabled();
}
else {
return QVariant();
}
}
QHash<int, QByteArray> TableModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[EnabledRole] = "enabled";
return roles;
}
tablemodel.hpp
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum Roles {
NameRole = Qt::UserRole + 1,
EnabledRole
};
explicit TableModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex & parent = QModelIndex()) const;
Q_INVOKABLE QVariant data (const QModelIndex & index, int role) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<QString> list;
main.qml
TableView {
id: Table
model: TableModel
TableViewColumn {
role: "name"
}
TableViewColumn {
role: "enabled"
delegate: CheckBox {
//how to get the right state from the model
//checked: ??
}
}
}
main.cpp
QQmlApplicationEngine engine;
QQmlContext * context = new QQmlContext(engine.rootContext());
TableModel tableModel;
context->setContextProperty("tableModel",&tableModel);
QQmlComponent component(&engine, QUrl("qrc:///main.qml"));
QQuickWindow * window = qobject_cast<QQuickWindow*>(component.create(context));
window->show();
You can emit signal from qml, when clicked on checkbox; connect this signal to your model slot and do something
main.qml
TableView {
id: table
objectName: "myTable"
signal checkedChanged(int index, bool cheked)
TableViewColumn {
role: "enabled"
delegate: CheckBox {
onClicked: table.checkedChanged(styleData.row, checked);
}
}
main.cpp
QQuickItem *obj = engine.rootObjects().at(0)->findChild<QQuickItem*>(QStringLiteral("myTable"));
QObject::connect(obj,SIGNAL(checkedChanged(int,bool)),tableModel,SLOT(mySlot(int,bool)));