如何在QSortFilterProxyModel中正确重写parent()方法修改树结构
问题解决:代理模型重写
parent()未改变树结构的处理方案 问题描述
我有一个继承自QAbstractItemModel的ObjectTreeModel类,模型存储的ObjectTreeItem元素带有_placement属性,该属性是另一个ObjectTreeItem的uuid。希望当元素存在该属性时,ObjectTreeModel::parent()返回对应的placement元素而非实际父元素,以此改变树结构。我在代理模型SortFilterProxyTreeModel(继承自QSortFilterProxyModel)中重写了parent()方法,但树结构并未改变,请问如何正确实现?
原ObjectTreeModel::parent()代码:
QModelIndex ObjectTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); ObjectTreeItem *childItem = getItem(index); ObjectTreeItem *parentItem = childItem ? childItem->parent() : nullptr; if (parentItem == rootItem || !parentItem) return QModelIndex(); return createIndex(parentItem->childNumber(), 0, parentItem); }
代理模型中重写的parent()代码:
QModelIndex SortFilterProxyTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); // get original model ObjectTreeModel* treeModel = static_cast< ObjectTreeModel* >( sourceModel() ); // convert the index from SortFilterProxyTreeModel to ObjectTreeModel QModelIndex sourceIndex = mapToSource( index ); ObjectTreeItem *item = treeModel->getItem(sourceIndex); QModelIndex parentIndex; // if the uuid placement property exists, then we get its index, otherwise — the real parent. // the isPlacementParent variable is needed for optional behavior, by default it is true. const QUuid placementUuid = item->payload->_placement.value(); if( isPlacementParent && !placementUuid.isNull() ) { ObjectTreeItem* placementItem = treeModel->items().value( placementUuid ); parentIndex = treeModel->createThisIndex(placementItem->childNumber(), 0, placementItem); } else { ObjectTreeItem *parentItem = item ? item->parent() : nullptr; if (parentItem == treeModel->rootItem || !parentItem) return QModelIndex(); parentIndex = treeModel->createThisIndex(parentItem->childNumber(), 0, parentItem); } // convert parentIndex from ObjectTreeModel to SortFilterProxyTreeModel return mapFromSource( parentIndex ); }
核心问题分析
只重写parent()方法不足以让QSortFilterProxyModel正确识别自定义树结构,因为代理模型的默认实现依赖源模型的父子关系来构建索引、计算子项数量等。必须同时重写多个核心方法,确保代理模型完全适配自定义的父节点逻辑。
正确实现步骤
1. 重写index()方法
代理模型需要根据自定义父节点逻辑创建索引,而非依赖源模型默认结构:
QModelIndex SortFilterProxyTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); ObjectTreeModel* treeModel = static_cast<ObjectTreeModel*>(sourceModel()); ObjectTreeItem* parentItem = nullptr; if (!parent.isValid()) { parentItem = treeModel->rootItem; } else { QModelIndex sourceParent = mapToSource(parent); parentItem = treeModel->getItem(sourceParent); } // 筛选当前父节点的所有子项(根据placement属性匹配) QList<ObjectTreeItem*> childItems; for (auto item : treeModel->items()) { if (isPlacementParent && !item->payload->_placement.value().isNull()) { if (item->payload->_placement.value() == parentItem->uuid()) { childItems.append(item); } } else { // 未启用placement时使用原父子关系 if (item->parent() == parentItem) { childItems.append(item); } } } if (row >= 0 && row < childItems.size()) { ObjectTreeItem* childItem = childItems[row]; QModelIndex sourceIndex = treeModel->createIndex(childItem->childNumber(), column, childItem); return mapFromSource(sourceIndex); } return QModelIndex(); }
2. 重写rowCount()方法
根据自定义父节点逻辑返回正确的子项数量:
int SortFilterProxyTreeModel::rowCount(const QModelIndex &parent) const { ObjectTreeModel* treeModel = static_cast<ObjectTreeModel*>(sourceModel()); ObjectTreeItem* parentItem = nullptr; if (!parent.isValid()) { parentItem = treeModel->rootItem; } else { QModelIndex sourceParent = mapToSource(parent); parentItem = treeModel->getItem(sourceParent); } int count = 0; for (auto item : treeModel->items()) { if (isPlacementParent && !item->payload->_placement.value().isNull()) { if (item->payload->_placement.value() == parentItem->uuid()) { count++; } } else { if (item->parent() == parentItem) { count++; } } } return count; }
3. 修正parent()方法的边界问题
处理placement节点不存在、placement为根节点的情况:
QModelIndex SortFilterProxyTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); ObjectTreeModel* treeModel = static_cast<ObjectTreeModel*>(sourceModel()); QModelIndex sourceIndex = mapToSource(index); ObjectTreeItem *item = treeModel->getItem(sourceIndex); if (!item) return QModelIndex(); QModelIndex parentIndex; const QUuid placementUuid = item->payload->_placement.value(); if (isPlacementParent && !placementUuid.isNull()) { ObjectTreeItem* placementItem = treeModel->items().value(placementUuid); if (!placementItem) { // placement节点不存在时回退到原父节点 ObjectTreeItem *parentItem = item->parent(); if (parentItem && parentItem != treeModel->rootItem) { parentIndex = treeModel->createIndex(parentItem->childNumber(), 0, parentItem); } } else if (placementItem == treeModel->rootItem) { // placement是根节点,返回无效索引 return QModelIndex(); } else { parentIndex = treeModel->createIndex(placementItem->childNumber(), 0, placementItem); } } else { ObjectTreeItem *parentItem = item->parent(); if (parentItem && parentItem != treeModel->rootItem) { parentIndex = treeModel->createIndex(parentItem->childNumber(), 0, parentItem); } } return parentIndex.isValid() ? mapFromSource(parentIndex) : QModelIndex(); }
4. 处理模型数据变更通知
当_placement属性修改时,必须触发模型重置,因为该变更会影响整个树结构:
// 在ObjectTreeItem的_placement属性修改后调用 void ObjectTreeModel::onPlacementChanged(ObjectTreeItem* item) { beginResetModel(); endResetModel(); }
关键注意事项
QSortFilterProxyModel的默认实现基于源模型父子关系,自定义树结构必须覆盖index()、rowCount()、parent()三个核心方法。- 必须处理placement节点不存在的边界情况,避免无效索引导致异常。
- placement属性变更时,必须触发模型重置,否则代理模型无法感知树结构变化。
内容的提问来源于stack exchange,提问作者ilyancod
相关产品推荐
相关产品推荐

