PDA

View Full Version : Problem with ProxyModel merging models with QSortFilterProxyModel.



SvenA
13th April 2015, 01:20
Hello!

I'm trying to implement a ProxyModel that combines up to 3 existing models (based on QAbstractTableModel). It works mostly, but I have some small problems.

If I use my proxy model directly in a QTableView anything works perfectly. I can add, change and delete entries to any of the models and the QableView updates correctly.

But if I try to use my ProxyModel indirectly through a QSortFilterProxyModel then only adding and changing works correctly. If I delete an entry, the QTableView is not updated correctly.

I'm not sure, if the signals for row removal are passed correctly?

Maybe somebody could take a look at my current implementation?!
What did I wrong?

Any help is appreciated...


#include <QAbstractProxyModel>
class MergeProxyModel : public QAbstractProxyModel
{
Q_OBJECT
public:
MergeProxyModel(QAbstractTableModel* first_model=0, QAbstractTableModel* second_model=0, QAbstractTableModel* third_model=0, QObject* parent=0);
~MergeProxyModel();

void setColumnCount(int count) { column_count=count; };

Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const;

int rowCount(const QModelIndex &index=QModelIndex()) const;
int columnCount(const QModelIndex &index=QModelIndex()) const;

QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const;

QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &proxyChild) const;

bool indexCheck(const QModelIndex &index) const;

int getListNo(const QModelIndex &index) const;
int getListPos(int n) const;
int getListPos(const QModelIndex &index) const;

QAbstractTableModel* sourceModel(const QModelIndex &index) const;

protected:
int column_count;
int listsCount() const;
int getOffset(const QAbstractItemModel* smodel) const;

QAbstractTableModel* first_model;
QAbstractTableModel* second_model;
QAbstractTableModel* third_model;

private:
void connectModels(QAbstractTableModel* model);

private slots:

void dataChangedSlot(const QModelIndex &topLeft, const QModelIndex &bottomRight);

void rowsAboutToBeInsertedSlot(const QModelIndex &parent, int first, int last);
void rowsInsertedSlot(const QModelIndex &parent, int first, int last);

void rowsAboutToBeRemovedSlot(const QModelIndex &parent, int first, int last);
void rowsRemovedSlot(const QModelIndex &parent, int first, int last);

void modelAboutToBeResetSlot();
void modelResetSlot();
};

MergeProxyModel::MergeProxyModel(QAbstractTableMod el* _first_model,
QAbstractTableModel* _second_model,
QAbstractTableModel* _third_model,
QObject *_parent)
: QAbstractProxyModel(_parent),
column_count(1),
first_model(_first_model),
second_model(_second_model),
third_model(_third_model)
{
if (first_model) connectModels(first_model);
if (second_model) connectModels(second_model);
if (third_model) connectModels(third_model);
}

MergeProxyModel::~MergeProxyModel()
{
}

void MergeProxyModel::connectModels(QAbstractTableModel * model)
{
Q_ASSERT(model);

connect(model, SIGNAL(layoutAboutToBeChanged()), this, SIGNAL(layoutAboutToBeChanged()), Qt::DirectConnection);
connect(model, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged()), Qt::DirectConnection);

connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChangedSlot(QModelIndex,QModelIndex)), Qt::DirectConnection);

connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)) , this, SLOT(rowsAboutToBeInsertedSlot(QModelIndex,int,int )), Qt::DirectConnection);
connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInsertedSlot(QModelIndex,int,int)), Qt::DirectConnection);

connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(rowsAboutToBeRemovedSlot(QModelIndex,int,int) ), Qt::DirectConnection);
connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(rowsRemovedSlot(QModelIndex,int,int)), Qt::DirectConnection);

connect(model, SIGNAL(modelAboutToBeReset()), this, SLOT(modelAboutToBeResetSlot()), Qt::DirectConnection);
connect(model, SIGNAL(modelReset()), this, SLOT(modelResetSlot()), Qt::DirectConnection);
}

int MergeProxyModel::listsCount() const
{
int totalCount = 0;
if (first_model) totalCount += first_model->rowCount();
if (second_model) totalCount += second_model->rowCount();
if (third_model) totalCount += third_model->rowCount();
return totalCount;
}

bool MergeProxyModel::indexCheck(const QModelIndex &index) const
{
return (index.isValid() && index.row() >= 0 && index.row() < rowCount() && index.column() >= 0 && index.column() < columnCount());
}

Qt::ItemFlags MergeProxyModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags theFlags = Qt::NoItemFlags;
if (index.isValid()) return QAbstractProxyModel::flags(index);
return theFlags;
}

QVariant MergeProxyModel::data(const QModelIndex &index, int role) const
{
if (!indexCheck(index)) return QVariant();

switch (getListNo(index))
{
case 0: return first_model->data(mapToSource(index), role);
case 1: return second_model->data(mapToSource(index), role);
case 2: return third_model->data(mapToSource(index), role);
default: return QVariant();
}
return QVariant();
}

int MergeProxyModel::rowCount(const QModelIndex &index) const
{
return index.isValid() ? 0 : listsCount();
}

int MergeProxyModel::columnCount(const QModelIndex &index) const
{
return index.isValid() ? 0 : column_count;
}

int MergeProxyModel::getOffset(const QAbstractItemModel* smodel) const
{
int offset = 0;

if (smodel != first_model) {
if (first_model) offset += first_model->rowCount();
if (smodel != second_model) {
if (second_model) offset += second_model->rowCount();
}
}
return offset;
}

QModelIndex MergeProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if (!sourceIndex.isValid()) return QModelIndex();
else if (sourceIndex.parent().isValid()) return QModelIndex();

int offset = getOffset(sourceIndex.model());
return createIndex(sourceIndex.row() + offset, sourceIndex.column());
}

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

