Qt QAbstractItemModel - сбой удаления элемента - PullRequest
3 голосов
/ 20 марта 2012

Я пишу приложение, используя классы Qt, где у меня есть иерархическая структура, и мне нужно отобразить ее в виде дерева (я использую виджет QTreeView). Сами данные выглядят так:

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;
}

У меня есть класс, названный им StatisticsModel, который реализует QAbstractItemModel - используйте его для манипулирования и представления данных, хранящихся в дереве StatisticsEntry. В классе есть метод addStatisticsData, который я использую для добавления записей StatisticsEntry в модель. Метод выглядит примерно так:

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 и setDescription идентичны - вот один из методов setTitle:

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);
}

Метод setData выглядит следующим образом:

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;
}

Остался метод getItem:

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

    return _rootItem;
}

Это все, что есть, когда речь идет о добавлении новых и изменении существующих записей. В моем приложении я также реализовал QSortFilterProxyModel - ничего особенного, только метод lessThan. Я использую модель прокси для предоставления функции сортировки в QTreeView, которая отображает данные. Существует следующий код, который соединяет модели с виджетом в виде дерева:

в заголовке главного окна:

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

в конструкторе главной вдовы

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

Приложение также имеет кнопку, которая позволяет мне удалить выбранный элемент из модели - в настоящее время я тестирую только с QTreeView :: selectionModel () -> currentIndex, пока нет множественного выбора.

У меня есть следующий код для метода StatisticsModel :: removeRows

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;
}

Проблема в том, что иногда, когда я удаляю элемент с помощью метода QAbstractItemModel :: removeRow, я получаю исключение, и трассировка стека выглядит следующим образом:

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

Как ни странно, это происходит после того, как все непосредственные вызовы методов, касающиеся удаления элемента, уже завершены. Кажется, что модель прокси ищет индексы модели, которые больше не должны присутствовать (или я так думаю). Метод StatisticsModel :: parent выглядит следующим образом:

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();
}

Когда происходит исключение, значения, связанные с переменными childItem и parentItem из вышеприведенного метода, кажутся недействительными - либо сами указатели указывают на недоступную память, либо списки членов QLists либо не имеют записей, либо их записи приводят к нарушению доступа к памяти. Возможно, что родительский метод не верен, но тогда как извлечь родительский индекс - документация qabstractItemModel препятствует использованию QModelIndex :: parent в этом методе, поскольку он создает бесконечную рекурсию.

Любая помощь будет оценена,

1 Ответ

1 голос
/ 20 марта 2012

При выполнении StatisticsModel::removeRows вы вкладываете begingRemoveRows/endRemoveRows, что, AFAIK, не работает.Вы должны сделать:

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;
}
...