QTableView model out of memory - qt

I am getting an out of memory crash when using QTableView + QAbstractTableModel with a large number of items.
The problem seems to come from the vertical header view. It tries to allocate a QVector with same size as the row count (150 millions).
How can i use millions of rows in my model without crashing the table?
Here is a sample to reproduce the issue.
class MiniModel : public QAbstractTableModel
{
Q_OBJECT
public:
MiniModel(QObject * parent = nullptr);
int columnCount(QModelIndex const &parent = QModelIndex()) const override;
int rowCount(QModelIndex const &parent = QModelIndex()) const override;
QVariant data(QModelIndex const &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
};
MiniModel::MiniModel(QObject * parent) : QAbstractTableModel(parent)
{
}
int MiniModel::columnCount(QModelIndex const & parent) const
{
return 1;
}
int MiniModel::rowCount(QModelIndex const & parent) const
{
return 150000000;
}
QVariant MiniModel::data(QModelIndex const & index, int role) const
{
if (role == Qt::DisplayRole)
return 23; // just return 23 in all cells
else
return QVariant();
}
QVariant MiniModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (section == 0 && role == Qt::DisplayRole && orientation == Qt::Horizontal)
return "Dummy";
else
return QVariant();
}

Related

how to port a QAbstractItemModel to QtQuick (without defining ItemDelegate)

