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

QTreeView中自定义QWidget作为项代理不显示问题排查

问题分析与解决建议

尝试将自定义复合QWidget作为QTreeView的项代理,发现树项尺寸已变大,但自定义Widget未显示,核心问题和修复建议如下:

自定义Widget为包含多控件的复合界面(如标签、按钮等)。

核心问题

QStyledItemDelegate的createEditor方法仅在项进入编辑状态时才会创建编辑器,默认情况下树项处于显示状态,不会触发该方法,所以自定义Widget不会显示。此外代码还存在模型实现错误,导致代理无法正常工作。

修复步骤

1. 修正模型(NodeModel)的错误

错误点:

  • data方法仅处理重复的EditRole,未处理DisplayRole,导致项无法正常显示基础内容
  • flags方法错误将DisplayRole(数据角色)加入项标志,应使用状态类标志
  • rowCount方法固定返回root的子项数,未根据父节点动态返回,导致子节点无法正确加载

修正后的NodeModel代码:

class NodeModel(QtCore.QAbstractItemModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.headers = ["Node"]
        self.root_node = BaseNode(["root"])

    def getNode(self, index):
        if index.isValid():
            node = index.internalPointer()
            if node:
                return node
        return self.root_node

    def rowCount(self, parent=QtCore.QModelIndex()):
        parent_node = self.getNode(parent)
        return parent_node.rowCount()  # 根据父节点返回子项数量

    def columnCount(self, parent=QtCore.QModelIndex()):
        return self.root_node.columnCount()

    def data(self, index, role):
        if not index.isValid():
            return None
        node = self.getNode(index)
        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            return node.data(index.column())  # 返回节点对应列的数据而非node本身

    def headerData(self, section, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self.headers[section]

    def flags(self, index):
        flgs = QtCore.Qt.NoItemFlags
        if index.isValid():
            # 使用正确的项状态标志
            flgs = QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
        return flgs

    def parent(self, child):
        node = self.getNode(child)
        parent = node.parent()
        if parent == self.root_node or parent is None:
            return QtCore.QModelIndex()
        return self.createIndex(parent.index(), 0, parent)

    def index(self, row, column, parent):
        parent_node = self.getNode(parent)
        child = parent_node.child(row)
        if child:
            return self.createIndex(row, column, child)
        return QtCore.QModelIndex()

    def insertRows(self, row, count, parent=QtCore.QModelIndex(), children=None):
        parent_node = self.getNode(parent)
        self.beginInsertRows(parent, row, row + count - 1)
        result = parent_node.insertChildren(row, children)
        self.endInsertRows()
        return result

    def removeRows(self, row, count, parent=QtCore.QModelIndex()):
        parent_node = self.getNode(parent)
        self.beginRemoveRows(parent, row, row + count - 1)
        success = parent_node.removeChildren(row, count)
        self.endRemoveRows()
        return success

2. 修改代理(NodeDelegate),让自定义Widget持续显示

有两种可行方案:

方案一:使用持久编辑器(推荐,支持交互)

为目标项打开持久编辑器,让编辑器始终显示:

class NodeDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._size_hint = None

    def createEditor(self, parent, option, index):
        if index.column() == 0:
            editor = ItemEditor(parent)  # 传入父控件避免内存泄漏
            return editor
        return super().createEditor(parent, option, index)

    def setEditorData(self, editor, index):
        if index.column() == 0:
            # 此处可根据模型数据更新Widget内容,示例:
            # editor.ui.label.setText(index.data(QtCore.Qt.DisplayRole))
            pass
        else:
            super().setEditorData(editor, index)

    def setModelData(self, editor, model, index):
        if index.column() == 0:
            # 此处可将Widget内容同步回模型,示例:
            # model.setData(index, editor.ui.input.text())
            pass
        else:
            super().setModelData(editor, model, index)

    def sizeHint(self, option, index):
        # 缓存尺寸,避免重复创建Widget
        if not self._size_hint:
            editor = ItemEditor()
            self._size_hint = editor.sizeHint()
        return self._size_hint

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)

在树视图代码中打开持久编辑器:

app = QtWidgets.QApplication([])
tw = QtWidgets.QTreeView()
model = NodeModel()
tw.setModel(model)

# 正确插入节点到root下
for i in "abcd":
    node = BaseNode(data=[i])
    model.insertRows(model.root_node.rowCount(), 1, QtCore.QModelIndex(), children=[node])

item_delegate = NodeDelegate()
tw.setItemDelegate(item_delegate)

# 为所有root子项打开持久编辑器
for row in range(model.root_node.rowCount()):
    index = model.index(row, 0, QtCore.QModelIndex())
    tw.openPersistentEditor(index)

tw.show()
app.exec_()

方案二:重写paint方法绘制Widget(适合无交互场景)

若不需要Widget的交互功能,可直接绘制内容到项区域:

class NodeDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._editor = ItemEditor()

    def paint(self, painter, option, index):
        if index.column() == 0:
            self._editor.setGeometry(option.rect)
            painter.save()
            painter.translate(option.rect.topLeft())
            self._editor.render(painter)
            painter.restore()
        else:
            super().paint(painter, option, index)

    def sizeHint(self, option, index):
        return self._editor.sizeHint()

3. 其他细节修正

  • 确保ItemEditor中的Ui_Form正确加载,检查ui文件导入路径
  • 修正原插入节点的位置错误:直接传入QtCore.QModelIndex()作为父节点(代表root),避免无效索引

总结

  1. 修复模型的data和flags方法,确保数据正常传递
  2. 通过持久编辑器或重写paint方法让自定义Widget显示
  3. 修正节点插入逻辑,确保节点正确添加到树结构中

内容的提问来源于stack exchange,提问作者Rahul

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 18:01:16