I am trying to make the model QAbstractTableModel
in cpp and connect to qml.
This code works well.
MyModel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractTableModel>
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
explicit MyModel(QObject *parent = nullptr);
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Animal> m_animals;
};
#endif // MYMODEL_H
MyModel.cpp
#include "MyModel.h"
#include <QDebug>
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent)
{
qDebug() << __FUNCTION__;
addAnimal(Animal("Wolf", "Medium"));
addAnimal(Animal("Polar bear", "Large"));
addAnimal(Animal("Quoll", "Small"));
}
int MyModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_animals.size();
}
int MyModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 2;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
qDebug() << __FUNCTION__ << index.row() << index.column() << role;
if (!index.isValid())
return QVariant();
const Animal &animal = m_animals[index.row()];
switch (role) {
case TypeRole:
return animal.type();
case SizeRole:
return animal.size();
default:
break;
}
return QVariant();
}
void MyModel::addAnimal(const Animal &animal)
{
qDebug() << __FUNCTION__;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_animals << animal;
endInsertRows();
}
QHash<int, QByteArray> MyModel::roleNames() const
{
qDebug() << __FUNCTION__;
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MyModel model;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main_test.qml
import QtQuick 2.0
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: main_view
width: 250
height: 600
visible: true
ListView {
id: list_view
width: 200; height: 250
model: myModel
delegate: Text { text: "Animal Test: " + type + ", " + size }
}
TableView {
id: table_view
objectName: "tableView"
width: 250; height: 250
anchors.top: list_view.bottom
model: myModel
TableViewColumn {
id: type_col
role: "type"
title: "Type"
width: 100
}
TableViewColumn {
id: size_col
role: "size"
title: "Size"
width: 100
}
}
}
It looks like this
But, if I change the main.cpp a little bit, the list view dispaly as normal, but not the table view.
main.cpp
#include "MainView.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MainView mainView;
return app.exec();
}
MainView.h
#ifndef MAINVIEW_H
#define MAINVIEW_H
#include <QObject>
#include <QQmlApplicationEngine>
class MainView: public QObject
{
Q_OBJECT
public:
explicit MainView(QObject *parent=nullptr);
void initializeView();
private:
QQmlApplicationEngine m_engine;
};
#endif // MAINVIEW_H
MainView.cpp
#include "MainView.h"
#include "MyModel.h"
#include <QQmlContext>
MainView::MainView(QObject *parent)
: QObject(parent)
{
initializeView();
}
void MainView::initializeView()
{
MyModel model;
m_engine.rootContext()->setContextProperty("myModel", &model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
It looks like this.
I really don't understand why this happens. What the difference between ListView and TableView in the second case? And how to fix it, i.e. make data display in the second case? Thank in advance.
The problem is in initializeView
, model is a local variable in that scope, and every local variable is deleted from the memory when the function finishes executing. In the first case model will be eliminated when the application is closed, in the second case it will be eliminated when initializeView
ends that is when the window is displayed, there are 2 possible solutions:
MainView
as a parent so that he removes it from memory (this last is a feature of the QObject
s):void MainView::initializeView()
{
MyModel *model = new MyModel(this);
m_engine.rootContext()->setContextProperty("myModel", model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
*.h
#ifndef MAINVIEW_H
#define MAINVIEW_H
#include <QObject>
#include <QQmlApplicationEngine>
class MainView: public QObject
{
Q_OBJECT
public:
explicit MainView(QObject *parent=nullptr);
void initializeView();
private:
QQmlApplicationEngine m_engine;
MyModel model{this};
};
#endif // MAINVIEW_H
*.cpp
...
void MainView::initializeView()
{
m_engine.rootContext()->setContextProperty("myModel", &model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
To understand the behavior I have added more points of impression:
MyModel::~MyModel()
{
qDebug()<<"destructor";
}
TableView {
...
Component.onCompleted: {
console.log("completed table")
if(!timer.running)
timer.running = true
}
}
ListView {
...
Component.onCompleted: {
console.log("completed list")
if(!timer.running)
timer.running = true
}
}
Timer {
id: timer
interval: 0;
onTriggered: console.log(myModel, table_view.model, list_view.model)
}
And I get the following:
MyModel
addAnimal
addAnimal
addAnimal
roleNames
data 0 0 257
data 0 0 258
data 1 0 257
data 1 0 258
data 2 0 257
data 2 0 258
roleNames
qml: completed list
data 0 0 257
data 0 0 258
qml: completed table
destructor
qml: null null null
We note that ListView
manages to load the data, whereas TableView
in the middle of the load is called the destructor.
Possible explanation: I think that the ListView
stores the data making a copy of them and only updates when the model notifies, should also be notified when the model is deleted to clean the data, it seems that this is a bug. On the other hand, TableView
, being at the moment of loading and deleting the model, is null, so it is notified and cleans the data.
Doing another test:
void MainView::initializeView()
{
MyModel *model = new MyModel;
m_engine.rootContext()->setContextProperty("myModel", model);
m_engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QTimer::singleShot(1000, model, &MyModel::deleteLater);
}
It is observed that both load the data correctly, and after a second the model is destroyed, but the one that is only notified is the TableView
since it is the only one that cleans the data shown, and ListView
does not.
my conclusion that it is a ListView
bug.