HorizontalHeaderView not calling headerData() of QAbstractTableModel's child - c++

According to HorizontalHeaderView's doc, If the model is a QAbstractTableModel, then the header will display the model's horizontal headerData(); otherwise, the model's data(). but It's not even calling it in my case. but QTableView from widgets module seems to work just fine.
here is my main.qml:-
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
id: root
HorizontalHeaderView {
id: header
syncView: tb
anchors.top: parent.top
// model: KoolModel // specify model explicitly also does not work
delegate: Button {
text: display
}
}
TableView {
id: tb
width: parent.width
height: parent.height - header.height
anchors.top: header.bottom
onWidthChanged: forceLayout()
model: KoolModel
columnWidthProvider: function (_) {
return root.width / 3
}
delegate: Frame {
Label {
text: display
anchors.centerIn: parent
}
}
}
}
here is my model:-
#include <QApplication>
#include <QGuiApplication>
#include <QTableView>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QAbstractTableModel>
#include <QString>
#include <map>
class Exams : public QAbstractTableModel {
private:
struct DS {
QString title;
unsigned int marks;
int state;
std::vector<int>* questions = nullptr;
};
//id and exams
std::map<int, DS> exams;
public:
Exams()
{
for (int i = 0; i < 10; i++) {
DS exam { "Exam" + QString::number(i), 0, (i * 3) / 2, nullptr }; // fill with garbage data for now
exams[i] = exam;
}
}
int rowCount(const QModelIndex& parent) const override
{
return exams.size();
}
int columnCount(const QModelIndex& parent) const override
{
return 3;
}
QVariant data(const QModelIndex& index, int role) const override
{
if (role == Qt::DisplayRole) {
if (index.column() == 0)
return exams.at(index.row()).title;
else if (index.column() == 1)
return exams.at(index.row()).marks;
else if (index.column() == 2)
return exams.at(index.row()).state;
}
qDebug() << "oops";
return QVariant();
}
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
{
qDebug() << "headerData is at least called";
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case 0:
return QString("Title");
case 1:
return QString("Marks");
case 2:
return QString("state");
}
}
return QVariant();
}
QHash<int, QByteArray> roleNames() const override
{
return { { Qt::DisplayRole, "display" } };
}
};
int main(int argc, char* argv[])
{
Exams exams;
// widgets seems to work
// QApplication app(argc, argv);
// QTableView widget;
// widget.setModel(&exams);
// widget.show();
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("KoolModel", &exams);
const QUrl url(QStringLiteral("qrc:/main.qml"));
engine.load(url);
return app.exec();
}

The problem is caused by a naming conflict between the Button's display property and the role. The solution is to access the role through the model explicitly:
// ...
delegate: Button {
text: model.display
}
// ...

Related

Qml ListView from C++ QAbstractListModel with QThread processing

