Qt::QAbstractItemModel::beginRemoveRows() crash - c++

I have a problem with implementation of custom model (base class is QAbstractItemModel) for QTreeView.
In a nutshell, after calling of beginRemoveRows(), QAbstractItemModel calls my implementation of parent() with QModelIndex that returns dangling pointer from QModelIndex::internalPointer(). Here is call stack:
Ok, posting as small part of code as possible..
I'm working with next tree:
Here are solutions that contain projects that contain files.
Model contains instances of FileItem class:
class FileItem;
typedef QSharedPointer<FileItem> FileItemPtr;
class FileItem
{
public:
explicit FileItem(File* file, FileItem* parentItem = nullptr);
...
FileItem* child(int row); // returns #row element of #childItems_ vector
FileItem* parentItem() const; // returns #parentItem_
void removeChild(int row); // removes #row element from #childItems_ vector
private:
QVector<FileItemPtr> childItems_;
FileItem* parentItem_;
...
};
Here is model's (FilesModel) main functions:
class FilesModel :
public QAbstractItemModel
{
...
private:
QModelIndex index(
int row,
int column = 0,
const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE;
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) Q_DECL_OVERRIDE;
QModelIndex parent(const QModelIndex& index) const Q_DECL_OVERRIDE;
// returns corresponding childCount() for valid #parent's internalPointer()
int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE;
int columnCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE
{
Q_UNUSED(parent);
return 1;
}
private:
QVector<FileItemPtr> items_;
}
QModelIndex's creation:
QModelIndex FilesModel::index(
int row,
int column /*= 0*/,
const QModelIndex& parent /*= QModelIndex()*/) const
{
if(!hasIndex(row, column, parent))
return invalidIndex();
if(!parent.isValid()) // If, parent is invalid, then #row is our items_[row]
{
if(row < items_.size())
{
FileItem* item = items_.value(row).data();
// Pass #item as QModelIndex's internal pointer
return createIndex(row, column, item);
}
return invalidIndex();
}
FileItem* parentItem = static_cast<FileItem*>(parent.internalPointer());
FileItem* childItem = parentItem->child(row);
if(childItem)
// Pass #childItem as QModelIndex's internal pointer
return createIndex(row, column, childItem);
else
return invalidIndex();
}
Getting parent of item:
QModelIndex FilesModel::parent(const QModelIndex& index) const
{
if(!index.isValid())
return invalidIndex();
FileItem* childItem = static_cast<FileItem*>(index.internalPointer());
///////////////////////////////////////////////////////////////////////////
//
// #childItem is dandling poiner after call of beginRemoveRows()
// for root item. See FilesModel::removeRows()
//
FileItem* parentItem = childItem->parentItem();
if(parentItem == nullptr)
return invalidIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
FilesModel::removeRows()
Ok, next impl of FilesModel::removeRows() works fine in case, when i try to remove any file (sub item of project) and any project (sub item of solution). But, when I'm removing solution item (on of roots element of view), I have the situation, that was described on the beginning of question: QAbstractItemModel calls parent() function with QModelIndex that returns dangling pointer from QModelIndex::internalPointer(), but I'm passing valid pointers to createIndex() and never use explicit deletion of QModelIndex::internalPointer().
Ok, here is items removing:
bool FilesModel::removeRows(int row, int count, const QModelIndex& parent /*= QModelIndex()*/)
{
Q_ASSERT(count > 0);
Q_ASSERT(row >= 0);
if(parent.isValid()) // Child items of #items_
{
// This branch works fine
for(int r = row; r < (row + count); ++r)
{
QModelIndex idxRemove = parent.child(r, 0);
Q_ASSERT(idxRemove.isValid());
FileItem* fiRemove = static_cast<FileItem*>(idxRemove.internalPointer());
Q_ASSERT(fiRemove);
if(idxRemove.child(0, 0).isValid()) // Has childrens
{
bool childRemoved = removeRows(0, fiRemove->childCount(), idxRemove);
Q_ASSERT(childRemoved);
}
}
FileItem* fiParent = static_cast<FileItem*>(parent.internalPointer());
Q_ASSERT(fiParent->childCount() >= (row + count - 1));
beginRemoveRows(parent, row, row + count - 1);
int childToRemove = row;
for(int r = row; r < (row + count); ++r)
fiParent->removeChild(childToRemove);
endRemoveRows();
return true;
}
else // Removing #items_
{
// Here is problem branch
Q_ASSERT(rowCount() >= (row + count - 1));
for(int r = row; r < (row + count); ++r)
{
FileItem* slnItem = items_.value(r).data();
bool projectRemoved = removeRows(0, slnItem->childCount(), index(r));
Q_ASSERT(projectRemoved);
}
///////////////////////////////////////////////////////////////////////
// This call to beginRemoveRows() cause call of parent() function
// with invalid QModelIndex::internalPointer()
//
beginRemoveRows(QModelIndex(), row, row + count - 1);
int slnRemove = row;
for(int r = row; r < (row + count); ++r)
items_.remove(slnRemove);
endRemoveRows();
return true;
}
return false;
}
Any one know what is the problem ?
I'm working with this model with one, GUI thread. I have Qt 5.4.0 on Windows.
Thanks
UPDATE:
validateItem() just checks the pointer value to catch the problem with dandling pointer from QModelView::internalPointer()
void FileItem::validateItem() const
{
Q_ASSERT((reinterpret_cast<size_t>(this) > 0x1000)
&& "Invalid item");
for(const FileItemPtr& child : childItems_)
child->validateItem();
}
So, for finding the problem my FilesModel::parent() looks like this:
QModelIndex FilesModel::parent(const QModelIndex& index) const
{
if(!index.isValid())
return invalidIndex();
FileItem* childItem = static_cast<FileItem*>(index.internalPointer());
#if !defined(NDEBUG)
// Try to understand, if #childItem is OK
childItem->validateItem();
#endif
...
}

Call stack you are showing is indicating that assertion from YOUR CODE is failing, so you should be aware what is the problem!
Check your code in FileItem::validateItem line nr 316 to see what is the problem. (Just click proper (second) item in call stack view when crash occurs again)
referring to update:
UPDATE: validateItem() just checks the pointer value to catch the
problem with dandling pointer from QModelView::internalPointer()
Q_ASSERT((reinterpret_cast(this) > 0x1000)
WTF? Who told you that this is proper way to detect that? Dangling pointer doesn't mean that pointer has some specific value!

Related

QAbstractTableModel editing without clearing previous data in cell

I have created a model based off of QAbstractTableModel that allows the user to edit data in that model. The model is displayed in a QTableView in a QMainWindow. So far in my model I am able to make the cells editable, and save whatever the user types in after editing is finished.
The issue is that when the user begins editing, it 'clears' the previous contents of that cell. So if for example I only wanted to change the spelling of a string in a cell, I have to re-type the entire value. I would like when editing that the editor would start with the data that is already in the model, rather than empty.
How can I do that?
Example of the issue:
Before I begin editing a cell:
As soon as I begin editing, the cell is empty. I would like it to star with the previous value already in the model:
Here is a minimal example of my model. My actual model is much larger and uses a struct instead of just a 2D array of QVariants to store the data.
Header:
const int COLS= 2;
const int ROWS= 6;
class EditableTableModel : public QAbstractTableModel
{
Q_OBJECT
private:
QVariant tableData[ROWS][COLS];
public:
EditableTableModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
signals:
void editCompleted(QString);
};
Implementation:
EditableTableModel::EditableTableModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
int EditableTableModel::rowCount(const QModelIndex & /*parent*/) const
{
return ROWS;
}
int EditableTableModel::columnCount(const QModelIndex & /*parent*/) const
{
return COLS;
}
QVariant EditableTableModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
switch (role) {
case Qt::DisplayRole:
return tableData[row][col];
}
return QVariant();
}
bool EditableTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::EditRole) {
if (!checkIndex(index))
return false;
tableData[index.row()][index.column()] = value;
return true;
}
return false;
}
QVariant EditableTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case 0:
return QString("First Name");
case 1:
return QString("Last Name");
}
}
return QVariant();
}
Qt::ItemFlags EditableTableModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}
Data should be returned for Qt::EditRole in the data() method. The following should work:
QVariant EditableTableModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole: // <-- add this line
return tableData[row][col];
}
return QVariant();
}
Note that the above switch-case uses something known as fallthrough, so that the switch-case will match for both Qt::DisplayRole and Qt::EditRole.

