PDA

View Full Version : 2 subclasses of QSortFilterProxyModel behind each other.



Delphi
28th May 2013, 10:18
Hi,

currently I am using 2 subclasses of QSortFilterProxyModel

Some of the data needs to be editable, some data not. So i made a QSortFilterProxyModelDisableEdit that does only remove the editable flag from a column.
On some tables I need an extra column that is not in the database. There for i used a ProxyModel (http://www.qtcentre.org/threads/54555-QsqlQueryModel-Subclass?p=244397#post244397) described in this threat.

The objects are connected in the following way:

QSqlTableModel* DataTable = new QSqlTableModel();

SqlProxyModelAddColumn* AddColumn = new SqlProxyModelAddColumn();
AddColumn->setSourceModel(DataTable);

SqlProxyModelDisalbeEdit* DisableEdit = new SqlProxyModelDisalbeEdit();
DisableEdit->setSourceModel(AddColumn);


Noting is set in DisableEdit so it acts like a QSqlProxyModel,
AddColumn implementation is descriped in ProxyModel (http://www.qtcentre.org/threads/54555-QsqlQueryModel-Subclass?p=244397#post244397)

When i try to edit some value for the DataTable all works fine.
but when i try to edit a value for the added column set data is never called. So i think there is something going wrong with the indexes, but i cant find the problem.

Any help is appreciated, thanks.

wysota
28th May 2013, 11:12
Why don't you subclass QIdentityProxyModel instead?

Delphi
28th May 2013, 11:34
I needed to sort something as well on that place,

I found the root of the problem: ProxyModel (http://www.qtcentre.org/threads/54555-QsqlQueryModel-Subclass?p=244397#post244397).

ProxyModel (http://www.qtcentre.org/threads/54555-QsqlQueryModel-Subclass?p=244397#post244397) with a QsqlQueryModel as source works.
When using a QSqlTablemodel or a other ProxyModel (http://www.qtcentre.org/threads/54555-QsqlQueryModel-Subclass?p=244397#post244397) it stops working.

has someome a neat solution for the index problem from ProxyModel (http://www.qtcentre.org/threads/54555-QsqlQueryModel-Subclass?p=244397#post244397)

wysota
28th May 2013, 11:53
Don't do 10 tasks at once. Have one proxy for modifying item flags, another for adding/removing columns and one more for sorting. Assemble them all to get the final solution.

Delphi
28th May 2013, 12:33
I we do not quite understand each other,
that's what I try to do exactly.

a model that changes flags and can filter
and a model that adds a column.


the problem lies with the ProxyModel

when it is having a QsqlQueryModel as source model it works correctly.
when it is having a QsqlTableModel or ProxyModel as sourceModel it is displaying correct but setdata never gets called.
the flags are working correctly.

So i think it is caused by the Indexes. As previous post said.
Is there someone that can help me get a neat solution for the index problem in ProxyModel

Any help is appreciated, thanks.

wysota
28th May 2013, 14:20
the problem lies with the ProxyModel
Get rid of that model and use a subclass of QIdentityProxyModel as advised. There you don't need to reimplement mapFromSource() and mapToSource() which are the source of your trouble.

Delphi
28th May 2013, 14:56
Why would be a QIdentityProxyModel easier?

I still will have problems with an index.
because (set)data works with them. Which index will have the extra column? It still needs to be maped by maped to and from source.

I also think that ToSource and FromSource need to be reimplemented, because QIdentityProxyModel works with 1 source model. My subclass needs to work with 1 source model and an extra column.

Santosh Reddy
28th May 2013, 16:42
Ok, I looked at the ProxyModel in the other post, it looks fine and will works for all QStandardItemModel, QSqlTableModel and QSqlQueryModel (only first column is editable, and other columns are redable)

Here is an example using the similar ProxyModel (with few changes), and it shows all combinations simultaneously.
9074


#include <QtGui>
#include <QtSql>
#include <QtWidgets>
#include <QApplication>

// Add one extra column at the begining
class ProxyModel : public QAbstractProxyModel
{
Q_OBJECT
public:
explicit ProxyModel(QObject * parent = 0)
: QAbstractProxyModel(parent)
, mData()
{
connect(this, SIGNAL(sourceModelChanged()), SLOT(slotSourceModelChanged()));
}

protected slots:
void slotSourceModelChanged(void)
{
disconnect(this, SLOT(slotDataChanged(QModelIndex,QModelIndex)));
connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(slotDataChanged(QModelIndex,QModelIndex)));
}

void slotDataChanged(const QModelIndex first, QModelIndex last)
{
emit dataChanged(mapFromSource(first), mapFromSource(last));
}

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

QModelIndex parent(const QModelIndex & /*child*/) const
{
return QModelIndex();
}

int rowCount(const QModelIndex & parent = QModelIndex()) const
{
if(!parent.isValid())
return sourceModel()->rowCount(QModelIndex());
return 0;
}

int columnCount(const QModelIndex & parent = QModelIndex()) const
{
if(!parent.isValid())
return sourceModel()->columnCount() + 1;
return 0;
}

QModelIndex mapToSource(const QModelIndex & proxyIndex) const
{
if(!proxyIndex.isValid())
return QModelIndex();

if(proxyIndex.column() == 0)
return createIndex(proxyIndex.row(), -1, (quintptr)-1); //<<<<<<<<<<<<<<<<<<<<<< This is a workaround. Note it is weird to create a index of proxy model and return as if it were an index of sourceModel(), it is not a common way but will work (at-least in this case)

return sourceModel()->index(proxyIndex.row(), proxyIndex.column() - 1);
}

QModelIndex mapFromSource(const QModelIndex & sourceIndex) const
{
if(!sourceIndex.isValid())
{
if((sourceIndex.row() > -1) and //<<<<<<<<<<<<<<<<<<<<<< This is a workaround.
(sourceIndex.column() == -1) and
(sourceIndex.internalId() == (quintptr)-1) )
return index(sourceIndex.row(), 0);
else
return QModelIndex();
}

return index(sourceIndex.row(), sourceIndex.column() + 1);
}

QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
{
if(orientation == Qt::Horizontal)
{
if(section == 0)
return QString("Proxy Column");
return sourceModel()->headerData(section - 1, orientation, role);
}
return sourceModel()->headerData(section, orientation, role);
}

QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
if(index.column() == 0)
{
if((role == Qt::DisplayRole) or (role == Qt::EditRole))
if(index.row() < mData.size())
return mData.at(index.row());
return QVariant();
}

return sourceModel()->data(mapToSource(index), role);
}

bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)
{
if(index.column() == 0)
{
if((role == Qt::DisplayRole) or (role == Qt::EditRole))
{
while((index.row() + 1) > mData.size())
mData.append(QString());
mData[index.row()] = value.toString();
emit dataChanged(index, index);
return true;
}
return false;
}

return sourceModel()->setData(mapToSource(index), value, role);
}

Qt::ItemFlags flags(const QModelIndex & index) const
{
if(index.column() == 0)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;

return sourceModel()->flags(mapToSource(index));
}

private:
QStringList mData;
};

class Widget : public QWidget
{
public:
explicit Widget(QWidget * parent = 0)
: QWidget(parent)
, mGridLayout(new QGridLayout(this))
{ }

void addWidget(int row, int col, QWidget * widget, const QString & title)
{
mGridLayout->addWidget(new QLabel(title), row * 2, col, 1, 1, Qt::AlignCenter);
mGridLayout->addWidget(widget, (row * 2) + 1, col, 1, 1);
}

private:
QGridLayout * mGridLayout;
};

const QString TableName = "Locations";
const QString Column1 = "Country";
const QString Column2 = "Location";
const QStringList countries = QStringList() << "Norway" << "Australia" << "USA" << "China" << "Germany";
const QStringList locations = QStringList() << "Oslo" << "Brisbane" << "Palo Alto" << "Beijing" << "Berlin";

void addSampleTableData(QAbstractItemModel * model)
{
if(model->rowCount() == 0)
model->insertRows(0, countries.size());

if(model->columnCount() == 0)
model->insertColumns(0, 2);

for(int r = 0; r < model->rowCount(); r++)
{
model->setData(model->index(r, 0), countries.at(r));
model->setData(model->index(r, 1), locations.at(r));
}

model->setHeaderData(0, Qt::Horizontal, Column1);
model->setHeaderData(1, Qt::Horizontal, Column2);
model->submit();
}

QTableView * createView(QAbstractItemModel * model)
{
QTableView * tableView = new QTableView;
tableView->setModel(model);
tableView->setEditTriggers(QTableView::DoubleClicked);
tableView->resizeColumnsToContents();
return tableView;
}

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

const QString fileName = "Qt.sqlite";
QDir dir;
if(dir.exists(fileName))
if(!dir.remove(fileName))
{
qDebug() << "Unable to delete old Database file " << fileName;
return -1;
}

QSqlDatabase sqlDatabase = QSqlDatabase::addDatabase("QSQLITE");
sqlDatabase.setDatabaseName(fileName);

if(!sqlDatabase.open())
{
qDebug() << sqlDatabase.lastError().text();
return -2;
}

QStandardItemModel standardItemModel;
addSampleTableData(&standardItemModel);

QSqlQuery sqlQuery(sqlDatabase);
if(!sqlQuery.exec(QString("CREATE TABLE %1 ( %2 TEXT, %3 TEXT)").arg(TableName).arg(Column1).arg(Column2)))
{
qDebug() << sqlQuery.lastError().text();
return -3;
}

QSqlTableModel sqlTableModel(0, sqlDatabase);
sqlTableModel.setTable("Locations");
sqlTableModel.setEditStrategy(QSqlTableModel::OnMa nualSubmit);
sqlTableModel.select();
addSampleTableData(&sqlTableModel);
sqlTableModel.submitAll();

QSqlQueryModel sqlQueryModel;
sqlQueryModel.setQuery(QString("SELECT %2, %3 FROM %1").arg(TableName).arg(Column1).arg(Column2), sqlDatabase);

ProxyModel standardItemModelProxy;
standardItemModelProxy.setSourceModel(&standardItemModel);

ProxyModel sqlTableModelProxy;
sqlTableModelProxy.setSourceModel(&sqlTableModel);

ProxyModel sqlQueryModelProxy;
sqlQueryModelProxy.setSourceModel(&sqlQueryModel);

Widget widget;

widget.addWidget(0, 0, createView(&standardItemModel), "QStandardItemModel View");
widget.addWidget(0, 1, createView(&sqlTableModel), "QSqlTableModel View");
widget.addWidget(0, 2, createView(&sqlQueryModel), "QSqlQueryModel View");

widget.addWidget(1, 0, createView(&standardItemModelProxy), "QStandardItemModel Proxy View");
widget.addWidget(1, 1, createView(&sqlTableModelProxy), "QSqlTableModel Proxy View");
widget.addWidget(1, 2, createView(&sqlQueryModelProxy), "QSqlQueryModel Proxy View");

widget.showMaximized();

return app.exec();
}

#include "main.moc"

wysota
28th May 2013, 17:16
Ok, I looked at the ProxyModel in the other post, it looks fine and will works for all QStandardItemModel, QSqlTableModel and QSqlQueryModel (only first column is editable, and other columns are redable)

I think it won't work with QSortFilterProxyModel because the latter requires proper internal pointers to be present which will not be the case with your implementation. The proper approach is to use QIdentityProxyModel which is able to return proper internal pointers for the source model.

Santosh Reddy
29th May 2013, 08:13
Ok, I looked at the ProxyModel in the other post, it looks fine and will works for all QStandardItemModel, QSqlTableModel and QSqlQueryModel (only first column is editable, and other columns are redable)
I think it won't work with QSortFilterProxyModel because the latter requires proper internal pointers to be present which will not be the case with your implementation. The proper approach is to use QIdentityProxyModel which is able to return proper internal pointers for the source model.
I don't think using QIdentityProxyModel will help in any way adding an extra column to the source model. Why do you think it is better than QAbstractItemModel?

As I see it, as far as adding a virtual column is concerned both QIdentityProxyModel and QAbstractItemModel just the same, you will need to implement index(), columnCount(), mapToSource(), mapFromSource(), data() and setData(). I don't think you can rely on the QIdentityProxyModel's mapToSource() and mapFromSource() as they not aware of the extra column we added, and infact will crash the program as view will try to access the index for the column which does not exist in QIdentityProxyModel nor in sourceModel().

I agree that the proposed ProxyModel approach will not work when installed on QSortFilterProxyModel, but the other way round will work.
Ok: QStandardItemModel->ProxyModel->QIdentityModel->QSortFilterProxyModel->QTreeView
Ok: QSqlTableModel->ProxyModel->QIdentityModel->QSortFilterProxyModel->QTreeView
Ok: QSqlQueryModel->ProxyModel->QIdentityModel->QSortFilterProxyModel->QTreeView
FAIL: *->QSortFilterProxyModel->ProxyModel->*
FAIL: *->QIdentityModel->ProxyModel->*

Bottom line is that ProxyModel cannot have source model as QSortFilterProxyModel / QIdentityModel, but QSortFilterProxyModel / QIdentityModel can have ProxyModel as source model.

Here is an example and source.
9077

Delphi
29th May 2013, 09:43
my apologies to Santosh Reddy,

The old and new model work with QSqlTableModel.

In my code dit not work because there was a QSqlSortfilterModel between the QSqlTableModel and ProxyModel. It was an leftover so it is removed.

With that in mind i tested the code from Post #8 With an QSortFilterProxyModel.

ProxyModel -> QSortFilterProxyModel -> 1 of the 3 source models.

As wysota thought, It is not working.

But, Testing
ProxyModel -> QIdentityProxyModel -> 1 of the 3 source models.
this did not work either.

So is it possible to make make a ProxyModel that accepts all QAbstractItemModel and its subclasses as source model.

Added after 1 1:

Both thanx for helping,

I think question is partially answered, with this solution it is not gonna work.

I agree that the proposed ProxyModel approach will not work when installed on QSortFilterProxyModel, but the other way round will work.
Ok: QStandardItemModel->ProxyModel->QIdentityModel->QSortFilterProxyModel->QTreeView
Ok: QSqlTableModel->ProxyModel->QIdentityModel->QSortFilterProxyModel->QTreeView
Ok: QSqlQueryModel->ProxyModel->QIdentityModel->QSortFilterProxyModel->QTreeView
FAIL: *->QSortFilterProxyModel->ProxyModel->*
FAIL: *->QIdentityModel->ProxyModel->*


FAIL: *->ProxyModel->ProxyModel->*
First is editable second not

I wil make sure that I am using them in the right order.

Santosh Reddy
29th May 2013, 10:23
So is it possible to make make a ProxyModel that accepts all QAbstractItemModel and its subclasses as source model.
To get perfect solution you will be implement a proxy model from QAbstractItemModel, not from QAbstractProxyModel. QAbstractProxyModel expects a one-to-one map between source and proxy.

When adding extra columns in a proxy model the problem is that column's QModelIndexes will not have a corresponding QModelIndex in source source model. It is simple to assume that the extra column's QModelIndexes will map to QModelIndex() (invalid index) in source but the real problem will arise as there it not way to reach the exrta column's QModelIndexed from source model indexes.

wysota
29th May 2013, 10:23
As I see it, as far as adding a virtual column is concerned both QIdentityProxyModel and QAbstractItemModel just the same, you will need to implement index(), columnCount(), mapToSource(), mapFromSource(), data() and setData().
The point is your ProxyModel implementation returns invalid indices for the source model. QIdentityProxyModel is made friend of QAbstractItemModel which lets it call createIndex() on its behalf.


I don't think you can rely on the QIdentityProxyModel's mapToSource() and mapFromSource() as they not aware of the extra column we added
You can extend the existing implementation. The point is what I wrote above -- QIdentityProxyModel is friend of QAbstractItemModel so it can return proper indexes for the source model. QAbstractProxyModel can't thus a subclass of QAbstractProxyModel will not either but a subclass of QIdentityProxyModel will.

Santosh Reddy
29th May 2013, 11:29
I don't think you can rely on the QIdentityProxyModel's mapToSource() and mapFromSource() as they not aware of the extra column we added, and infact will crash the program as view will try to access the index for the column which does not exist in QIdentityProxyModel nor in sourceModel().
Wysota, How do you think QIdentityProxyModel will address this?

Santosh Reddy
29th May 2013, 14:44
Here is another example which seems to be perfect solution by inheriting QAbstractItemModel. In this example there is a chain of 9 models connected back to back and 9 view, and the connected as following. Each model has its previous model as source model. The 9 QTableViews viewing each of these models are arranged in a grid layout of 3x3.

Note the porxy model to add one column is ColumnProxyModel

1. QStandardItemModel
2. ColumnProxyModel
3. QIdentityProxyModel
4. QSortFilterProxyModel
5. ColumnProxyModel
6. QIdentityProxyModel
7. ColumnProxyModel
8. ColumnProxyModel
9. QSortFilterProxyModel

9081

wysota
29th May 2013, 16:46
Wysota, How do you think QIdentityProxyModel will address this?

It will call

sourceModel()->createIndex(index.row(), index.column(), index.internalPointer());

which will preserve:

the model
row and column
implicitly the parent
the internal pointer


Your implementation will only preserve row and column and maybe also the internal pointer. But certainly not the model (thus calling index.data(...) will return wrong values) and probably not the parent.

You should treat QIdentityProxyModel as a NO-OP, nothing more, nothing less.

Santosh Reddy
29th May 2013, 17:35
Your implementation will only preserve row and column and maybe also the internal pointer. But certainly not the model (thus calling index.data(...) will return wrong values) and probably not the parent.
createIndex() is protected, how can we call it on sourceModel()?

Do you mean QIdentityProxyModel internally calls sourceModel().createIndex()? If yes then my question was not clear.

Can you give a sample implementation of mapToSouce() and mapFromSource() of the proxy (to add a column) when using QIdentityProxyModel as base class, if you say one does not need to implement these, then how can QIdentityProxyModel know about the extra column and there indexes?

BTW, a proxy based on QAbstractItemModel in post #15 works well with QSortFilterProxyModel and hopefuly other models too.

wysota
29th May 2013, 18:36
createIndex() is protected, how can we call it on sourceModel()?
As I already said, QIdentityProxyModel is declared friend of QAbstractItemModel so it can call its private methods.


Can you give a sample implementation of mapToSouce() and mapFromSource() of the proxy (to add a column) when using QIdentityProxyModel as base class,
It depends on where you want to add the column. If at the end then the default implementation of mapFromSource() is fine and mapToSource() needs to be adjusted to return an invalid index for the artificial column.


if you say one does not need to implement these, then how can QIdentityProxyModel know about the extra column and there indexes?
mapFromSource() and mapToSource() don't deal with accessing artificial columns. For that you have to reimplement index() and data().

By the way, a problem with making a proxy derived from QAbstractItemModel and not QAbstractProxyModel (or its subclass) is that you lose the possibility of going up the chain of proxy models to reach the final model with a single loop, like so:


QModelIndex idx = view->currentIndex();
const QAbstractProxyModel *proxy = 0;
while(proxy = qobject_cast<const QAbstractProxyModel*>(idx.model())) {
idx = proxy->mapToSource(idx);
}

Santosh Reddy
3rd August 2013, 14:24
By the way, a problem with making a proxy derived from QAbstractItemModel and not QAbstractProxyModel (or its subclass) is that you lose the possibility of going up the chain of proxy models to reach the final model with a single loop, like so:
Yes that is somthing we have take seriously in design, it will be better to subclass QAbstractProxyModel (or its subclass)