I found a QML ListView sample using a C++ QAbstractListModel. However, it took a while to fetch the list model's data and waiting popup was freezing. So, I tried to use QThread samples (a, b, c) in the cpu intensive task sample.
Then, I got the below errors, when a different thread (ThreadHandler::process()) tried to fetch the model data in the main thread (PersonModel::requestPersonData()).
QObject::connect: Cannot queue arguments of type 'QQmlChangeSet'
(Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)
My question is how to add data into the QAbstractListModel from the different QThread's function. Or is there any way to handle the time consuming list model due to the large data?
Here is the reproducible code. (Qt 5.12.10 MSVC2015 64bit, Qt Creator 4.14.2. windows 10)
Thanks in advance.
personmodel.h
#ifndef PERSONMODEL_H
#define PERSONMODEL_H
#include <QAbstractListModel>
struct Person
{
QString First;
QString Last;
};
class PersonModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
explicit PersonModel(QObject *parent = nullptr);
virtual ~PersonModel();
enum PersonRoles {
FirstRole = Qt::UserRole + 1,
LastRole
};
Q_ENUM(PersonRoles)
void addPerson(Person *p);
Person* getPerson(int index);
void clear();
int count() const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual QHash<int, QByteArray> roleNames() const override;
public slots:
void requestPersonData(void);
void requestDeleteAll();
bool remove(const int inIndex);
signals:
void countChanged();
private:
QHash<int, QByteArray> _roles;
QList<Person *> _people;
};
#endif // PERSONMODEL_H
personmodel.cpp
#include "personmodel.h"
#include "threadhandler.h"
#include <QThread>
#include <QWaitCondition>
#include <QDebug>
#include <time.h>
PersonModel::PersonModel(QObject *parent)
: QAbstractListModel(parent)
{
_roles[FirstRole] = "first";
_roles[LastRole] = "last";
}
PersonModel::~PersonModel()
{
_people.clear();
}
int PersonModel::count() const
{
return rowCount();
}
int PersonModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return _people.count();
}
QVariant PersonModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= _people.count())
return QVariant();
Person *p = _people.at(index.row());
switch (role) {
case FirstRole:
return QVariant::fromValue(p->First);
case LastRole:
return QVariant::fromValue(p->Last);
default:
break;
}
return QVariant();
}
QHash<int, QByteArray> PersonModel::roleNames() const
{
return _roles;
}
void PersonModel::clear()
{
qDeleteAll(_people);
_people.clear();
}
Person *PersonModel::getPerson(int index)
{
return _people.at(index);
}
void PersonModel::addPerson(Person *p)
{
int row = _people.count();
beginInsertRows(QModelIndex(), row, row);
_people.append(p);
endInsertRows();
}
bool PersonModel::remove(const int inIndex)
{
if ((rowCount() <= 0) || (inIndex < 0) || (rowCount() <= inIndex))
return false;
beginRemoveRows(QModelIndex(), inIndex, inIndex);
_people.removeAt(inIndex);
endRemoveRows();
return true;
}
void PersonModel::requestPersonData()
{
QThread* pPThread = new QThread();
ThreadHandler* pHandler = new ThreadHandler();
pHandler->setListModel(this);
pHandler->moveToThread(pPThread);
connect(pPThread, SIGNAL(started()), pHandler, SLOT(process()));
connect(pHandler, SIGNAL(finished()), this, SIGNAL(countChanged()));
// Automatically delete pPThread and pHandler after the work is done.
connect(pHandler, SIGNAL(finished()), pHandler, SLOT(deleteLater()), Qt::QueuedConnection);
connect(pPThread, SIGNAL(finished()), pPThread, SLOT(deleteLater()), Qt::QueuedConnection);
pPThread->start();
}
void PersonModel::requestDeleteAll()
{
for (int i = rowCount() - 1; i >= 0; i--)
{
remove(i);
}
}
threadhandler.h
#ifndef THREADHANDLER_H
#define THREADHANDLER_H
#include <QObject>
class PersonModel;
class ThreadHandler : public QObject
{
Q_OBJECT
public:
explicit ThreadHandler(QObject *parent = nullptr);
void setListModel(PersonModel* personModel);
public slots:
void process();
signals:
void finished();
private:
void doPrimes();
int calculatePrimes(int inRepeat);
private:
PersonModel* m_personModel;
};
#endif // THREADHANDLER_H
threadhandler.cpp
#include "threadhandler.h"
#include "personmodel.h"
#include <QDebug>
#include "time.h"
ThreadHandler::ThreadHandler(QObject *parent) : QObject(parent)
{
}
void ThreadHandler::setListModel(PersonModel *personModel)
{
m_personModel = personModel;
}
void ThreadHandler::process()
{
qDebug() << Q_FUNC_INFO << "thread handler starts";
// simulate the cpu intensive procedure such as db query or 3rd party library call
calculatePrimes(3);
int row = 5;//rowCount();
QString strLastNameIndex;
for (int i = 0; i < row; i++)
{
strLastNameIndex = QString::number(i);
Person* person3 = new Person();
person3->First = "Bob" + strLastNameIndex;
person3->Last = "LastName" + strLastNameIndex;
m_personModel->addPerson(person3);
}
qDebug() << Q_FUNC_INFO << "row count: " << row;
emit finished();
qDebug() << Q_FUNC_INFO << "thread handler ends";
}
#define MAX_PRIME 100000
void ThreadHandler::doPrimes()
{
unsigned long i, num, primes = 0;
for (num = 1; num <= MAX_PRIME; ++num) {
for (i = 2; (i <= num) && (num % i != 0); ++i);
if (i == num)
++primes;
}
printf("Calculated %ld primes.\n", primes);
}
int ThreadHandler::calculatePrimes(int inRepeat)
{
time_t start, end;
time_t run_time;
start = time(NULL);
for (int i = 0; i < inRepeat; ++i) {
doPrimes();
}
end = time(NULL);
run_time = (end - start);
printf("This machine calculated all prime numbers under %d %d times "
"in %lld seconds\n", MAX_PRIME, inRepeat, run_time);
return run_time;
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQml 2.0
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
onClicked: {
timer.start()
// loadingDialog.is_running = true
console.log("personListView click to request data from qml to C++")
PersonModel.requestDeleteAll();
PersonModel.requestPersonData();
}
}
ListView {
id: personListView
width: 150; height: 400
visible: loadingDialog.is_running === true ? false : true;
model: PersonModel
delegate: personDelegate
Component.onCompleted: {
console.log(PersonModel, model)
console.log(PersonModel.count, model.count)
}
onCountChanged: {
// console.log("personListView onCountChanged getting person data is finished.")
// console.log("personListView onCountChanged after search model count: " + PersonModel.count)
loadingDialog.is_running = false
}
}
Component {
id: personDelegate
Rectangle {
width: personListView.width
height: 30
color: "lightgreen"
Text {
text: model.first + " " + model.last
}
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
personListView.currentIndex = model.index
console.log("personListView ListView item" + model.index + " is clicked.")
PersonModel.remove(model.index);
}
}
}
}
Timer {
id: timer
interval: 100
repeat: false
running: false
triggeredOnStart: false
onTriggered: {
loadingDialog.is_running = true
timer.stop()
}
}
Rectangle {
id: loadingDialog
width: 50; height: 50
color: "red"
property bool is_running: false
visible: is_running;
NumberAnimation on x {
from: 0
to: 250;
duration: 3000
loops: Animation.Infinite
running: loadingDialog.is_running
}
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext> // setContextProperty()
#include "personmodel.h"
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
PersonModel mymodel;
// Add initial data for list model
Person person1;
person1.First = "Bob";
person1.Last = "One";
Person person2;
person2.First = "Bob2";
person2.Last = "Two";
mymodel.addPerson(&person1);
mymodel.addPerson(&person2);
engine.rootContext()->setContextProperty("PersonModel", &mymodel);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
Since the model is related to the view then you cannot modify it directly from another thread. In this case it is better to create a signal that sends the information to the model:
threadhandler.h
signals:
void finished();
void sendPerson(Person *person);
threadhandler.cpp
void ThreadHandler::process()
{
qDebug() << Q_FUNC_INFO << "thread handler starts";
// simulate the cpu intensive procedure such as db query or 3rd party library call
calculatePrimes(3);
int row = 5;//rowCount();
QString strLastNameIndex;
for (int i = 0; i < row; i++)
{
strLastNameIndex = QString::number(i);
Person* person3 = new Person;
person3->First = "Bob" + strLastNameIndex;
person3->Last = "LastName" + strLastNameIndex;
Q_EMIT sendPerson(person3);
}
qDebug() << Q_FUNC_INFO << "row count: " << row;
emit finished();
qDebug() << Q_FUNC_INFO << "thread handler ends";
}
personmodel.cpp
void PersonModel::requestPersonData()
{
QThread* pPThread = new QThread();
ThreadHandler* pHandler = new ThreadHandler();
// pHandler->setListModel(this);
pHandler->moveToThread(pPThread);
connect(pPThread, &QThread::started, pHandler, &ThreadHandler::process);
connect(pHandler, &ThreadHandler::finished, this, &PersonModel::countChanged);
connect(pHandler, &ThreadHandler::sendPerson, this, &PersonModel::addPerson);
// Automatically delete pPThread and pHandler after the work is done.
connect(pHandler, &ThreadHandler::finished, pHandler, &QObject::deleteLater, Qt::QueuedConnection);
connect(pPThread, &QThread::finished, pPThread, &QObject::deleteLater, Qt::QueuedConnection);
pPThread->start();
}
Remove the setListModel method and everything related to that method.

Qt 5.11 - QML - show XML file in treeView as QAbstractItemModel

I'm trying to show a simple XML file in a QML treeView through the QAbstractItemModel class, but I can't find any decent tutorial on how to do so online. The best I managed to find is a tutorial about how to do it in QWidget, but I want to do it in QML. I tried anyway with all the bits I could find here and there but couldn't even manage to get a tree to show up.
Code compiles, but my model is null. I'm not even sure if I'm actually reading my XML file.
There's very likely a lot of things I am missing or doing wrong. Can anyone help ?
DomItem.h
#ifndef DOMITEM_H
#define DOMITEM_H
#include <QDomNode>
#include <QHash>
class DomItem
{
public:
DomItem(const QDomNode &node, int row, DomItem *parent = nullptr);
~DomItem();
DomItem *child(int i);
DomItem *parent();
QDomNode node() const;
int row() const;
private:
QDomNode domNode;
QHash<int, DomItem *> childItems;
DomItem *parentItem;
int rowNumber;
};
#endif // DOMITEM_H
DomItem.cpp
#include "DomItem.h"
DomItem::DomItem(const QDomNode &node, int row, DomItem *parent)
: domNode(node),
parentItem(parent),
rowNumber(row)
{
}
DomItem::~DomItem()
{
qDeleteAll(childItems);
}
DomItem *DomItem::parent()
{
return parentItem;
}
int DomItem::row() const
{
return rowNumber;
}
QDomNode DomItem::node() const
{
return domNode;
}
DomItem *DomItem::child(int i)
{
DomItem *childItem = childItems.value(i);
if (childItem)
return childItem;
// if child does not yet exist, create it
if (i >= 0 && i < domNode.childNodes().count()) {
QDomNode childNode = domNode.childNodes().item(i);
childItem = new DomItem(childNode, i, this);
childItems[i] = childItem;
}
return childItem;
}
DomModel.h
#ifndef DOMMODEL_H
#define DOMMODEL_H
#include <QObject>
#include <QModelIndex>
#include <QAbstractItemModel>
#include <QDomDocument>
#include <QFile>
#include "DomItem.h"
class DomModel : public QAbstractItemModel
{
Q_OBJECT
public:
enum DomModelRoles
{
DomModelRoleName = Qt::UserRole + 1,
DomModelRoleAttributes,
DomModelRoleValue
};
explicit DomModel(const QDomDocument &document, QObject *parent = nullptr);
~DomModel();
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
void loadXml(QString xmlFilePath);
void reload();
QHash<int, QByteArray> roleNames() const override;
private:
QDomDocument domDocument;
DomItem *rootItem;
QString xmlSource;
};
#endif // DOMMODEL_H
DomModel.cpp
#include "DomModel.h"
DomModel::DomModel(const QDomDocument &document, QObject *parent)
: QAbstractItemModel(parent),
domDocument(document)
{
rootItem = new DomItem(domDocument, 0);
}
DomModel::~DomModel()
{
delete rootItem;
}
int DomModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 3;
}
Qt::ItemFlags DomModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return QAbstractItemModel::flags(index);
}
QVariant DomModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case 0:
return tr("Name");
case 1:
return tr("Attributes");
case 2:
return tr("Value");
default:
break;
}
}
return QVariant();
}
QModelIndex DomModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
DomItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<DomItem*>(parent.internalPointer());
DomItem *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
return QModelIndex();
}
int DomModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
DomItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<DomItem*>(parent.internalPointer());
return parentItem->node().childNodes().count();
}
QModelIndex DomModel::parent(const QModelIndex &child) const
{
if (!child.isValid())
return QModelIndex();
DomItem *childItem = static_cast<DomItem*>(child.internalPointer());
DomItem *parentItem = childItem->parent();
if (!parentItem || parentItem == rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
QVariant DomModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
const DomItem *item = static_cast<DomItem*>(index.internalPointer());
const QDomNode node = item->node();
switch (role)
{
case DomModelRoleName:
return node.nodeName();
case DomModelRoleAttributes:
{
const QDomNamedNodeMap attributeMap = node.attributes();
QStringList attributes;
for (int i = 0; i < attributeMap.count(); ++i) {
QDomNode attribute = attributeMap.item(i);
attributes << attribute.nodeName() + "=\""
+ attribute.nodeValue() + '"';
}
return attributes.join(' ');
}
case DomModelRoleValue:
return node.nodeValue().split('\n').join(' ');
default:
break;
}
return QVariant();
}
void DomModel::loadXml(QString xmlFilePath)
{
xmlFilePath.replace("file:///","");
// on parse le xml de l'environnement
QFile file(xmlFilePath);
if (!file.open(QIODevice::ReadOnly| QFile::Text)){
//QMessageBox::critical(this, "ParserFichierXML", "Error");
}
else{
domDocument.clear();
if (!domDocument.setContent(&file)) {
//QMessageBox::critical(this, "ParserFichierXML", "Error");
}
file.close();
}
reload();
}
void DomModel::reload()
{
beginResetModel();
delete rootItem;
rootItem = new DomItem(domDocument, 0,false);
endResetModel();
}
QHash<int, QByteArray> DomModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[DomModelRoleName] = "Name";
roles[DomModelRoleAttributes] = "Attributes";
roles[DomModelRoleValue] = "Value";
return roles;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QDomDocument>
#include <QQmlContext>
#include "DomModel.h"
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
QQmlContext *ctx = engine.rootContext();
QFile file("./test.xml");
if (file.open(QIODevice::ReadOnly))
{
QDomDocument document;
if (document.setContent(&file))
{
DomModel *model = new DomModel(document);
ctx->setContextProperty("theModel", model);
}
}
file.close();
engine.load(url);
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
import QtLocation 5.3
import QtQuick.Controls 1.4
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.4
import QtQml 2.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
TreeView
{
id:treeView
anchors.fill: parent
model: theModel
itemDelegate: TreeDelegate
{
id: treeDeleg
}
style: TreeViewStyle
{
id: styleTree
}
TableViewColumn
{
id:column1
role: "Name"
title: "Name"
width: 450
}
TableViewColumn
{
id:column3
role: "Value"
title: "Value"
width: 400
}
Component.onCompleted:
{
treeView.model = Qt.binding(function()
{
if (typeof theModel !== "undefined")
{
console.log("Model loaded");
return theModel;
}
else
{
console.log("Couldn't load model");
return null;
}
});
}
}
}
TreeDelegate.qml
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import QtQuick 2.7
import QtQml 2.0
Item
{
id: item
width : parent.width
height: 25
TextEdit
{
id: text1
font.pixelSize: 14
readOnly: true
focus: false
width : parent.width
height: 25 * lineCount
anchors.left: parent.left
wrapMode: TextEdit.Wrap
text: styleData.value
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
}
}