Delete rows from QTreeView with custom model

I'm new to Qt. I'm trying to create custom model for tree view with support of rows deletion. I've implemented it according to examples http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html and http://doc.qt.io/qt-5/qtwidgets-itemviews-editabletreemodel-example.html. Also, I've made context menu with option to remove row after pressing with right mouse button on the row.
Now, I have hardly reproducible error (there is no exact pattern, but it is easy to obtain). When I start to delete rows from model randomly, sometimes my program crashes, sometimes I receive following messages to output:
QAbstractItemModel::endRemoveRows: Invalid index ( 1 , 0 ) in model QAbstractItemModel(0x55555580db10)
When program crashes, I almost always in fuction
QModelIndex TreeModel::parent(const QModelIndex &child) const
which is inherited from
QModelIndex QAbstractItemModel::parent(const QModelIndex &child) const
Stack of function calls shows that this function is called from
void QAbstractItemModel::beginRemoveRows(const QModelIndex &parent, int first, int last)
which I call in overrided
bool TreeModel::removeRows(int row, int count, const QModelIndex &parent)
When I compared adresses of child.indernalPointer() (where I store pointer to internal tree Nodes, representing my model) with already deleted Nodes, It became clear, that by some reason beginRemoveRows() using already invalid indices.
There is question with very similar error: QModelIndex becomes invalid when removing rows, howerer I can't understand why and where I use invalid indices.
So, I place the minimal example with this behavior (I've put a lot of effort to minimize it to this size and make the code clear, sorry for it is nevertheless long).
tree.pro
QT += core gui widgets
TARGET = tree
TEMPLATE = app
SOURCES += main.cpp widget.cpp treemodel.cpp
HEADERS += widget.h treemodel.h
treemodel.h
#ifndef TREEMODEL_H
#define TREEMODEL_H
#include <QAbstractItemModel>
class TreeModel : public QAbstractItemModel
{
public:
TreeModel();
~TreeModel();
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool removeRows(int row, int count, const QModelIndex &parent) override;
private:
class Impl;
Impl* impl = nullptr;
};
#endif // TREEMODEL_H
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
private slots:
void projectTreeMenuRequested(const QPoint& point);
void eraseItem();
private:
class Impl;
Impl* impl;
};
#endif // WIDGET_H
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
treemodel.cpp
#include "treemodel.h"
#include <cassert>
#include <string>
#include <list>
#include <memory>
namespace {
struct Node {
Node(const std::string& name)
: text(name)
{
}
~Node() {
}
Node& append(const std::string& name) {
child.emplace_back(name);
Node& n = child.back();
n.parent = this;
return n;
}
size_t getChildNum() const {
return child.size();
}
bool hasParent() const {
return parent != nullptr;
}
Node& getParent() {
assert(hasParent());
return *parent;
}
size_t getIndexInParent() const {
if (parent) {
size_t index = 0;
Childs::iterator it = parent->child.begin();
while (it != parent->child.end()) {
if (&*it == this) {
return index;
}
++it;
++index;
}
}
return 0;
}
Node& getChild(size_t i) {
assert(i < child.size());
Childs::iterator it = child.begin();
std::advance(it, i);
return *it;
}
void setText(std::string name) {
this->text = std::move(name);
}
std::string getText() const {
return text;
}
void remove() {
assert(hasParent());
Node& p = getParent();
for (Childs::iterator it = p.child.begin(); it != p.child.end(); ++it) {
if (&*it == this) {
p.child.erase(it);
return;
}
}
assert(0); // Child for remove not found
}
bool removeChilds(size_t start, size_t end) {
if (start < end && end <= child.size()) {
Childs::iterator it1 = child.begin();
assert(it1 != child.end());
std::advance(it1, start);
assert(it1 != child.end());
Childs::iterator it2 = it1;
std::advance(it2, end - start);
child.erase(it1, it2);
return true;
} else {
return false;
}
}
static const int Columns = 1;
private:
using Childs = std::list<Node>;
std::string text;
Node* parent = nullptr;
Childs child;
};
} // namespace
struct TreeModel::Impl {
Impl()
: root("Root")
{
fill(root);
}
void fill(Node& from, std::string str = "", int depth = 0) {
if (depth == 10) return;
for (int j = 0; j != 5; ++j) {
std::string name = str + std::to_string(j);
fill(from.append(name), name, depth+1);
}
}
Node root;
};
TreeModel::TreeModel()
: impl(new Impl)
{
}
TreeModel::~TreeModel()
{
delete impl;
}
QModelIndex
TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent)) {
return QModelIndex();
} else {
Node* node = nullptr;
if (!parent.isValid()) {
node = &impl->root;
} else {
node = static_cast<Node*>(parent.internalPointer());
}
return createIndex(row, column, &node->getChild(row));
}
}
QModelIndex TreeModel::parent(const QModelIndex &child) const
{
if (!child.isValid()) {
return QModelIndex();
}
Node* node = static_cast<Node*>(child.internalPointer());
if (!node->hasParent()) {
return QModelIndex();
}
return createIndex(node->getIndexInParent(),
child.column(),
&node->getParent());
}
int TreeModel::rowCount(const QModelIndex &parent) const
{
Node* p = nullptr;
if (parent.isValid()) {
p = static_cast<Node*>(parent.internalPointer());
} else {
p = &impl->root;
}
return p->getChildNum();
}
int TreeModel::columnCount(const QModelIndex &) const
{
return Node::Columns;
}
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (index.isValid()) {
Node* node = static_cast<Node*>(index.internalPointer());
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
return QString::fromUtf8(node->getText().data(),
node->getText().size());
break;
}
}
return QVariant();
}
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role != Qt::EditRole)
return false;
Node* node = nullptr;
if (index.isValid()) {
node = static_cast<Node*>(index.internalPointer());
} else {
node = &impl->root;
}
node->setText(value.toString().toStdString());
emit dataChanged(index, index);
return true;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}
bool TreeModel::removeRows(int row, int count, const QModelIndex &parent)
{
Node* node = nullptr;
QModelIndex correctParent;
if (parent.isValid()) {
node = static_cast<Node*>(parent.internalPointer());
correctParent = parent;
} else {
node = &impl->root;
correctParent = QModelIndex();
}
beginRemoveRows(correctParent, row, row + count - 1); // [row, row + count - 1]
bool success = node->removeChilds(row, row + count); // [row, row + count)
endRemoveRows();
return success;
}
widget.cpp
#include "widget.h"
#include <QVBoxLayout>
#include <QTreeView>
#include <QPoint>
#include <QMenu>
#include "treemodel.h"
struct Widget::Impl {
QVBoxLayout* layout;
QTreeView* treeView;
TreeModel* treeModel;
};
Widget::Widget(QWidget *parent)
: QWidget(parent)
, impl(new Impl)
{
impl->layout = new QVBoxLayout(this);
impl->treeView = new QTreeView;
impl->treeModel = new TreeModel;
impl->layout->addWidget(impl->treeView);
impl->treeView->setModel(impl->treeModel);
impl->treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
impl->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(impl->treeView, SIGNAL(customContextMenuRequested(const QPoint&)),
this, SLOT(projectTreeMenuRequested(const QPoint&)));
}
Widget::~Widget()
{
delete impl->treeModel;
delete impl;
}
void Widget::projectTreeMenuRequested(const QPoint &point)
{
QPoint globalPos = impl->treeView->mapToGlobal(point);
QMenu myMenu;
myMenu.addAction("Erase", this, SLOT(eraseItem()));
myMenu.exec(globalPos);
}
void Widget::eraseItem()
{
for (QModelIndex index : impl->treeView->selectionModel()->selectedIndexes()) {
impl->treeModel->removeRow(index.row(), index.parent());
}
}
EDIT
I think about two ways to solve the problem. The first is direct approach when somebody point me to the incorrect use of Qt API. The second approach is if somebody write independent implementation of this functionality (tree with infinity nesting and ability to remove) and I will try to figure out what I am doing wrong compared to another implementation.
EDIT 2
After thorough analysis of QStandardItemModel I come to conclusion, that it is important to store in internalPointer of indices parent of actual Node, but in my example I use internalPointer to store Node itself. So it seems correct for Qt internal implementation to call parent() on indices of already deleted elements, assuming that the information in indernalPointer is not related to element and remains correct. (Please, correct me if I am wrong.)
Confirmed, that after rewriting implementation to store pointers to parent node in internal nodes, this bug was eliminated. Correction of other bugs is provided in accepted answer.
You are using for(index: selectedIndexes()) in Widget::eraseItem(), but after removing something, indexes changes, so your index in for becomes invalid. Also, it is a bad practice, to change container while you iterating through it.
In removeChilds() try changing the conditional from end <= child.size() to end < child.size()

