How could I insert The row with multiple column in Qml - c++

I am trying to prepare a tree Model and I have done this:
treemodel.h
#include <QStandardItemModel>
class TreeModel : public QStandardItemModel
{
Q_OBJECT
public:
TreeModel( QObject *parent = nullptr );
private:
QVector<TreeModel *> child_items;
TreeModel* parent_item;
static const int DisplayName;
static const int DisplayInfo;
static const int DisplayId;
};
treemodel.cpp
#include "treemodel.h"
#include <QStandardItemModel>
#include <QStandardItem>
#include <QAbstractItemModel>
#include <QString>
#include <QTreeView>
#include <iostream>
#include <QList>
#include <QModelIndex>
const int TreeModel::DisplayName = Qt::UserRole+1;
const int TreeModel::DisplayInfo = Qt::UserRole+2;
const int TreeModel::DisplayId = Qt::UserRole+3;
TreeModel::TreeModel( QObject *parent ) : QStandardItemModel(parent)
{
QStandardItem *parentItem = this->invisibleRootItem();
QHash<int, QByteArray> roles = roleNames();
roles.insert(DisplayName, "A");
roles.insert(DisplayInfo, "B");
roles.insert(DisplayId, "C");
setItemRoleNames(roles);
QStandardItem *c1 = new QStandardItem;
c1->setData("Sudip", DisplayName);
QStandardItem *c2 = new QStandardItem;
c2->setData("Ghimire", DisplayInfo);
QStandardItem *c3 = new QStandardItem;
c3->setData("Bahadur", DisplayId);
QList<QStandardItem *> r1 { c1, c2, c3};
parentItem->insertRow(0, r1);
}
main.qml
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import QtQuick.Window 2.12
import QtQuick 2.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Tree View Example")
id: root
TreeView{
id: tree_view
model: TreeModel
Layout.fillHeight: true
Layout.fillWidth: true
width: root.width
height: root.height
sortIndicatorVisible: true
alternatingRowColors: false
backgroundVisible: false
TableViewColumn{
role: "A"
title: "Elements"
width: 300
}
TableViewColumn{
role: "B"
title: "more"
width: 300
}
TableViewColumn{
role: "C"
title: "I am some title"
width: 300
}
/*
MouseArea{
anchors.fill: tree_view
hoverEnabled: true
onHoveredChanged: function(){
}
}
*/
}
}
main.cpp
QQmlApplicationEngine engine;
TreeModel t_model;
engine.rootContext()->setContextProperty("TreeModel", &t_model);
But the reasult is
But I want to fill all column by a row. I think appendRow in treemodel.cpp should have done the wok. But its wired.

This is because ur model doesn't know how to deal with your custom user role when QStandardItemModel::data() called to bind value in QML. So what you have to do is, override the data() function your TreeModel class and return the data for ur custom roles. You should add below code to ur TreeModel class.
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
QVariant value;
if (index.isValid()) {
if (role <= Qt::UserRole) {
value = QStandardItemModel::data(index, role);
}
else if(role == DisplayName)
{
value = QStandardItemModel::data(index, DisplayName);
}
else if(role == DisplayInfo)
{
int columnIdx = role - Qt::UserRole - 1;
QModelIndex modelIndex = this->index(index.row(), columnIdx);
value = QStandardItemModel::data(modelIndex, DisplayInfo);
}
else if(role == DisplayId)
{
int columnIdx = role - Qt::UserRole - 1;
QModelIndex modelIndex = this->index(index.row(), columnIdx);
value = QStandardItemModel::data(modelIndex, DisplayId);
}
else
{
// undefined role.
}
}
return value;
}

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

Dynamically filled combobox not showing text, list is undefined

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");

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

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.

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
}

Inserting/Deleting Items in a drag&drop QML listView with cpp model

