Qt QAbstractItemModel - item removal crashes

user360607 picture user360607 · Mar 20, 2012 · Viewed 6.9k times · Source

I'm writing an application using Qt classes where I have a hierarchical structure and I need to display it in a tree view (I'm using a QTreeView widget). The data itself look like this:

class StatisticsEntry
{
 // additional data management methods
 ...

bool setData(int column, const QVariant &value)
{
    if (column < 0 || column >= _data.size())
        return false;

    _data[column] = value;
    return true;
}

private:
    // each item has a parent item except the top-most one
    StatisticsEntry *_parent;
    // each item may have child items which are accessible through the following member
    QList<StatisticsEntry*> _children;
    // each item is defined by a set of vallues stored in the following vector
    QVector<QVariant> _data;
}

I have a class called it StatisticsModel that implements QAbstractItemModel - use it to manipulate and present the data stored in the StatisticsEntry tree. The class has a method called addStatisticsData which I use to push StatisticsEntry records into the model. The method roughly looks like this:

QModelIndex StatisticsModel::addStatisticsData(const QString &title, const QString &description, const QModelIndex &parent)
{
    int row = rowCount(parent);
    if (!insertRow(row, parent))
        return QModelIndex();

    // Get new item index
    QModelIndex child = index(row, 0, parent);

    // set item data
    setTitle(child, title);
    setDescription(child, description);

    return child;
}

SetTitle and setDescription methods are identical - here's the setTitle one:

void StatisticsModel::setTitle(const QModelIndex &index, const QString& title)
{
    QModelIndex columnIndex = this->index(index.row(), StatColumn::Title, index.parent());
    this->setData(columnIndex, title, Qt::EditRole);
}

The setData method is as follows:

bool StatisticsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role != Qt::EditRole)
        return false;

    int column = index.column();

    StatisticsEntry *item = getItem(index);
    if (!item->setData(column, value))
        return false;

    emit dataChanged(index, index);

    return true;
}

What is left is the getItem method:

StatisticsEntry *StatisticsModel::getItem(const QModelIndex &index) const
{
    if (index.isValid())
    {
        StatisticsEntry *item = static_cast<StatisticsEntry*>(index.internalPointer());
        if (item)
            return item;
    }

    return _rootItem;
}

That is about all there is when speaking of adding new and modifying existing entries. In my application I have implemented QSortFilterProxyModel as well - nothing special, just the lessThan method. I use the proxy model to provide a sort feature in the QTreeView that displays the data. There is the following code that connects the models to the treeview widget:

in the header of the main window:

...
StatisticsModel* _statisticsModel;
StatisticsProxyModel* _statisticsProxyModel;
...

in the constructor of the main widow

...
_statisticsModel = new StatisticsModel(ths);
_statisticsProxyModel = new StatisticsProxyModel(htis);
...
_statisticsProxyModel->setSourceModel(_statisticsModel);
ui->statisticsTreeView->setModel(_statisticsProxyModel);
...

The application also has a button that allows me to remove the selected item from the model - I'm currently only testing with the QTreeView::selectionModel()->currentIndex, no multi-selections for the time being.

I have the following code for the StatisticsModel::removeRows method

bool StatisticsModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    StatisticsEntry *parentItem = getItem(parent);

    bool success1 = true;
    bool success2 = true;
    beginRemoveRows(parent, position, position + rows - 1);
    for (int i = position + rows - 1; i >= position; i--)
    {
        QModelIndex child = index(i, 0, parent);
        QString title = this->title(child); // the title method is the getter method that matches the setTitle one 
        success1 = success1 && removeRows(0, rowCount(child), child); //rowCount is implemented to return the number of items stored in StatisticsEntry::_children list for the specified parent index
        success2 = success2 && parentItem->removeChild(i); // deletes an entry from the StatisticsEntry::_children list
    }
    endRemoveRows();
    return success1 && success2;
}

The problem is that sometimes when I remove an item using QAbstractItemModel::removeRow method I get an exception and the stack trace looks like:

StatisticsModel::parent StatisticsModel.cpp 307 0x13e7bf8   
QModelIndex::parent qabstractitemmodel.h    393 0x72e57265  
QSortFilterProxyModelPrivate::source_to_proxy   qsortfilterproxymodel.cpp   386 0x711963e2  
QSortFilterProxyModel::mapFromSource    qsortfilterproxymodel.cpp   2514    0x7119d28b  
QSortFilterProxyModel::parent   qsortfilterproxymodel.cpp   1660    0x7119a32c  
QModelIndex::parent qabstractitemmodel.h    393 0x72e57265  
QPersistentModelIndex::parent   qabstractitemmodel.cpp  347 0x72e58b86  
QItemSelectionRange::isValid    qitemselectionmodel.h   132 0x70e3f62b  
QItemSelection::merge   qitemselectionmodel.cpp 466 0x711503d1  
QItemSelectionModelPrivate::finalize    qitemselectionmodel_p.h 92  0x7115809a  
QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved qitemselectionmodel.cpp 623 0x71151132  
QItemSelectionModel::qt_static_metacall moc_qitemselectionmodel.cpp 113 0x711561c2  
QMetaObject::activate   qobject.cpp 3547    0x72e8d9a4  
QAbstractItemModel::rowsAboutToBeRemoved    moc_qabstractitemmodel.cpp  204 0x72f08a76  
QAbstractItemModel::beginRemoveRows qabstractitemmodel.cpp  2471    0x72e5c53f  
QSortFilterProxyModelPrivate::remove_proxy_interval qsortfilterproxymodel.cpp   558 0x71196ce7  
QSortFilterProxyModelPrivate::remove_source_items   qsortfilterproxymodel.cpp   540 0x71196c7f  
QSortFilterProxyModelPrivate::source_items_about_to_be_removed  qsortfilterproxymodel.cpp   841 0x71197c77  
QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeRemoved qsortfilterproxymodel.cpp   1291    0x711995cc  
QSortFilterProxyModel::qt_static_metacall   moc_qsortfilterproxymodel.cpp   115 0x7119d506  
QMetaCallEvent::placeMetaCall   qobject.cpp 525 0x72e883fd  
QObject::event  qobject.cpp 1195    0x72e894ba  
QApplicationPrivate::notify_helper  qapplication.cpp    4550    0x709d710e  
QApplication::notify    qapplication.cpp    3932    0x709d4d87  
QCoreApplication::notifyInternal    qcoreapplication.cpp    876 0x72e6b091

Oddly enough this seems to happen after all immediate method calls concerning the item removal are already over. It seems that the proxy model is looking for model indices that should not be present anymore (or so I think). The StatisticsModel::parent method is as follows:

QModelIndex StatisticsModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    StatisticsEntry *childItem = getItem(index);
    StatisticsEntry *parentItem = childItem->parent();

    if (NULL != parentItem)
        return createIndex(parentItem->childNumber(), 0, parentItem);
    return QModelIndex();
}

When the exception happens the values associated with the childItem and parentItem variables from the above method seem invalid - either the pointers themselves point to non-accessible memory or the member QLists either have no entries or their entries give Memory access violation. It may be that the parent method is not correct, but then how to fetch the parent index - the qabstractItemModel documentation discourages the usage of QModelIndex::parent in that method for it will create an infinite recursion.

Any help would be appreciated,

Answer

Chris Browet picture Chris Browet · Mar 20, 2012

When doing your StatisticsModel::removeRows, You are nesting begingRemoveRows/endRemoveRows, which, AFAIK, does not work. You should do:

bool StatisticsModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    StatisticsEntry *parentItem = getItem(parent);

    bool success1 = true;
    bool success2 = true;
    for (int i = position + rows - 1; i >= position; i--)
    {
        QModelIndex child = index(i, 0, parent);
        QString title = this->title(child); // the title method is the getter method that matches the setTitle one 
        success1 = success1 && removeRows(0, rowCount(child), child); //rowCount is implemented to return the number of items stored in StatisticsEntry::_children list for the specified parent index
        beginRemoveRows(parent, i, i);
        success2 = success2 && parentItem->removeChild(i); // deletes an entry from the StatisticsEntry::_children list
        endRemoveRows();
    }
    return success1 && success2;
}