How to unite several models into one? - c++

I've created two models and now it's need to be united in one.
Both of them are inheriting QAbstractListModel and contains only roles and simple data as QVector<QMap<..., ...>> backing; what I pass to data() function
There's any way to unite them into one model and pass to Qml page through delegate?
Here my models:
ExtraModel
#include "extramodel.h"
ExtraModel::ExtraModel(QObject *parent) : QAbstractListModel(parent)
{
QMap<QString, QString> element;
element.insert("SimpleText", "Hello, world!");
backing.append(element);
element.clear();
}
QHash<int, QByteArray> ExtraModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[SimpleTextRole] = "SimpleText";
return roles;
}
QVariant ExtraModel::data(const QModelIndex &index, int role) const
{
if(role == SimpleTextRole)
return backing[index.row()].value("SimpleText");
}
MainModel (here I am trying to pass ExtraModel to MainModel)
#include <mainmodel.h>
#include <extramodel.h>
MainModel::MainModel(QObject *parent) : QAbstractListModel(parent)
{
ExtraModel *extra;
QMap<QString, QHash<int, QByteArray>> element;
element.insert("Role", extra->roleNames());
backing.append(element);
element.clear();
}
QHash<int, QByteArray> MainModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[ModelRole] = "Role";
return roles;
}
QVariant MainModel::data(const QModelIndex &index, int role) const
{
if (role == ModelRole)
return backing[index.row()].value("Role");
return QVariant();
}
That's how I want it to be in .qml file
import QtQuick 2.6
import Sailfish.Silica 1.0
import MainModel 1.0
Page {
SilicaListView {
model: MainModel {
id: _mainModel
}
delegate: Rectangle {
Text {
id: _simpleText
text: _mainModel.[someRoleNameFromExtraModel].SimpleText
}
}
}
}

You are probably better of writing a plain class with Q_PROPERTY to ExtraModel
class MainModel : public QObject {
Q_OBJECT
Q_PROPERTY(ExtraModel* extra READ extraModel CONSTANT)
public:
MainModel();
ExtraModel* extraModel() const { return extraModel_; }
private:
ExtraModel* extraModel_ = nullptr;
};
cpp file:
MainModel::MainModel(QObject *parent)
: QObject(parent)
, extraModel_(new ExtraModel(this))
{
}
Then your QML will look like this:
import QtQuick 2.6
import Sailfish.Silica 1.0
import MainModel 1.0
Page {
MainModel {
id: _mainModel
}
SilicaListView {
id: _mainModelView
model: _mainModel.extra
delegate: Rectangle {
Text {
id: _simpleText
text: simpleText
}
}
}
}
BTW, you should also change the rolenames to be uncapitalized for the first letter:
QHash<int, QByteArray> ExtraModel::roleNames() const
{
return {
{SimpleTextRole, "simpleText"},
};
}

Related

Qml/Qt c++: How to automatically update items in Qml ListView when the underlying Qt c++ ListModel changes?