How to get TableView with exapanding columns

I'm trying to get TableView (the new one from pure qml introdudced in qt5.12) with column which resize them dynamically to fill extra available space but It's not working
here is my minimal reproducible example:-
main.qml:-
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
Window {
id: window
width: 640
height: 480
visible: true
title: qsTr("Hello World")
TableView {
anchors.fill: parent
model: KoolModel
// columnWidthProvider: function (_) { return parent.width/3} // Method 1, Does not work
delegate: Frame {
// implicitWidth: parent.width/3 //Method 2, Does not work
implicitWidth: 640/3 // works but not the solution for my problem
Label {
text: display
anchors.centerIn: parent
}
}
}
}
If I execute this code, I get following:-
it is the desired output but when I resize the window the columns should expand which does not happen due to hardcoding the implicitWidth
if I use columnWidthProvider: function (_) { return parent.width/3} then I get nothing, just a white window:-
and If I try to use binding like this implicitWidth: parent.width/3 I get following:-
main.cpp:-
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "models/exams.hpp"
int main(int argc, char* argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Models::Exams exams;
engine.rootContext()->setContextProperty("KoolModel", &exams);
const QUrl url(QStringLiteral("qrc:/main.qml"));
engine.load(url);
return app.exec();
}
models/exam.cpp
#pragma once
#include <QAbstractTableModel>
#include <QString>
#include <map>
namespace Models {
class Exams : public QAbstractTableModel {
private:
struct DS {
QString title;
unsigned int marks;
int state;
std::vector<int>* questions = nullptr;
};
//id and exams
std::map<int, DS> exams;
public:
Exams();
// QAbstractItemModel interface
int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role) const override;
// QAbstractItemModel interface
public:
QHash<int, QByteArray> roleNames() const override;
};
} //end namespace Models
models/exam.cpp:-
#include "exams.hpp"
namespace Models {
Exams::Exams()
{
for (int i = 0; i < 200; i++) { //fill garbage data for now
DS exam {
"Exam" + QString::number(i),
0,
(i * 3) / 2,
nullptr
};
exams[i] = exam;
}
exams[2] = {
"Exam" + QString::number(10000000324),
0,
10,
nullptr
};
}
int Exams::rowCount(const QModelIndex& parent) const
{
return exams.size();
}
int Exams::columnCount(const QModelIndex& parent) const
{
return 3;
}
QVariant Exams::data(const QModelIndex& index, int role) const
{
if (role == Qt::DisplayRole) {
if (index.column() == 0)
return exams.at(index.row()).title;
else if (index.column() == 1)
return exams.at(index.row()).marks;
else if (index.column() == 2)
return exams.at(index.row()).state;
}
return QVariant();
}
QHash<int, QByteArray> Exams::roleNames() const
{
return { { Qt::DisplayRole, "display" } };
}
} // end namepsace Models
I'm using Qt 5.15 from Archlinux repos
Restating my comment as an answer: For performance reasons, TableView does not re-calculate its row height or column width unless absolutely necessary. But you can force it by calling forceLayout() whenever the width changes.
onWidthChanged: forceLayout()

