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

PyQt5 QTreeView如何实现根项仅顶层重排、子项限父层级内移动

解决PyQt5 QTreeView中根项与子项的拖拽限制问题

需求说明

  • 顶层根项(代表文档)仅可在顶层调整顺序,始终不能被拖入其他根项的子层级
  • 子项(代表章节)仅能在所属顶层父项的子层级内移动,不能跨根项移动,也不能被拖到顶层根项层级

问题分析

原代码的dropEvent逻辑存在两处错误:直接禁止了根项之间的拖拽操作,导致根项无法调整顺序;同时未限制子项的跨父项拖拽,导致子项可被移出所属根项。

修正后的代码实现

from PyQt5.QtWidgets import QApplication, QTreeView, QMainWindow, QVBoxLayout, QWidget
from PyQt5.QtGui import QStandardItem, QStandardItemModel


class MyTreeView(QTreeView):
    def _get_top_level_parent(self, index):
        """获取索引对应的顶层根项索引"""
        current = index
        while current.parent().isValid():
            current = current.parent()
        return current

    def dropEvent(self, e):
        dragged_index = e.source().currentIndex()
        dest_index = self.indexAt(e.pos())

        # 获取拖拽项的顶层根项
        dragged_top_parent = self._get_top_level_parent(dragged_index)
        # 获取目标位置的顶层根项(处理拖到空白区域的情况)
        if dest_index.isValid():
            dest_top_parent = self._get_top_level_parent(dest_index)
        else:
            parent_index = self.indexAt(e.pos()).parent()
            dest_top_parent = self._get_top_level_parent(parent_index) if parent_index.isValid() else parent_index

        # 根项拖拽规则:只能在顶层根项区域移动
        if not dragged_index.parent().isValid():
            if dest_index.isValid() and dest_index.parent().isValid():
                # 禁止根项拖入子项层级
                e.ignore()
                return
        # 子项拖拽规则:只能在所属根项的子层级内移动
        else:
            if dragged_top_parent != dest_top_parent or not dest_top_parent.isValid():
                # 禁止跨根项移动,禁止拖入顶层根项区域
                e.ignore()
                return

        # 合法拖拽,执行默认处理逻辑
        super().dropEvent(e)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.tree_view = MyTreeView(self)
        self.tree_view.setDragEnabled(True)
        self.tree_view.setAcceptDrops(True)
        self.tree_view.setDropIndicatorShown(True)
        self.tree_view.setDragDropMode(QTreeView.InternalMove)

        self.model = QStandardItemModel()

        # 创建示例数据
        root_item1 = QStandardItem("文档1")
        child_item11 = QStandardItem("章节1.1")
        child_item12 = QStandardItem("章节1.2")
        root_item1.appendRow(child_item11)
        root_item1.appendRow(child_item12)

        root_item2 = QStandardItem("文档2")
        child_item21 = QStandardItem("章节2.1")
        child_item22 = QStandardItem("章节2.2")
        root_item2.appendRow(child_item21)
        root_item2.appendRow(child_item22)

        self.model.appendRow(root_item1)
        self.model.appendRow(root_item2)

        self.tree_view.setModel(self.model)

        layout = QVBoxLayout()
        layout.addWidget(self.tree_view)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec_()

关键逻辑说明

  1. 顶层根项识别:通过_get_top_level_parent方法循环向上查找父项,最终定位到根项,用于判断子项是否属于同一文档。
  2. 根项拖拽限制:仅允许根项在顶层区域移动(包括根项之间的前后位置或顶层空白处),禁止拖入任何子项层级。
  3. 子项拖拽限制:子项的目标位置必须与自身同属一个顶层根项,且不能拖入顶层根项区域,确保章节只能在所属文档内调整顺序。
  4. 空白区域处理:针对拖拽到空白区域的场景,通过鼠标位置判断所属层级,避免合法拖拽被误拦截。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 10:45:06