int pos = getListPos(proxyIndex);
QAbstractTableModel* smodel = sourceModel(proxyIndex);
if (!smodel) return QModelIndex();

return smodel->index(pos, proxyIndex.column());
}

QModelIndex MergeProxyModel::index(int row, int column, const QModelIndex &parent) const
{
if (row >= rowCount()) return QModelIndex();
return createIndex(row, column, getListPos(row));
}

QModelIndex MergeProxyModel::parent(const QModelIndex &proxyChild) const
{
return QModelIndex();
}

int MergeProxyModel::getListNo(const QModelIndex &index) const
{
if (index.isValid())
{
int n = index.row();
int offset = 0;

if (first_model) {
if (n < first_model->rowCount()) return 0;
offset += first_model->rowCount();
}

if (second_model) {
if (n-offset < second_model->rowCount()) return 1;
offset += second_model->rowCount();
}

if (third_model) {
if (n-offset < third_model->rowCount()) return 2;
}
}
return -1;
}

int MergeProxyModel::getListPos(int n) const
{
if (first_model) {
if (n < first_model->rowCount()) return n;
n -= first_model->rowCount();
}

if (second_model) {
if (n < second_model->rowCount()) return n;
n -= second_model->rowCount();
}

if (third_model) {
if (n < third_model->rowCount()) return n;
}
return -1;
}

int MergeProxyModel::getListPos(const QModelIndex &index) const
{
if (index.isValid())
{
int n = index.row();

if (first_model) {
if (n < first_model->rowCount()) return n;
n -= first_model->rowCount();
}

if (second_model) {
if (n < second_model->rowCount()) return n;
n -= second_model->rowCount();
}

if (third_model) {
if (n < third_model->rowCount()) return n;
}
}
return -1;
}

QAbstractTableModel* MergeProxyModel::sourceModel(const QModelIndex &index) const
{
switch (getListNo(index))
{
case 0: return first_model;
case 1: return second_model;
case 2: return third_model;
default: return 0;
}
}

void MergeProxyModel::dataChangedSlot(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
const QModelIndex &proxyTopLeft = mapFromSource(topLeft);
const QModelIndex &proxyBottomRight = mapFromSource(bottomRight);
emit dataChanged(proxyTopLeft, proxyBottomRight);
}

void MergeProxyModel::rowsAboutToBeInsertedSlot(const QModelIndex &parent, int first, int last)
{
beginInsertRows(parent, first, last);
}

void MergeProxyModel::rowsInsertedSlot(const QModelIndex &parent, int first, int last)
{
endInsertRows();
}

void MergeProxyModel::rowsAboutToBeRemovedSlot(const QModelIndex &parent, int first, int last)
{
QAbstractTableModel *smodel = qobject_cast<QAbstractTableModel *>(sender());
const int offset = getOffset(smodel);
beginRemoveRows(parent, first + offset, last + offset);
}

void MergeProxyModel::rowsRemovedSlot(const QModelIndex &parent, int first, int last)
{
endRemoveRows();
}

void MergeProxyModel::modelAboutToBeResetSlot()
{
beginResetModel();
}

void MergeProxyModel::modelResetSlot()
{
endResetModel();
}


Regards
Sven

anda_skoa
13th April 2015, 08:18
Maybe running the model test suite can give you some input on what might be wrong: http://wiki.qt.io/Model_Test

Cheers,
_

SvenA
13th April 2015, 11:37
Thanks for the tip!
I tried it, but it reported no obvious error.

But I recognized the following:

1. In the simple case (without using QSortFilterProxyModel), I saw this output from the ModelTest:

ratbr QModelIndex(-1,-1,0x0,QObject(0x0) ) 1 0
rr QModelIndex(-1,-1,0x0,QObject(0x0) ) 1 0

2. In the problematic second case (with using QSortFilterProxyModel) these output are missing!

Are the signals are not passed correctly?

Any other ideas?

Regards
Sven

anda_skoa
13th April 2015, 12:06
One thing I am seeing is that you are mapping the rows from the source model into the proxy when handling rowsAboutToBeRemoved but do not do so in the case of handling rowsAboutToBeInserted.

Cheers,
_

SvenA
13th April 2015, 12:55
Hmmm, that's correct. But this changes nothing. It's still does not work.
If I understand it correctly, I have to do the mapping in both slots, but interestingly it works without this mapping the the rowsAboutToBeInserted() slot. I tried to remove the mapping from rowsAboutToBeRemoved() temporarily, but no change again.

Because I think it is correct to map it in both functions, I added the missing mapping to rowsAboutToBeInserted() too.

Now my rowsAboutToBeInsertedSlot() slot looks like:



void MergeProxyModel::rowsAboutToBeInsertedSlot(const QModelIndex &parent, int first, int last)
{
QAbstractTableModel *smodel = qobject_cast<QAbstractTableModel *>(sender());
const int offset = getOffset(smodel);
beginInsertRows(parent, first + offset, last + offset);
}

Would a small example application to test it, help?

anda_skoa
13th April 2015, 13:44
Would a small example application to test it, help?

Can't hurt. I don't have time today unfortunately, but someone else might.
A small test application often also leads to discovering the bug yourself :)

Cheers,
_

SvenA
14th April 2015, 11:40
Thanks for your help!
I found the bug.

The model itself was right. The bug was in the source model which was used by my proxy model.
In the beginRemoveRows() call I had an offset by one. Was hard to find.

Nevertheless, thanks for your time and input!

Cheers!

aliks-os
22nd September 2016, 09:05
Could you please share the final version of MergeProxyModel class?

jefftee
23rd September 2016, 02:33
A small test application often also leads to discovering the bug yourself :)
Dang @anda_skoa, you're good! :)