Creating custom QAbstractItemModel from comma seperated text file - qt

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

Related

QTableView model out of memory

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

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

QTableView doesn't seem to react on dataChanged signal

Shown below is the my custom table model. I am trying to use that tablemodel together with a QTableView. If the method append of the table model is called I would expect the table view to update its contents. But it doesn't and I don't know why. If however I use that same table model together with a QListView, everything works fine, i.e. the list view does update its contents, when append of the table model gets called. Is there anything special I need to do in case of the QTableView?
class MyModel : public QAbstractTableModel
{
public:
MyModel(QObject* parent=NULL) : QAbstractTableModel(parent) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const {
return mData.size();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const {
return 2;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (!index.isValid()) {
return QVariant();
}
if (role == Qt::DisplayRole) {
if (index.column()==0) {
return QVariant(QString::fromStdString(getFirst(index.row())));
}
if (index.column()==1) {
return QVariant(QString::fromStdString(getSecond(index.row())));
}
}
return QVariant();
}
void append(std::string const& first, std::string const& second) {
mData.push_back(std::make_pair(first, second));
emit dataChanged(index(mData.size()-1, 0), index(mData.size()-1, 1));
}
std::string const& getFirst(int i) const {
return mData[i].first;
}
std::string const& getSecond(int i) const {
return mData[i].second;
}
protected:
std::vector<std::pair<std::string, std::string> > mData;
};
As you're inserting a new row instead of changing existing data, you should use beginInsertRows and endInsertRows instead:
void append(std::string const& first, std::string const& second) {
int row = mData.size();
beginInsertRows( QModelIndex(), row, row );
mData.push_back(std::make_pair(first, second));
endInsertRows();
}
See if that helps.

View doesn't display data from a model

could someone please tell me, why this code for the freaking hell, doesn't display data in a view?
#include <QApplication>
#include <QtGui>
class File_Model : public QAbstractItemModel
{
private:
QStringList data_;
public:
File_Model()
{}
QVariant data(const QModelIndex &index, int role) const
{
return data_.at(index.row());
}
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole)
{
switch(role)
{
case Qt::DisplayRole:
data_ = value.toStringList();
emit dataChanged(index,index);
return true;
}
return false;
}
virtual QModelIndex index(int row, int column, const QModelIndex&) const
{
return createIndex(row,column);
}
virtual QModelIndex parent(const QModelIndex&) const
{
return QModelIndex();
}
virtual int rowCount(const QModelIndex&) const
{
return data_.size();
}
virtual int columnCount(const QModelIndex&) const
{
return 1;
}
};
int main(int argc,char** argv)
{
QApplication app(argc,argv);
QDir dir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation));
File_Model* model = new File_Model;//(dir.entryList());
bool t = model->setData(QModelIndex(),dir.entryList());
QListView* view = new QListView;
view->setModel(model);
view->show();
return app.exec();
}
The problem is on your data function. You should check the role before displaying something:
QVariant data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole)
return QVariant(data_.at(index.row()));
return QVariant();
}
Also note that you don't have to use the setData in your case. setData is udes for editing models, not initializing them with some values.
To enable editing in your model, you must also implement setData(),
and reimplement flags() to ensure that ItemIsEditable is returned.
Instead you could add a public function in your model and call it instead:
void setEntries(QStringList entries)
{
beginInsertRows(createIndex(0,0), 0, entries.count());
data_ = entries;
endInsertRows();
}

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