I have a QAbstractItemModel tree model for a Qt3D Entity tree:
qt3dentitytreemodel.h:
#define QT3DENTITYTREEMODEL_H
#include <QAbstractItemModel>
#include <Qt3DCore/QEntity>
class Qt3DEntityTreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
Q_PROPERTY(Qt3DCore::QEntity * rootEntity READ rootEntity WRITE setRootEntity NOTIFY rootEntityChanged)
explicit Qt3DEntityTreeModel(QObject *parent = nullptr);
void setRootEntity(Qt3DCore::QEntity *rootEntity);
Qt3DCore::QEntity * rootEntity() const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
signals:
void rootEntityChanged(Qt3DCore::QEntity *rootEntity);
private:
Qt3DCore::QEntity *rootEntity_;
};
#endif // QT3DENTITYTREEMODEL_H
qt3dentitytreemodel.cpp:
#include "qt3dentitytreemodel.h"
Qt3DEntityTreeModel::Qt3DEntityTreeModel(QObject *parent)
: QAbstractItemModel(parent)
{
}
void Qt3DEntityTreeModel::setRootEntity(Qt3DCore::QEntity *rootEntity)
{
qDebug() << "Qt3DEntityTreeModel::setRootEntity" << rootEntity;
auto old = rootEntity_;
rootEntity_ = rootEntity;
if(rootEntity_ != old)
emit rootEntityChanged(rootEntity);
}
Qt3DCore::QEntity * Qt3DEntityTreeModel::rootEntity() const
{
return rootEntity_;
}
QModelIndex Qt3DEntityTreeModel::index(int row, int column, const QModelIndex &parent) const
{
if(!rootEntity_) return {};
if(column != 0) return {};
if(!parent.isValid())
{
if(row == 0) return createIndex(0, 0, rootEntity_);
}
else
{
auto entity = reinterpret_cast<Qt3DCore::QEntity*>(parent.internalPointer());
if(!entity) return {};
return createIndex(row, column, entity->childNodes().at(row));
}
return {};
}
QModelIndex Qt3DEntityTreeModel::parent(const QModelIndex &child) const
{
if(!rootEntity_) return {};
if(!child.isValid()) return {};
auto entity = reinterpret_cast<Qt3DCore::QEntity*>(child.internalPointer());
if(!entity) return {};
auto parent = entity->parentNode();
if(!parent) return {};
auto grandParent = parent->parentNode();
if(!grandParent) return createIndex(0, 0, parent);
return createIndex(grandParent->childNodes().indexOf(parent), 0, parent);
}
QVariant Qt3DEntityTreeModel::data(const QModelIndex &index, int role) const
{
if(!rootEntity_) return {};
if(!index.isValid()) return {};
if(role != Qt::DisplayRole && role != DisplayRole) return {};
auto entity = reinterpret_cast<Qt3DCore::QEntity*>(index.internalPointer());
if(!entity) return {};
QString data = entity->metaObject()->className();
if(!entity->objectName().isEmpty())
data += QString(" \"%1\"").arg(entity->objectName());
return data;
}
QVariant Qt3DEntityTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
return {};
}
int Qt3DEntityTreeModel::rowCount(const QModelIndex &parent) const
{
if(!parent.isValid()) return 1;
auto entity = reinterpret_cast<Qt3DCore::QEntity*>(parent.internalPointer());
if(!entity) return 0;
return entity->childNodes().count();
}
int Qt3DEntityTreeModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
Qt::ItemFlags Qt3DEntityTreeModel::flags(const QModelIndex &index) const
{
return {};
}
which works fine when displayed in a QTreeView (QtWidgets):
auto treeView = new QTreeView();
auto treeModel = new Qt3DEntityTreeModel(treeView);
treeModel->setRootEntity(rootEntity);
treeView->setModel(treeModel);
treeView->resize(640, 480);
treeView->show();
however when used in a QtQuick Controls TreeView, I cannot see the item text, and I cannot even expand the root tree item:
Item {
...
Qt3DEntityTreeModel {
id: entityTreeModel
rootEntity: root
}
TreeView {
model: entityTreeModel
TableViewColumn {
title: "Name"
width: 200
}
}
Scene3D {
id: scene3d
Entity {
id: root
...
}
}
}
I also tried to explicitly define a role in Qt3DEntityTreeModel:
qt3dentitytreemodel.h:
enum Roles {
DisplayRole = Qt::UserRole + 1
};
QHash<int, QByteArray> roleNames() const override;
qt3dentitytreemodel.cpp:
QVariant Qt3DEntityTreeModel::data(const QModelIndex &index, int role) const
{
if(!rootEntity_) return {};
if(!index.isValid()) return {};
if(role != Qt::DisplayRole && role != DisplayRole) return {};
...
}
QHash<int, QByteArray> Qt3DEntityTreeModel::roleNames() const
{
QHash<int, QByteArray> result;
result.insert(DisplayRole, QByteArrayLiteral("display"));
return result;
}
and define the respective role property in the column of the QtQuick TreeView:
TreeView {
model: entityTreeModel
TableViewColumn {
title: "Name"
role: "display"
width: 200
}
}
but still shows nothing.
Is there something else to do to be able to use a custom abstract item model with QtQuick's TreeView?
I think I found the explanation: the model is created before the Qt3D entity tree is "ready".
If I create the model in C++ after the view is loaded, then set the model as a context property, it displays correctly in the QML's TreeView:
QQuickView view;
view.resize(500, 500);
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl("qrc:/main.qml"));
auto rootObject = view.rootObject();
auto rootEntity = rootObject->findChild<Qt3DCore::QEntity*>("theRootEntity");
qDebug() << "rootEntity:" << rootEntity;
auto treeModel = new Qt3DEntityTreeModel(&app);
treeModel->setRootEntity(rootEntity);
view.rootContext()->setContextProperty("theModel", treeModel);
view.show();
The fundamental problem is that my model doesn't monitor updates to the Qt3D entity tree, and it does not reflect such updates.

Qt QSortfilterproxy model virtual column