QIdentityProxyModel display empty rows

I've implemented QIdentityProxyModel like this:
class ProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
ProxyModel(QObject *parent)
{
entriesPerPage = 3;
page = 1;
}
inline int getEntriesPerPage() const
{ return entriesPerPage; }
inline void setEntriesPerPage(int value)
{ entriesPerPage = value; }
inline qint64 getPage() const
{ return page; }
inline void setPage(const qint64 &value)
{ page = value; }
inline int rowCount(const QModelIndex &parent = QModelIndex()) const override{
Q_UNUSED(parent)
if(!sourceModel())
return 0;
return entriesPerPage * page <= sourceModel()->rowCount()
? entriesPerPage
: sourceModel()->rowCount() - entriesPerPage * (page - 1);
}
inline int columnCount(const QModelIndex &parent = QModelIndex()) const override{
Q_UNUSED(parent);
return 6;
}
QModelIndex ProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if(!sourceModel() && !proxyIndex.isValid())
return QModelIndex();
return sourceIndex.isValid()
? createIndex(sourceIndex.row() % entriesPerPage, sourceIndex.column(), sourceIndex.internalPointer())
: QModelIndex();
}
QModelIndex ProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
if(!sourceModel() && !proxyIndex.isValid())
return QModelIndex();
QModelIndex remapped = createIndex(proxyIndex.row() ,
proxyIndex.column(),
proxyIndex.internalPointer());
return QIdentityProxyModel::mapToSource(remapped);
}
private:
int entriesPerPage;
qint64 page;
};
When I insert a row in the sourceModel() with index more then entriesPerPage, view displays empty rows, so that row number is more then entriesPerPage, although rowCount() return number equal to entriesPerPage.
How can I get rid of empty rows?
First. It is a bad practice to override rowCount/mapFromSource for QIdentityProxyModel. I propose you to use QAbstractProxyModel to have more clear code.
Main. Your problem is in getEntriesPerPage/setPage methods. You need to call beginResetModel/endResetModel after updating such data.
inline void setPage(const qint64 &value)
{
beginResetModel();
page = value;
endResetModel();
}
Offtopic: it is pretty cool, that you have to code with Qt in BSUIR. Who is your teacher?

