PDA

View Full Version : Add virtual rows & columns to a proxy model



STM
22nd July 2015, 11:57
Hi,

i have a QSortFilterProxyModel and i'd like to add some rows and columns, but only in the proxy model.

For example, i have 6 rows and 4 columns and i want to add an additional column with data not from the source model.

I've already reimplemented rowCount, columnCount and data. The QTableView shows the correct count of rows and columns but there is not data in the "virtual" column. I found out, that the QModelIndex rage in data() is only from column 0 to 3, but never comes to column 4.

What else do i have to do, to get the additional column(s)?

Thank you.

Regards,
Mani

anda_skoa
22nd July 2015, 14:03
So columnCount() of the proxy returns 5 but the proxy's data() never gets called with index.column==4?

Cheers,
_

STM
22nd July 2015, 14:06
Yes, this is the problem! The whole problem in one sentence ... :eek:

anda_skoa
23rd July 2015, 09:11
Ok, this is strange, the view should be trying to get data for those cells.

Can you post the code of your proxy model or maybe even a minimal example demonstrating the problem?

Cheers,
_

STM
23rd July 2015, 09:45
This demo model shows the problem. It starts with 6 rows and 4 columns and in the proxy model i remove 3 rows and add one column. The last column is created but there is no data in it, it's also not clickable.



#ifndef TestTableModel_H
#define TestTableModel_H

#include <QAbstractTableModel>

class TestTableModel : public QAbstractTableModel
{
private:
typedef QVector<QVariant> RowData;
typedef QVector<RowData> ModelData;

public:
TestTableModel(QObject *parent = 0);

int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;

private:
void init();

private:
ModelData mModelData;
};

#endif // TestTableModel_H




#include "TestTableModel.h"

TestTableModel::TestTableModel(QObject *parent) : QAbstractTableModel(parent)
{
init();
}

int TestTableModel::rowCount(const QModelIndex &parent) const
{
if(parent.isValid())
return 0;
else
return mModelData.size();
}

int TestTableModel::columnCount(const QModelIndex &parent) const
{
if(parent.isValid())
return 0;
else
{
if(mModelData.size() > 0)
return mModelData.at(0).size();
}

return 0;
}

QVariant TestTableModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::DisplayRole)
{
if(index.isValid())
{
return mModelData.at(index.row()).at(index.column());
}
}

return QVariant();
}

QVariant TestTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch(section)
{
case 0:
return "Value1";
case 1:
return "Value2";
case 2:
return "Value3";
case 3:
return "Value4";
default:
return QVariant();
}
}

return QVariant();
}

void TestTableModel::init()
{
RowData rdA;
rdA.append("A");
rdA.append(1000);
rdA.append(800);
rdA.append(100);

mModelData.append(rdA);

RowData rdB;
rdB.append("B");
rdB.append(2500);
rdB.append(2000);
rdB.append(300);

mModelData.append(rdB);

RowData rdC;
rdC.append("C");
rdC.append(1400);
rdC.append(1120);
rdC.append(100);

mModelData.append(rdC);

RowData rdA1;
rdA1.append("A");
rdA1.append(1200);
rdA1.append(960);
rdA1.append(180);

mModelData.append(rdA1);

RowData rdA2;
rdA2.append("A");
rdA2.append(1000);
rdA2.append(800);
rdA2.append(100);

mModelData.append(rdA2);

RowData rdB1;
rdB1.append("B");
rdB1.append(1500);
rdB1.append(1200);
rdB1.append(100);

mModelData.append(rdB1);
}


And the proxy model


#ifndef TestTableProxyModel_H
#define TestTableProxyModel_H

#include <QSortFilterProxyModel>

class TestTableProxyModel : public QSortFilterProxyModel
{
private:
typedef QVector<QVariant> RowData;
typedef QVector<RowData> ModelData;

public:
TestTableProxyModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QVariant data(const QModelIndex &index, int role) const;

void refreshView();

private:
QStringList mHeaderFields;
int mRowCount;
int mColumnCount;
ModelData mTmpModelData;
bool mUpdateDone;
};

#endif // TestTableProxyModel_H




#include "TestTableProxyModel.h"