A related question has been asked by ymoreau here - but there is no decisive solution. I have subclassed QSortFilterProxyModel with the purpose to display some data in the virtual column on addition to QSqlRelationalTableModel (sourceModel) whose data is from MYSQL database.
Below is my code:
class vModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit vModel(QObject *parent): QSortFilterProxyModel(parent)
{
}
virtual QModelIndex index(int row, int column) const
{
if(column >= columnCount()-1){
return createIndex(row,column);
}
else
return QSortFilterProxyModel::index(row, column);
}
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const
{
Q_UNUSED(parent);
return sourceModel() ? (sourceModel()->columnCount() + 1) : 0;
}
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)
{
if (index.isValid() && role == Qt::EditRole)
{
QSortFilterProxyModel::setData(index,value,role);
emit dataChanged(index,index);
return true;
}
else
return false;
}
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const
{
int amtCol = columnCount()-1;
if (orientation == Qt::Horizontal
&& section == amtCol
&& role == Qt::DisplayRole){
return QString("Amount");
}
else
return QSortFilterProxyModel::headerData(section, orientation, role);
}
virtual QModelIndex parent(const QModelIndex &index) const {
Q_UNUSED(index);
return QModelIndex();
}
virtual QVariant data(const QModelIndex &index, int role) const
{
int amtCol = columnCount()-1;
if(index.column() == amtCol ){
if (role != Qt::DisplayRole)
return QSortFilterProxyModel::data(index, role);
else{
QString val = QString("Amount Index(%1,%2)").arg(index.row()).arg(index.column());
return val;
}
}
return QSortFilterProxyModel::data(index, role);
}
virtual QModelIndex mapFromSource(const QModelIndex &source) const
{
return index(source.row(), source.column());
}
virtual QModelIndex mapToSource(const QModelIndex &proxy) const
{
return (sourceModel()&&proxy.isValid())
? sourceModel()->index(proxy.row(), proxy.column(), proxy.parent())
: QModelIndex();
}
protected:
Qt::ItemFlags flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = QSortFilterProxyModel::flags(index);
if (index.isValid())
{
flags |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
}
return flags;
}
};
The view draws the extra column with correct header as I specified, but the content is empty even alternating row background is not painted. The model returns the correct data when I query the extra column indexes.The delegates returns false when I check the validity of any index in the virtual column yet the my subclassed model returns true.
Where's my problem...Is it the model or the delegates? Is there any additional function that I need to include?
Subclassing the Sourcemodel yielded my expectations so easily. I believe QIdentityProxyModel or any other proxy can do the same task but you have to man-handle so many issues. My Sourcemodel (the QSqlRelationalTableModel) responds to data changes (calculated values in more than 4 virtual columns) and communicates with the default relational-delegate so easily.
If there's away to make proxymodels do this task, I still welcome those suggestions. Am using Qt5.9

Creating custom QAbstractItemModel from comma seperated text file