QSortFilterProxyModel not showing 2nd column in QTreeView

I have inherited a class from QSortFilterProxyModel to support filtering of a hierarchical tree in my code.
I have added code below of what I have done. After filtering is done, data in the 2nd column is NOT shown...
I am not able to understand why is that...
Can anyone help me on that?
Also, when filtering is completed, the tree is collapsed... I want to call expandAll on the tree when the filtering is completed. Is there some signal emitted or some function called where I know that the filtering is done?
Class Declaration
class MySortFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
MySortFilterProxyModel(QObject *parent = 0);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
bool ShowThis(const QModelIndex index) const;
private:
};
USAGE:
_proxyModel = new MySortFilterProxyModel(this);
_proxyModel->setFilterKeyColumn(-1);
_proxyModel->setSourceModel(_directiveTreeModel);
DEFINITION:
bool MySortFilterProxyModel::filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
return ShowThis(index);
}
bool MySortFilterProxyModel::ShowThis(const QModelIndex index) const
{
bool retVal = false;
//Gives you the info for number of childs with a parent
if ( sourceModel()->rowCount(index) > 0 )
{
for( int nChild = 0; nChild < sourceModel()->rowCount(index); nChild++)
{
QModelIndex childIndex = sourceModel()->index(nChild,0,index);
if ( ! childIndex.isValid() )
break;
retVal = ShowThis(childIndex);
if (retVal)
{
break;
}
}
}
else
{
QModelIndex useIndex0 = sourceModel()->index(index.row(), 0, index.parent());
QString name = sourceModel()->data(useIndex0, Qt::DisplayRole).toString();
QModelIndex useIndex1 = sourceModel()->index(index.row(), 1, index.parent());
QString value = sourceModel()->data(useIndex1, Qt::DisplayRole).toString();
std::cout << "name : " << name.toStdString() << ", value : " << value.toStdString() << "\n";// , filterRegExp : " << filterRegExp() << "\n";
if ( (name.contains(filterRegExp()) || value.contains(filterRegExp())) )
{
retVal = true;
}
else
retVal = false;
}`enter code here`
return retVal;
}
OUTPUT : (Data in the 2nd column is missing)
I figured out the issue...
I was using QStyledItemDelegate and I had derived a class from it and overridden the paint function.
In the pain function, I was referring to my original model .. But, in case of filtering, that model is NULL and I should have used the proxy model that I had created.

