【问题标题】:Qt QAbstractItemModel - item removal crashesQt QAbstractItemModel - 项目删除崩溃
【发布时间】:2023-04-07 09:50:01
【问题描述】:

我正在使用具有层次结构的 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 ,因为它会创建无限递归。

任何帮助将不胜感激,

【问题讨论】:

  • 已经有很多代码了,但请您也显示StatisticsEntry::removeChild

标签: c++ qt qabstractitemmodel


【解决方案1】:

当你做你的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;
}

【讨论】:

  • 如果我没记错的话,removeRows 是由 QAbstractItemModel::removeRow 方法调用的,据我所知,removeRows 中的父参数应该是我正在尝试的索引的父 QModelIndex去除。在这种情况下,我希望将调用 removeRows 并将 position 参数设置为我要删除的 QModelIdnex 的实际行,并将 rows 参数设置为 1。因此我相信在 for 构造之前对 beginRemoveRows 方法的调用将被启动删除我要删除的那个 QModelIdnex。
  • 实际上,我正在为属于父项并计划删除的每个 QModelIndex 子项创建一个对 removeRows 的递归调用 - 我也想删除所有底层项目。还是我弄错了?
  • 没错。那么可能是 begin../end... 的嵌套导致问题。答案已更新。
  • 10 倍的帮助。我还在考虑那个。
  • 会的。但是,经过一番调查,结果发现我的问题可能是因为模型正在除 GUI 之外的线程中更新(我注意到有时我会在调试输出中收到不一致的更改警告)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-30
相关资源
最近更新 更多