You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 19:15:36