I tried to add to a ListView in QML of N Items a way to add and delete a new Item at a given index.
I did the following example, but the problem is that when I move some Items, when I try to insert a new one, the position might be incorrect and I have no clue why. When I check my DataList in my cpp model, positions are correct, however, new or deleted items won't be inserted/deleted at the right position.
It seems that the error occurs when I insert a new Item, then I move it , and then I try to delete this Item or insert an Item next to this New Item.
Here is a simple example (you can run it if you need). I called my Items Data : Blocks
#include "mainwindow.h"
#include <QApplication>
#include <QtQml>
#include <QQuickView>
#include <QQuickWidget>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
main.cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "model.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
void addItem(int index);
~MainWindow();
private slots:
private:
QList<QObject*> dataList;
Ui::MainWindow *ui;
BlockModel model;
int cpt = 0;
};
#endif // MAINWINDOW_H
mainwindow.h
#include <QtQml>
#include <QQuickView>
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QQuickWidget"
#include <QStringList>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
int nbItems = 5;
for(; cpt < nbItems; cpt ++) {
Block a = Block(QString("Item ")+QString::number(cpt));
model.addBlock(a);
}
ui->setupUi(this);
QQuickWidget *view = new QQuickWidget;
QQmlContext *ctxt = view->rootContext();
ctxt->setContextProperty("myModel", &model);
view->setSource(QUrl::fromLocalFile("main.qml"));
view->setGeometry(0, 200, 600, 400);
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
ui->dockWidget_3->setWidget(view);
}
MainWindow::~MainWindow()
{
delete ui;
}
mainwindow.cpp
#include <QAbstractListModel>
#include <QStringList>
#include <qqmlcontext.h>
#include <QDebug>
#include <QStringList>
//![0]
class Block
{
public:
Block(){
}
Block(const QString &name);
QString nameBlock() const;
void setName(QString n) {
m_name = n;
}
private:
QString m_name;
};
class BlockModel : public QAbstractListModel
{
Q_OBJECT
public:
Block* getBlock(QString name);
Q_INVOKABLE void moveBlock(int from,int to);
Q_INVOKABLE void insertBlock(int index);
Q_INVOKABLE void deleteBlock(int index);
enum BlockRoles {
nameRole = Qt::UserRole + 1,
};
BlockModel(QObject *parent = 0);
void setContext(QQmlContext *ctx) {
m_ctx = ctx;
}
void setName(const QString &name);
void addBlock(const Block &Block);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
private:
QList<Block> m_blocks;
QQmlContext* m_ctx;
int cpt = 0;
};
mode.h
#include "model.h"
#include "qDebug"
Block::Block(const QString &name)
: m_name(name)
{
}
QString Block::nameBlock() const
{
return m_name;
}
BlockModel::BlockModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void BlockModel::addBlock(const Block &Block)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_blocks << Block;
endInsertRows();
}
int BlockModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_blocks.count();
}
void BlockModel::moveBlock(int from, int to) {
m_blocks.move(from,to);
}
void BlockModel::insertBlock(int index) {
Block b =(Block(QString("New Item ")+QString::number(cpt)));
beginInsertRows(QModelIndex(),index+1,index+1);
m_blocks.insert(index+1,b);
endInsertRows();
cpt++;
}
void BlockModel::deleteBlock(int index) {
beginRemoveRows(QModelIndex(),index,index);
m_blocks.removeAt(index);
endRemoveRows();
}
QVariant BlockModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_blocks.count())
return QVariant();
const Block &Block = m_blocks[index.row()];
if (role == nameRole)
return Block.nameBlock();
return QVariant();
}
//![0]
QHash<int, QByteArray> BlockModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[nameRole] = "nameBlock";
return roles;
}
model.cpp
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.2
import QtQml.Models 2.2
import QtQuick.Controls.Styles 1.4
Rectangle {
id : rootRectangle
visible: true
ScrollView {
anchors.fill:parent
ListView{
id: root
width: parent.width; height: parent.height
property int visualIndex: -1
displaced: Transition {
NumberAnimation { properties: "y"; easing.type: Easing.OutQuad }
}
model: DelegateModel {
id: visualModel
model: myModel
delegate: Component {
MouseArea {
id: delegateRoot
property int visualIndex: DelegateModel.itemsIndex
cursorShape: Qt.PointingHandCursor
width: root.width; height: 100
drag.target: icon
drag.axis: Drag.YAxis
Behavior on height {
PropertyAnimation { duration: 100 }
}
Rectangle {
anchors.top: delegateRoot.top
anchors.left: delegateRoot.left
id: icon
objectName: nameBlock
width: root.width-5; height: 100
color: "skyblue"
radius: 3
Text {
objectName: "rect"
id: title
anchors.fill: parent
anchors.margins: 10
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: nameBlock
}
Drag.active: delegateRoot.drag.active
Drag.source: delegateRoot
Drag.hotSpot.x: 36
Drag.hotSpot.y: 36
Button {
id : buttonAdd
text: "Add Block"
anchors{
right: parent.right
top: parent.top
bottom: parent.bottom
margins: 30
}
onClicked: {
myModel.insertBlock(visualIndex)
}
}
Button {
id : buttonDelete
text: "Delete Block"
anchors{
right: buttonAdd.left
top: parent.top
bottom: parent.bottom
margins: 30
}
onClicked: {
myModel.deleteBlock(visualIndex)
}
}
states: [
State {
when: icon.Drag.active
ParentChange {
target: icon
parent: root
}
AnchorChanges {
target: icon;
anchors.horizontalCenter: undefined;
anchors.verticalCenter: undefined
}
}
]
transitions: Transition {
// Make the state changes smooth
ParallelAnimation {
ColorAnimation { property: "color"; duration: 500 }
NumberAnimation { duration: 300; properties: "detailsOpacity,x,contentY,height,width,font.pixelSize,font.bold,visible" }
}
}
}
DropArea {
anchors { fill: parent; margins: 15 }
onEntered: {
visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex)
myModel.moveBlock(drag.source.visualIndex,delegateRoot.visualInde)
}
}
}
}
}
}
}
}
main.qml
Do you have any idea of what I am doing wrong ?
Thanks a lot and have a good day !
There are two bugs when moving items. In DropArea.onEntered, if you print out both drag.source.visualIndex and delegateRoot.visualIndex before and after visualModel.items.move, you'll see that values are modified after moving. That means you are moving wrong rows when calling myModel.moveBlock. To fix the problem, save the value before moving items:
DropArea {
anchors { fill: parent; margins: 15 }
onEntered: {
var from = drag.source.visualIndex;
var to = delegateRoot.visualIndex;
myModel.moveBlock(from, to);
}
}
When moving items in C++ model, QAbstractItemModel::beginMoveRows should be called just like insert/remove items. Otherwise the QML DelegateModel cannot correctly display your model. Remember that when implementing BlockModel::moveBlock, the destination row for the model is different from the one for your source list m_blocks. See the last example in QAbstractItemModel::beginMoveRows documentation for detail.
void BlockModel::moveBlock(int from, int to) {
if (from == to)
return;
auto modelFrom = from;
auto modelTo = to + (from < to ? 1 : 0);
beginMoveRows(QModelIndex(), modelFrom, modelFrom, QModelIndex(), modelTo);
m_blocks.move(from,to);
endMoveRows();
}