I have this issue that my Qtableview gets extreme slowly after inserting 100.000 rows and specially using multiple select.
So after some reasearch I have decided to use my own model that inherits from QAbstractItemModel. But the issue is I have no clue how to use it since, the text file can change. fx. I can load a file with 5 columns and 50.000 rows, and after sometime it can have 15 columns and 10.000 rows.
the data does not need to be changed, so its only read only.
Anyone can help me with this problem?
My "empty" custom model is here
#include "customabstractmodel.h"
CustomAbstractModel::CustomAbstractModel(QObject *parent)
: QAbstractItemModel(parent)
{
}
QVariant CustomAbstractModel::headerData(int section, Qt::Orientation orientation, int role) const
{
// FIXME: Implement me!
}
bool CustomAbstractModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
if (value != headerData(section, orientation, role)) {
// FIXME: Implement me!
emit headerDataChanged(orientation, section, section);
return true;
}
return false;
}
QModelIndex CustomAbstractModel::index(int row, int column, const QModelIndex &parent) const
{
// FIXME: Implement me!
}
QModelIndex CustomAbstractModel::parent(const QModelIndex &index) const
{
// FIXME: Implement me!
}
int CustomAbstractModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return 0;
// FIXME: Implement me!
}
int CustomAbstractModel::columnCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return 0;
// FIXME: Implement me!
}
QVariant CustomAbstractModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
// FIXME: Implement me!
return QVariant();
}
bool CustomAbstractModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (data(index, role) != value) {
// FIXME: Implement me!
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
return false;
}
Qt::ItemFlags CustomAbstractModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable; // FIXME: Implement me!
}
bool CustomAbstractModel::insertRows(int row, int count, const QModelIndex &parent)
{
beginInsertRows(parent, row, row + count - 1);
// FIXME: Implement me!
endInsertRows();
}
bool CustomAbstractModel::insertColumns(int column, int count, const QModelIndex &parent)
{
beginInsertColumns(parent, column, column + count - 1);
// FIXME: Implement me!
endInsertColumns();
}
bool CustomAbstractModel::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(parent, row, row + count - 1);
// FIXME: Implement me!
endRemoveRows();
}
bool CustomAbstractModel::removeColumns(int column, int count, const QModelIndex &parent)
{
beginRemoveColumns(parent, column, column + count - 1);
// FIXME: Implement me!
endRemoveColumns();
}
Edit #1 Csv data (Not real information, randomized)
name,phone,email,address,city,zip
Chavez, Lisandra F.,1-498-913-8181,ac.fermentum.vel#semperrutrumFusce.org,Ap #123-1044 Sed Rd.,Drayton Valley,21833
Humphrey, Briar W.,1-583-466-4027,Morbi.accumsan.laoreet#Loremipsumdolor.net,Ap #642-6497 Id Rd.,Lochgilphead,16394
Benson, Tasha H.,1-898-918-7872,consequat#enimEtiam.ca,P.O. Box 197, 4720 Ipsum. St.,Telford,56688
Emerson, Susan P.,1-190-818-1919,dignissim#liberoatauctor.ca,P.O. Box 482, 7813 Dolor. Ave,San Antonio,M8C 7F6
Dunn, Alexander U.,1-222-379-2231,libero.Donec.consectetuer#nonegestasa.ca,803-958 Lectus Rd.,Raleigh,74078
Assuming that we read the whole file into memory, I would implement the model (subclass from QAbstractTableModel) in the following way:
CustomAbstractModel::CustomAbstractModel(const QString &filePath,
QObject *parent)
:
QAbstractTableModel(parent)
{
// Parse the file and store its data in an internal data structure
QFile file(filePath);
if (file.open(QIODevice::ReadOnly))
{
QTextStream in(&file);
while (!in.atEnd())
{
// Assuming that m_data is a std::vector<QString>
m_data.emplace_back(in.readLine());
}
}
}
QVariant CustomAbstractModel::headerData(int section,
Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole &&
!m_data.empty())
{
// Read the column names from the first line
const QString &firstLine = m_data.front();
QStringList headers = firstLine.split(',');
return headers.at(section);
}
}
int CustomAbstractModel::rowCount(const QModelIndex &parent) const
{
if (!m_data.empty())
{
return m_data.size() - 1; // First line is the column names data
}
return 0;
}
int CustomAbstractModel::columnCount(const QModelIndex &parent) const
{
if(!m_data.empty())
{
const QString &firstLine = m_data.front();
return firstLine.count(",") + 1;
}
return 0;
}
QVariant CustomAbstractModel::data(const QModelIndex &index, int role) const
{
if (m_data.empty() && role == Qt::DisplayRole)
{
// First line is the header data.
const QString &line = m_data[index.row() + 1];
QStringList columnData = firstLine.split(',');
return columnData.at(index.column());
}
return QVariant();
}

Set color to a QTableView row