Based on an example video from Qt (the ToDo example), I have created a ListView based Qt/Qml application.
Data for the ListView in Qml comes from a c++ class based on QAbstractListModel. The c++ model class is populated with data from a database.
This all works fine.
I have added a pushbutton in the Qml file which invokes a method in the c++ code that fetches data from a remote source (i.e. makes an https request). The https response data is of course asynchronous with the method that makes the http request.
Debug lines in the c++ https response handler confirm that the response is received okay. I save the response to the database.
If I close the application and re-open it, the new data is shown in the Qml list because, once again, on opening the application the c++ model is populated from the db.
But what I really need is that, after saving the data to the db in the https response handler, I also push the new data to the Qml ListView so that I don't have to restart the application to refresh the updated list data.
Unfortunately I don't know how to push the new data from c++ to Qml. I have tried a number of ways (signals from c++, slots in Qml, reading the updated list from c++, etc) but nothing has worked so far.
I know it has to do with the fact that the pushbutton starts an http request in c++ which is not handled synchronously but in a slot function which is the http response handler.
But unfortunately I don't know how to resolve this issue.
I would appreciate some help with this.
Note:
The following example is in fact off of an excellent YouTube Video on Qt model/view by Mitch Curtis Using C++ Models in QML - To-Do List!
But my code is very similar except that I want to add a button which changes the descriptions based on an https response:
The c++ files providing the listdata:
todolist.h and todolist.cpp
=============================================================
#ifndef TODOLIST_H
#define TODOLIST_H
#include <QObject>
#include <QVector>
struct ToDoItem
{
bool done;
QString description;
};
class ToDoList : public QObject
{
Q_OBJECT
public:
explicit ToDoList(QObject *parent = nullptr);
QVector<ToDoItem> items() const;
bool setItemAt(int index, const ToDoItem &item);
signals:
void preItemAppended();
void postItemAppended();
void preItemRemoved(int index);
void postItemRemoved();
public slots:
void appendItem();
void removeCompletedItems();
private:
QVector<ToDoItem> m_Items;
};
=======================================================================
#include "todolist.h"
ToDoList::ToDoList(QObject *parent) : QObject(parent)
{
m_Items.append({ true, QStringLiteral("Wash the car") });
m_Items.append({ false, QStringLiteral("Fix the sink") });
m_Items.append({ true, QStringLiteral("Wash the dishes") });
}
QVector<ToDoItem> ToDoList::items() const
{
return m_Items;
}
bool ToDoList::setItemAt(int index, const ToDoItem &item)
{
if (index <0 || index >= m_Items.size()) {
return false;
}
const ToDoItem &oldItem = m_Items.at(index);
bool nothingChanged = oldItem.done == item.done
&& oldItem.description == item.description;
if(nothingChanged) {
return false;
}
m_Items[index] = item;
return true;
}
void ToDoList::appendItem()
{
emit preItemAppended();
ToDoItem item;
item.done = false;
m_Items.append(item);
emit postItemAppended();
}
void ToDoList::removeCompletedItems()
{
for (int i = 0; i < m_Items.size();) {
if(!m_Items[i].done) {
++i;
continue;
}
//otherwise...
emit preItemRemoved(i);
m_Items.removeAt(i);
emit postItemRemoved();
}
}
###################################################################
The c++ files implementing the listmodel:
todomodel.h and todomodel.cpp
==================================================
#ifndef TODOMODEL_H
#define TODOMODEL_H
#include <QAbstractListModel>
class ToDoList;
class TodoModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(ToDoList *list READ list WRITE setList)
public:
explicit TodoModel(QObject *parent = nullptr);
enum {
DoneRole = Qt::UserRole,
DescriptionRole
};
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// Editable:
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual QHash<int, QByteArray> roleNames() const override;
ToDoList *list() const;
void setList(ToDoList *newList);
private:
ToDoList *m_List;
};
#endif // TODOMODEL_H
===================================================================
#include "todomodel.h"
#include "todolist.h"
TodoModel::TodoModel(QObject *parent)
: QAbstractListModel(parent)
, m_List(nullptr)
{
}
int TodoModel::rowCount(const QModelIndex &parent) const
{
// For list models only the root node (an invalid parent) should return the list's size. For all
// other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
if (parent.isValid() || !m_List)
return 0;
return m_List->items().size();
}
QVariant TodoModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || !m_List)
return QVariant();
const ToDoItem item = m_List->items().at(index.row());
switch (role) {
case DoneRole:
return QVariant(item.done);
case DescriptionRole:
return QVariant(item.description);
}
return QVariant();
}
bool TodoModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!m_List) {
return false;
}
ToDoItem item = m_List->items().at(index.row());
switch (role) {
case DoneRole:
item.done = value.toBool();
break;
case DescriptionRole:
item.description = value.toByteArray();
break;
}
if (m_List->setItemAt(index.row(), item)) {
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
return false;
}
Qt::ItemFlags TodoModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable;
}
QHash<int, QByteArray> TodoModel::roleNames() const
{
QHash<int, QByteArray> names;
names[DoneRole] = "done";
names[DescriptionRole] = "description";
return names;
}
ToDoList *TodoModel::list() const
{
return m_List;
}
void TodoModel::setList(ToDoList *newList)
{
beginResetModel();
if(m_List) {
m_List->disconnect();
}
m_List = newList;
if(!m_List) {
endResetModel();
return;
}
connect(m_List, &ToDoList::preItemAppended, this, [=]() {
const int index = m_List->items().size();
beginInsertRows(QModelIndex(), index, index);
});
connect(m_List, &ToDoList::postItemAppended, this, [=]() {
endInsertRows();
});
connect(m_List, &ToDoList::preItemRemoved, this, [=](int index) {
beginRemoveRows(QModelIndex(), index, index);
});
connect(m_List, &ToDoList::postItemRemoved, this, [=]() {
endRemoveRows();
});
endResetModel();
}
#########################################################################
The View File: ToDoList.qml
Displays the data provided by the c++ classes
============================================
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.3
import ToDo 1.0
ColumnLayout {
Frame {
Layout.fillWidth: true
ListView {
implicitWidth: 250
implicitHeight: 250
clip: true
anchors.fill: parent
model: TodoModel {
list: toDoList
}
delegate: RowLayout {
width: parent.width
CheckBox {
checked: model.done
onClicked: model.done=checked
}
TextField {
text: model.description
onEditingFinished: model.desciption = text
Layout.fillWidth: true
}
}
}
}
RowLayout {
Button {
text: qsTr("Add new item")
onClicked: toDoList.appendItem()
Layout.fillWidth: true
}
Button {
text: qsTr("Remove Completed Items")
onClicked: toDoList.removeCompletedItems()
Layout.fillWidth: true
}
}
}
Whenever the Model changes we should notify the View. Refer to this link:
QML views are automatically updated when the model changes. Remember
the model must follow the standard rules for model changes and notify
the view when the model has changed by using
QAbstractItemModel::dataChanged(),
QAbstractItemModel::beginInsertRows(), and so on. See the Model
subclassing reference for more information.
Here's how you can enhance your example to achieve a similar result:
On click of Fetch data button, after 3 seconds, the first row's description changes to https.
todolist.h:
signals:
void updateData();
public slots:
void fetchData();
todolist.cpp:
void ToDoList::fetchData()
{
QTimer::singleShot(3000, (QObject*)this, SIGNAL(updateData()));
}
todomodel.cpp:
connect(mList, &ToDoList::postItemRemoved, this, [=]() {
endRemoveRows();
});
connect(mList, &ToDoList::updateData, this, [=]() {
QVariant value = "https";
QModelIndex index = createIndex(0,0);
setData(index, value, DescriptionRole);
});
ToDoList.qml:
Button {
text: qsTr("Remove completed")
onClicked: toDoList.removeCompletedItems()
Layout.fillWidth: true
}
Button {
text: qsTr("Fetch data")
onClicked: toDoList.fetchData()
Layout.fillWidth: true
}
I have now fixed my issue thanks to #ArunKumarB.
The main tip from #ArunKumarB's comment was QModelIndex index = createIndex(0,0);
That is, how to convert a row index to a QModelIndex object.
The rest was mostly plumbing.

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

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
}

Bind CheckBox checked-state in TableView to custom model attribute

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