QT qml model within a model? and accesible via qml

Is it possible to have a model within a model accessible via qml and each model are different classes
eg modelclass 1 has a vector of a subclass of model
i.e parent model can have many sub model
I tried to return a QAbstractItemModel as a role of QAbstractItemModel parent but it seems thats not possible any ideas of how to do these stuff??
[Edit 1]
The code :
message.h
#include <QAbstractListModel>
#include <QStringList>
//![0]
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]
};
//![2]
model.h
#include <QAbstractListModel>
#include <QStringList>
#include <QDebug>
#include <message.h>
//![0]
//!
//!
class lister{
public:
int id;
AnimalModel *list=nullptr;
void addlist(){
list->addAnimal(Animal("Wolf", "Medium"));
list->addAnimal(Animal("Polar bear", "Large"));
list->addAnimal(Animal("Quoll", "Small"));
}
};
class TestModel : public QAbstractListModel
{
public:
enum EventRoles {
ModelRole = Qt::UserRole + 1,
IdRole = Qt::UserRole + 2
};
TestModel()
{
m_roles[ ModelRole] = "mm";
m_roles[ IdRole] = "id";
// setRoleNames(m_roles);
}
int rowCount(const QModelIndex & = QModelIndex()) const
{
return mylist.count();
}
QVariant data(const QModelIndex &index, int role) const
{
if(role == IdRole)
{
return mylist.at(index.row()).id;
}
else if(role == ModelRole)
{
return QVariant::fromValue(mylist.at(index.row()).list);
}
return 0;
}
void addData(){
static int a=0;
qDebug()<<a<<"value";
beginInsertRows(QModelIndex(), rowCount(), rowCount());
lister temp;
temp.id=a;
temp.addlist();
a++;
mylist<<temp;
temp.id=a;
temp.addlist();
a++;
mylist<<temp;
endInsertRows();
}
QHash<int, QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[ModelRole] = "mm";
roles[IdRole] = "id";
return roles;
}
QList<lister> mylist;
QHash<int, QByteArray> m_roles;
};
message.cpp
#include "message.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);
TestModel model;
// model.addAnimal(Animal("Wolf", "Medium"));
// model.addAnimal(Animal("Polar bear", "Large"));
// model.addAnimal(Animal("Quoll", "Small"));
model.addData();
model.addData();
model.addData();
qRegisterMetaType<AnimalModel*>("AnimalModel*" );
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", &model);
//![0]
view.setSource(QUrl("qrc:view.qml"));
view.show();
return app.exec();
}
view.qml
import QtQuick 2.0
import QtQml 2.0
ListView {
anchors.fill: parent
model: myModel //this is your main model
delegate:
Rectangle {
height: 100
width: 100
color: "red"
Text {
id: cc
text: id
}
ListView {
anchors.fill: parent
model: mm //the internal QVariantList
delegate: Rectangle {
anchors.right: parent.right
width: 50
height: 50
color: "green"
border.color: "black"
Text {
text: type //role to get data from internal model
}
}
}
}
}
Yes its fine.
You need to return a pointer to your sub model object wrapped in a variant,
QVariant::fromValue(&subModel)
You probably also need to register your model pointer with Metatype system using
qRegisterMetaType<MySubModelClass*>("MySubModelClass*" );
Here is an implementation with DelegateModel introducet in Qt 5.13
import QtQuick 2.12
import QtQuick.Window 2.12
import com.example 1.0
import QtQml.Models 2.12
import QtQuick.Layouts 1.3
Window {
visible: true
width: 800
height: 480
title: qsTr("Hello World")
DelegateModel
{
id: delegateModel
model : MyModel{}
delegate:ColumnLayout
{
width: parent.width
Row
{
Text
{
text: qsTr("Word:")
color: "red"
}
Text
{
text: word
}
}
Text
{
color: "red"
text: qsTr("In Other Languages:")
}
ListView {
model: translations
height: 50
delegate:
Rectangle
{
height: 20
width: 100
Text {
anchors.right: parent.right
text: translations[index]
}
}
}
}
}
ListView
{
model : delegateModel
anchors.centerIn: parent
width: parent.width/2
height: parent.height
}
}
MyModel.h
#include <QAbstractListModel>
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
enum WordRoles
{
WordRole = Qt::UserRole+1,
TranslationsModelRole
};
explicit MyModel(QObject* parent=nullptr);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
private:
struct Word
{
QString word;
QStringList translations;
};
QList<Word> m_words;
};
MyModel.cpp
#include<MyModel.h>
#include <QList>
MyModel::MyModel(QObject *parent)
:QAbstractListModel(parent)
{
m_words={{"apple",{"elma","Apfel"}},
{"ball",{"top","ball"}},
{"friend",{"arkadas","freund"}}
};
}
int MyModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_words.size();
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if(index.row()>=m_words.size() || index.row()<0)
return QVariant();
if ( role == WordRole)
{
return m_words[index.row()].word;
}
else if ( role == TranslationsModelRole)
{
return m_words[index.row()].translations;
}
return QVariant();
}
QHash<int, QByteArray> MyModel::roleNames() const
{
QHash<int,QByteArray> roles;
roles[WordRoles::WordRole] = "word";
roles[WordRoles::TranslationsModelRole]="translations";
return roles;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <MyModel.h>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<MyModel>("com.example",1,0,"MyModel");
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
Result is ListView inside ListViev:

SqlQueryModel generic model - Role Names not setting right

I have been trying to use the SqlQueryModel generic model from the qt documentation but I just can't seem to get it working properly with qml.
very simular to another problem I saw on Stack OverFlow but they haven't got an answer yet either
My QSqlQueryModel doesn't show data in the listview
I can query the database and I get the right amount of results in my listview but if I try to access a property via the RoleName then it says the property is undefined
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "wordmodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
WordModel *wordListModel = new WordModel( qApp);
engine.rootContext()->setContextProperty("WLModel", wordListModel);
engine.load(QUrl(QStringLiteral("qrc:///qml/main.qml")));
return app.exec();
}
SqlQueryModel.h
#ifndef SQLQUERYMODEL_H
#define SQLQUERYMODEL_H
#include <QSqlQueryModel>
#include <QHash>
#include <QByteArray>
class SqlQueryModel : public QSqlQueryModel
{
Q_OBJECT
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole ) const;
inline RoleNameHash roleNames() const { return *roles; }
public:
explicit SqlQueryModel(QObject *parent = 0);
Q_INVOKABLE void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase());
Q_INVOKABLE void setQuery();
QHash<int, QByteArray> generateRoleNames();
//inline RoleNameHash roleNames() const { return *roles; }
QHash<int, QByteArray> roleNames() const{return roles;}
signals:
void countChanged();
public slots:
void connectToDb();
void closeDB();
private:
//QSqlQuery SQL_SELECT;
QHash<int, QByteArray> roles;
QSqlDatabase mydb;
};
#endif // SQLQUERYMODEL_H
SqlQueryModel.cpp
#include "sqlquerymodel.h"
#include <QSqlRecord>
#include <QSqlField>
#include <QDebug>
SqlQueryModel::SqlQueryModel(QObject *parent) :
QSqlQueryModel(parent)
{
mydb=QSqlDatabase::addDatabase("QSQLITE");
QString dbPath = "E://Qt//sqlite//dictionary.db";
mydb.setDatabaseName(dbPath);
mydb.setUserName("admin");
mydb.setPassword("admin");
}
void SqlQueryModel::connectToDb()
{
if(!mydb.open())
{
qDebug() << "Database didnt open";
}
else
{
qDebug() << "Your database is open";
}
}
void SqlQueryModel::closeDB()
{
mydb.close();
}
void SqlQueryModel::setQuery(const QString &query, const QSqlDatabase &db)
{
connectToDb();
QSqlQueryModel::setQuery("SELECT * FROM words_en WHERE word LIKE 'young%' LIMIT 10",mydb);
generateRoleNames();
closeDB();
}
void SqlQueryModel::setQuery()
{
connectToDb();
QSqlQueryModel::setQuery("SELECT * FROM words_en WHERE word LIKE 'young%' LIMIT 10");
generateRoleNames();
closeDB();
}
QHash<int, QByteArray> SqlQueryModel::generateRoleNames()
{
roles.clear();
for( int i = 0; i < record().count(); i++) {
roles[Qt::UserRole + i + 1] = record().fieldName(i).toLatin1();
qDebug() << roles[Qt::UserRole + i + 1];
}
return roles;
}
QVariant SqlQueryModel::data(const QModelIndex &index, int role) const
{
qDebug() << "ALALLALALALALALA";
QVariant value = QSqlQueryModel::data(index, role);
qDebug() << value;
if(role < Qt::UserRole)
{
value = QSqlQueryModel::data(index, role);
}
else
{
int columnIdx = role - Qt::UserRole - 1;
QModelIndex modelIndex = this->index(index.row(), columnIdx);
value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
}
return value;
}
main.qml
import QtQuick 2.2
import QtQuick.Window 2.1
Window {
visible: true
width: 800
height: 800
// here i tried putting the class into a Qml type with the qmlRegisterType()
// but also to no avail
// SqlQueryModel{
// id:something
// Component.onCompleted: {
// something.setQuery()
// }
// }
ListView{
width:parent.width
height:parent.height
model:wordListModel
delegate: Item{
width:parent.width
height: width/10
Text {
id: name
text: word
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.fill:parent
Component.onCompleted: {
console.log(id)
console.log(word)
}
}
}
}
}
Which version of Qt are you using. Method generateRoleNames() has been deprecated, reimplement roleNames() instead of generateRoleNames().