QTreeView QSortFilterProxyModel trigger filter

Here is my program:
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QTreeView>
class MySortFilterProxyModel : public QSortFilterProxyModel
{
public:
MySortFilterProxyModel();
void updateFilter(int filterType);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
bool lessThan(const QModelIndex &left,const QModelIndex &right) const;
private:
int _filterType;
};
MySortFilterProxyModel::MySortFilterProxyModel()
: _filterType(0)
{
}
bool MySortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QStandardItemModel* source = static_cast<QStandardItemModel*>(sourceModel());
QModelIndex modelIndex = source->index(sourceRow, 0, sourceParent);
QStandardItem* item = source->itemFromIndex(modelIndex);
QVariant v = item->data(Qt::UserRole);
int itemType = v.toInt();
if(itemType == _filterType)
return true;
return false;
}
bool MySortFilterProxyModel::lessThan(const QModelIndex &left,const QModelIndex &right) const
{
QVariant leftData = sourceModel()->data(left);
QVariant rightData = sourceModel()->data(right);
if(leftData.type() == QVariant::String && rightData.type() == QVariant::String)
{
QString leftString = leftData.toString();
QString rightString = rightData.toString();
return QString::localeAwareCompare(leftString, rightString) < 0;
}
return false;
}
void MySortFilterProxyModel::updateFilter(int filterType)
{
_filterType = filterType;
// how can i trigger filteracceptRows here ??
}
int main(int argc, char** argv)
{
QApplication qtApp(argc, argv);
MySortFilterProxyModel mySortFilterProxyModel;
QStandardItemModel standardModel;
QTreeView treeView;
mySortFilterProxyModel.setSourceModel(&standardModel);
treeView.setModel(&standardModel);
treeView.setSortingEnabled(true);
treeView.show();
return qtApp.exec();
}
Everytime i AppendRow to standardModel sort and filter works.
How can i trigger filtering without appending or removing something to standardModel?
I want to filter rows on QTreeView through right click but i couldn't find a way to triggger filterAcceptRows on my void MySortFilterProxyModel::updateFilter(int filterType) function.
Having multiple instances of MySortFilterProxyModel class for every possible filterType value and switching them according to filterType may work but is there a better solution?
calling invalidate() on updateFilter worked for me.
void MySortFilterProxyModel::updateFilter(int filterType)
{
_filterType = filterType;
invalidate();
}