c++qtqsortfilterproxymodel

Get top items of QSortFilterProxyModel in Qt C++


Currently, I'm trying to get five items from a QSortFilterProxyModel after I sort the model.

I tried to do this:

void setSourceModel(QAbstractItemModel* source) override
{
     QSortFilterProxyModel::setSourceModel(source);
     auto* model{sourceModel()};
     model->sort(1);
}

and then:

bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override
{
     return sourceRow < 5;
}

But that didn't work. I get the first 5 items in the unsorted source model, instead of the first 5 items of the sorted model.

Can someone help me out?


Solution

  • As was stated in the comments, not all models support sorting (The base implementation QAbstractItemModel::sort is documented to do nothing).

    Of course, you can override sort for your source model. Hopefully, there are at least 2 solutions to solve your problem without bothering with it.

    1. Using more than 1 level of proxy model:

    Instances of QSortFilterProxyModel can sort data regardless of what their source model is.

    1. We can change the implementation of setSourceModel so that instead of attaching source directly to this, we place another QSortFilterProxyModel in between.
    2. That way, we guarantee this->sourceModel() supports sorting.
    void setSourceModel(QAbstractItemModel* source) override
    {
        QSortFilterProxyModel* sortModel = new QSortFilterProxyModel(this);
        sortModel->setSourceModel(source);
        setSourceModel(sortModel);
        sortModel->sort(1);
    }
    bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override
    {
        return sourceRow < 5;
    }
    

    2. With a QIdentityProxyModel:

    The above solution builds 3 layers of models:
    QSortFilterProxyModel subclass (filter) > QSortFilterProxyModel (sort) > QAbstractItemModel.

    Your filter criterion is a little bit special in the sense it does not use the data method from its source. Since this is the case, you may as well extend the definition of what we call filtering and implement the layers as:
    QIdentityProxyModel subclass (filter) > QSortFilterProxyModel (sort) > QAbstractItemModel.

    Inheriting QIdentityProxyModel with the below methods should make your model a little bit faster, although the difference may not be visible at all:

    void setSourceModel(QAbstractItemModel* source) override
    {
        QSortFilterProxyModel* sortModel = new QSortFilterProxyModel(this);
        sortModel->setSourceModel(source);
        setSourceModel(sortModel);
        sortModel->sort(1);
    }
    int rowCount(const QModelIndex &parent = QModelIndex()) const override
    {
        //Should parent be tested?
        //if (!parent.isValid())
        return 5;
    }
    

    This is probably all you need. To be safe, you may choose to add the mapFromSource / mapSelectionFromSource methods (and as a bonus, I also include mapToSource / mapSelectionToSource even though I really think those last 2 are not needed):

    QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override
    {
        if (sourceIndex.row() >= 5)
            return QModelIndex();
        return QIdentityProxyModel::mapFromSource(sourceIndex);
    }
    QItemSelection mapSelectionFromSource(const QItemSelection& selection) const
    {
        QItemSelection proxySelection;
        for (auto selRange : selection) {
            QModelIndex topLeftIndex = selRange.topLeft();
            if (topLeftIndex.row() < 5) {
                QModelIndex bottomRightIndex = it->bottomRight();
                if (bottomRightIndex.row() < 5) {
                    const QItemSelectionRange range(
                       QIdentityProxyModel::mapFromSource(topLeftIndex),
                       QIdentityProxyModel::mapFromSource(bottomRightIndex)
                    );
                    proxySelection.append(range);
                }
                else {
                    const QItemSelectionRange range(
                       QIdentityProxyModel::mapFromSource(topLeftIndex),
                       QIdentityProxyModel::mapFromSource(bottomRightIndex.siblingAtRow(4))
                    );
                    proxySelection.append(range);
                }
            }
        }
        return proxySelection;
    }
    
    
    QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
    {
        if (proxyIndex.row() >= 5)
            return QModelIndex();
        return QIdentityProxyModel::mapToSource(proxyIndex);
    }
    QItemSelection mapSelectionToSource(const QItemSelection& selection) const
    {
        QItemSelection sourceSelection;
        for (auto selRange : selection) {
            QModelIndex topLeftIndex = selRange.topLeft();
            if (topLeftIndex.row() < 5) {
                QModelIndex bottomRightIndex = it->bottomRight();
                if (bottomRightIndex.row() < 5) {
                    const QItemSelectionRange range(
                       QIdentityProxyModel::mapToSource(topLeftIndex),
                       QIdentityProxyModel::mapToSource(bottomRightIndex)
                    );
                    sourceSelection.append(range);
                }
                else {
                    const QItemSelectionRange range(
                       QIdentityProxyModel::mapToSource(topLeftIndex),
                       QIdentityProxyModel::mapToSource(bottomRightIndex.siblingAtRow(4))
                    );
                    sourceSelection.append(range);
                }
            }
        }
        return sourceSelection;
    }