void MyWindow::initializeModelBySQL(QSqlQueryModel *model,QTableView *table,QString sql){
model = new QSqlQueryModel(this);
model->setQuery(sql);
}
With this method i can set a QSQlQueryModels to my QTableviews.
But How i can set color to a row based on a cell value?
The view draws the background based on the Qt::BackgroundRole role of the cell which is the QBrush value returned by QAbstractItemModel::data(index, role) for that role.
You can subclass the QSqlQueryModel to redefine data() to return your calculated color, or if you have Qt > 4.8, you can use a QIdentityProxyModel:
class MyModel : public QIdentityProxyModel
{
QColor calculateColorForRow(int row) const {
...
}
QVariant data(const QModelIndex &index, int role)
{
if (role == Qt::BackgroundRole) {
int row = index.row();
QColor color = calculateColorForRow(row);
return QBrush(color);
}
return QIdentityProxyModel::data(index, role);
}
};
And use that model in the view, with the sql model set as source with QIdentityProxyModel::setSourceModel.
OR
You can keep the model unchanged and modify the background with a delegate set on the view with QAbstractItemView::setItemDelegate:
class BackgroundColorDelegate : public QStyledItemDelegate {
public:
BackgroundColorDelegate(QObject *parent = 0)
: QStyledItemDelegate(parent)
{
}
QColor calculateColorForRow(int row) const;
void initStyleOption(QStyleOptionViewItem *option,
const QModelIndex &index) const
{
QStyledItemDelegate::initStyleOption(option, index);
QStyleOptionViewItemV4 *optionV4 =
qstyleoption_cast<QStyleOptionViewItemV4*>(option);
optionV4->backgroundBrush = QBrush(calculateColorForRow(index.row()));
}
};
As the last method is not always obvious to translate from C++ code, here is the equivalent in python:
def initStyleOption(self, option, index):
super(BackgroundColorDelegate,self).initStyleOption(option, index)
option.backgroundBrush = calculateColorForRow(index.row())
Your best bet is to define a custom model (QAbstractTableModel subclass). You probably want to have a QSqlQueryModel as a member in this custom class.
If it's a read-only model, you need to implement at least these methods:
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
and for well behaved models also
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
If you need the model to be able to edit/submit data, things get a bit more involved and you will also need to implement these methods:
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole);
bool insertRows(int position, int rows, const QModelIndex &index=QModelIndex());
bool removeRows(int position, int rows, const QModelIndex &index=QModelIndex());
What will actually change a row appearance lies in the return value of this method:
QVariant data(const QModelIndex &index, int role) const;
A dumb example:
QVariant MyCustomModel::data(const QModelIndex &index, int role) const
{
if ( !index.isValid() )
return QVariant();
int row = index.row();
int col = index.column();
switch ( role )
{
case Qt::BackgroundRole:
{
if(somecondition){
// background for this row,col is blue
return QVariant(QBrush (QColor(Qt::blue)));
}
// otherwise background is white
return QVariant(QBrush (QColor(Qt::white)));
}
case Qt::DisplayRole:
{
// return actual content for row,col here, ie. text, numbers
}
case Qt::TextAlignmentRole:
{
if (1==col)
return QVariant ( Qt::AlignVCenter | Qt::AlignLeft );
if (2==col)
return QVariant ( Qt::AlignVCenter | Qt::AlignTrailing );
return QVariant ( Qt::AlignVCenter | Qt::AlignHCenter );
}
}
}

Subclassing QAbstractTableModel

I have subclassed a QAbstractTableModel to represent data from a QMap. This QMap has QLists of QSqlRecords and this map is modified by some other part of my code. I want to use this model with a QTableView to display the sql records in this map for each key. Here is my code.
//mymodel.h
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
void setRecordMap(QMap<int, QList<QSqlRecord>> *map);
void setSelectedSerMsgIndex(QModelIndex *index);
private:
QMap<int, QList<QSqlRecord>> *recordMap;
QModelIndex *selectedSerendibMsgIndex;
};
//mymodel.cpp
MyModel::MyModel(QObject *parent) : QAbstractTableModel(parent)
{
}
int MyModel::rowCount(const QModelIndex &parent) const
{
if(recordMap->isEmpty())
return 0;
int row = selectedSerendibMsgIndex->row();
return recordMap->value(row).size();
}
int MyModel::columnCount(const QModelIndex &parent) const
{
if(recordMap->isEmpty())
return 0;
int row = selectedSerendibMsgIndex->row();
return recordMap->value(row).at(0).count();
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if(recordMap->isEmpty())
return QVariant();
if (!index.isValid())
return QVariant();
int row = selectedSerendibMsgIndex->row();
if (index.row() >= recordMap->value(row).size())
return QVariant();
if (role == Qt::DisplayRole)
{
return recordMap->value(row).value(index.row()).value(index.column()); /* QVariant("hello");*/
}
else
{
return QVariant();
}
}
void MyModel::setRecordMap(QMap<int, QList<QSqlRecord>> *map)
{
recordMap = map;
}
void MyModel::setSelectedSerMsgIndex(QModelIndex *index)
{
selectedSerendibMsgIndex = index;
}
Sorry for the huge post. But the problem is, I cannot see the data from the map. I am guessing it is because there's something wrong with my implementation of the data() method. But I can't figure out what it is. Please be kind enough to help me. Thank you.
try changing this:
void MyModel::setRecordMap(QMap<int, QList<QSqlRecord>> *map)
{
recordMap = map;
}
to this:
void MyModel::setRecordMap(QMap<int, QList<QSqlRecord>> *map)
{
beginResetModel();
recordMap = map;
endResetModel();
}

Resources