QTreeView拖拽时如何避免添加已存在的节点?
阻止QTreeView中重复节点的拖拽操作
嘿,我清楚你要解决的问题——当尝试把一个节点拖拽到某个父节点下,而这个父节点已经存在同名子节点时,要彻底禁止这个拖拽动作。咱们来调整你的代码实现这个功能,核心就是在dropEvent里先检查目标父节点下有没有重复的节点,有的话直接忽略拖拽事件。
核心思路
- 获取拖拽源节点的文本内容,这是我们要校验的目标
- 根据拖拽的位置确定目标父节点(区分是作为子节点还是兄弟节点添加)
- 遍历目标父节点的所有子节点,检查是否存在同名节点
- 如果存在重复或者不满足原有的层级拖拽规则,直接阻止拖拽事件;否则执行默认的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
相关产品推荐
相关产品推荐

