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

QTreeView拖拽时如何避免添加已存在的节点?

阻止QTreeView中重复节点的拖拽操作

嘿,我清楚你要解决的问题——当尝试把一个节点拖拽到某个父节点下,而这个父节点已经存在同名子节点时,要彻底禁止这个拖拽动作。咱们来调整你的代码实现这个功能,核心就是在dropEvent里先检查目标父节点下有没有重复的节点,有的话直接忽略拖拽事件。

核心思路

  1. 获取拖拽源节点的文本内容,这是我们要校验的目标
  2. 根据拖拽的位置确定目标父节点(区分是作为子节点还是兄弟节点添加)
  3. 遍历目标父节点的所有子节点,检查是否存在同名节点
  4. 如果存在重复或者不满足原有的层级拖拽规则,直接阻止拖拽事件;否则执行默认的drop操作

修改后的完整可运行代码

#!/usr/bin/env python3
# coding = utf-8
from PyQt5 import QtWidgets, QtCore, QtGui

TXT_CLIENT = "Clients"
TXT_STAKEHLD = "Stakeholders"
TXT_USER = "Users"
TXT_SYSTEM = "Master Data"
TXT_SECURITY = "Security Model"

CLS_LVL_ROOT = 0
CLS_LVL_CLIENT = 1
CLS_LVL_STAKEHLD = 2
CLS_LVL_USER = 3

ICON_LVL_CLIENT = "img/icons8-bank-16.png"
ICON_LVL_STAKEHLD = "img/icons8-initiate-money-transfer-24.png"
ICON_LVL_USER = "img/icons8-checked-user-male-32.png"

DATA = [
    (TXT_SYSTEM, [
        (TXT_USER, [
            ("user_a", []),
            ("user_b", [])
        ]),
        (TXT_CLIENT, [
            ("client_a", []),
            ("client_b", []),
            ("client_c", []),
            ("client_d", [])
        ]),
        (TXT_STAKEHLD, [
            ("stakeholder_a", []),
            ("stakeholder_b", []),
            ("stakeholder_c", []),
            ("stakeholder_d", [])
        ])
    ]),
    (TXT_SECURITY, [
        ("client_a", [
            ("stakeholder_b",[
                ("user_a",[])
            ])
        ]),
        ("client_c", [
            ("stakeholder_d",[
                ("user_b",[])
            ])
        ])
    ])
]

def create_tree_data(tree):
    model = QtGui.QStandardItemModel()
    addItems(tree, model, DATA)
    tree.setModel(model)

def addItems(tree, parent, elements, level=0, root=0):
    level += 1
    for text, children in elements:
        if text == TXT_SYSTEM:
            root = 1
        elif text == TXT_SECURITY:
            root = 2
        item = QtGui.QStandardItem(text)
        icon = QtGui.QIcon(TXT_USER)
        item.setIcon(icon)
        parent.appendRow(item)
        if root==1:
            if children:
                if text == TXT_CLIENT:
                    icon = QtGui.QIcon(ICON_LVL_CLIENT)
                    item.setIcon(icon)
                elif text == TXT_STAKEHLD:
                    icon = QtGui.QIcon(ICON_LVL_STAKEHLD)
                    item.setIcon(icon)
                elif text == TXT_USER:
                    icon = QtGui.QIcon(ICON_LVL_USER)
                    item.setIcon(icon)
        elif root == 2:
            if level == 2:
                icon = QtGui.QIcon(ICON_LVL_CLIENT)
                item.setIcon(icon)
            if level == 3:
                icon = QtGui.QIcon(ICON_LVL_STAKEHLD)
                item.setIcon(icon)
            elif level == 4:
                icon = QtGui.QIcon(ICON_LVL_USER)
                item.setIcon(icon)
        addItems(tree, item, children, level, root)

def get_tree_selection_level(index):
    level = 0
    while index.parent().isValid():
        index = index.parent()
        level += 1
    return level

class TreeView(QtWidgets.QTreeView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.initUI()

    def initUI(self):
        self.setHeaderHidden(True)
        self.setColumnHidden(1, True)
        self.setSelectionMode(self.SingleSelection)
        self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) # InternalMove)

    def dropEvent(self, event):
        tree = event.source()
        # 获取源节点的文本和父节点文本
        if not self.selectedIndexes():
            event.ignore()
            return
        source_index = self.selectedIndexes()[0]
        source_text = source_index.data()
        source_parent_text = source_index.parent().data() if source_index.parent().isValid() else ""

        to_index = self.indexAt(event.pos())
        if not to_index.isValid():
            super().dropEvent(event)
            return

        # 根据拖拽位置确定目标父节点
        if self.dropIndicatorPosition() in [QtWidgets.QAbstractItemView.AboveItem, QtWidgets.QAbstractItemView.BelowItem]:
            target_parent = to_index.parent()
        else:
            target_parent = to_index

        # 检查目标父节点下是否已存在同名节点
        node_exists = False
        if target_parent.isValid():
            for row in range(self.model().rowCount(target_parent)):
                child_index = self.model().index(row, 0, target_parent)
                if child_index.data() == source_text:
                    node_exists = True
                    break

        # 校验原有层级拖拽规则
        target_level = get_tree_selection_level(to_index)
        valid_drop = (
            (source_parent_text == TXT_CLIENT and target_level == CLS_LVL_ROOT) or
            (source_parent_text == TXT_STAKEHLD and target_level == CLS_LVL_CLIENT) or
            (source_parent_text == TXT_USER and target_level == CLS_LVL_STAKEHLD)
        )

        # 不满足规则或节点已存在则阻止拖拽
        if not valid_drop or node_exists:
            event.ignore()
            return

        # 执行默认拖拽操作
        super().dropEvent(event)
        self.setExpanded(to_index, True)

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

    def initUI(self):
        centralwidget = QtWidgets.QWidget()
        self.setCentralWidget(centralwidget)
        hBox = QtWidgets.QHBoxLayout(centralwidget)
        self.treeView = TreeView(centralwidget)
        hBox.addWidget(self.treeView)

if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    window = MainWindow()
    create_tree_data(window.treeView)
    window.treeView.expand(window.treeView.model().index(0, 0)) # expand the System-Branch
    window.setGeometry(400, 400, 500, 400)
    window.show()
    app.exec_()

关键修改说明

  • 源节点信息获取:直接从选中的节点获取文本和父节点文本,替代了原代码中解析mimeData的冗余逻辑
  • 目标父节点判断:通过dropIndicatorPosition区分拖拽位置,准确找到要添加节点的父容器
  • 重复节点校验:遍历目标父节点的所有子节点,对比文本判断是否存在重复
  • 事件拦截:如果不满足层级规则或存在重复节点,调用event.ignore()彻底阻止拖拽动作

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:13:56