TestTableProxyModel::TestTableProxyModel(QObject *parent)
: QSortFilterProxyModel(parent), mRowCount(0), mColumnCount(0), mUpdateDone(true)
{

}

int TestTableProxyModel::rowCount(const QModelIndex &parent) const
{
if(parent.isValid())
return 0;

if(mRowCount > 0 && mUpdateDone)
return mRowCount;
else
return QSortFilterProxyModel::rowCount(parent);
}

int TestTableProxyModel::columnCount(const QModelIndex &parent) const
{
if(parent.isValid())
return 0;

if(mColumnCount > 0 && mUpdateDone)
return mColumnCount;
else
return QSortFilterProxyModel::columnCount(parent);
}

QVariant TestTableProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(mUpdateDone)
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
if(!mHeaderFields.isEmpty())
{
if(section < mHeaderFields.size())
return mHeaderFields.at(section);
else
return QVariant();
}
}
}

return QSortFilterProxyModel::headerData(section, orientation, role);
}

QVariant TestTableProxyModel::data(const QModelIndex &index, int role) const
{
if(mUpdateDone)
{
if(role == Qt::DisplayRole)
{
if(!mTmpModelData.isEmpty())
{
if(index.row() < mTmpModelData.size())
{
if(index.column() < mTmpModelData.at(index.row()).size())
return mTmpModelData.at(index.row()).at(index.column());
}
else
return QVariant();
}
}
}

return QSortFilterProxyModel::data(index, role);
}

void TestTableProxyModel::refreshView()
{
mUpdateDone = false;

mRowCount = 3;
mColumnCount = 5;

for(int i=0;i<mColumnCount;i++)
mHeaderFields.append(QString("Column").append(QString::number(i+1)));

for(int i=0;i<mRowCount;i++)
{
RowData rd;
for(int j=0;j<mColumnCount;j++)
rd.append(QString("Value").append(QString::number(i+1)).append("_").append(QString::number(j+1)));

mTmpModelData.append(rd);
}

mUpdateDone = true;

invalidate();
}

d_stranz
23rd July 2015, 18:10
So where is this magical "refreshView()" method being called?

Your base model isn't calling any of the methods it should be when changing the model - things like beginInsertRows(), endInsertRows(), or even modelReset(). These things are necessary for the model to properly notify listeners (views, proxies, etc.) that something has changed and they need to update themselves. You shouldn't need to implement magic methods to get your proxy updated or to have to transfer data from your base model into the proxy.

In order to get clickable behaviour for your virtual column, you will need to implement the flags() method for your proxy.

STM
23rd July 2015, 18:36
Thank you for your reply.

The model itself should not be changed, i only want to add a "virtual" column with the proxy model to the view. refreshView() is called from a button on a form next to the table view. I also don't want to transfer any data from the base model to the proxy.

I have also reimplemented the flags() method but it does not change anything, same behaviour as before.

STM
27th July 2015, 09:35
I found the solution here (http://stackoverflow.com/questions/1387912/in-qt-chaining-models-does-not-work-as-expected).

I had to add the following methods to my proxy class:


QModelIndex index(int row, int column, const QModelIndex& parent=QModelIndex()) const {
return createIndex(row,column,row);
}

QModelIndex parent(const QModelIndex &index) const {
//Works only for non-tree models
return QModelIndex();
}

QModelIndex mapFromSource(const QModelIndex &source) const {
return index(source.row(), source.column(), source.parent());
}

QModelIndex mapToSource(const QModelIndex &proxy) const {
return (sourceModel()&&proxy.isValid())
? sourceModel()->index(proxy.row(), proxy.column(), proxy.parent())
: QModelIndex();
}

d_stranz
21st December 2018, 00:09
And here I thought elephants never forget. I guess that doesn't apply to old elephants.

Just got bit by this exact same issue this week, and finally decided to ask Google. That led me here, and adding the index() and parent() methods fixed it. (I had already added the map...() methods).

In my case, my proxy added a column which was mapped to one of the source model columns, but it split one of the source columns into two, each of which modified the original data - that is, if the original columns were x and y, it mapped x into x' and y', and y into z' by applying two different arithmetic transformations to the original x.