如何在QTreeView的dropEvent事件中为拖拽项设置对应图标?
解决QTreeView拖拽项后设置图标及重复项检测问题
我来帮你搞定这个需求——把Master Data下的用户项拖拽到Security Model的对应层级后,自动设置正确的用户图标,同时避免重复添加相同项。咱们直接调整你的代码,核心修改集中在dropEvent方法里,同时优化一些逻辑细节:
修改后的完整代码
#!/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() 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): # 获取拖拽的源项 selected_indexes = self.selectedIndexes() if not selected_indexes: super().dropEvent(event) return source_index = selected_indexes[0] source_item = self.model().itemFromIndex(source_index) source_text = source_item.text() # 获取目标父节点:如果拖拽到项上,就用该节点的父节点;如果拖拽到空白区域,用当前节点 target_index = self.indexAt(event.pos()) if target_index.isValid(): # 判断目标节点的层级,如果是用户层级(CLS_LVL_USER),则父节点是stakeholder target_level = get_tree_selection_level(target_index) if target_level == CLS_LVL_USER: target_parent_item = self.model().itemFromIndex(target_index.parent()) else: target_parent_item = self.model().itemFromIndex(target_index) else: # 拖拽到根节点区域,直接返回(不符合需求场景) super().dropEvent(event) return # 验证拖拽场景:只有从Master Data的Users拖到Security Model的Stakeholder下才处理 # 先判断源项的父节点是否是Users source_parent_index = source_index.parent() if not source_parent_index.isValid() or source_parent_index.data() != TXT_USER: super().dropEvent(event) return # 判断目标父节点是否是Stakeholder层级(CLS_LVL_STAKEHLD) target_parent_level = get_tree_selection_level(target_parent_item.index()) if target_parent_level != CLS_LVL_STAKEHLD: super().dropEvent(event) return # 1. 检测重复项:检查目标父节点下是否已有同名子项 duplicate_exists = False for row in range(target_parent_item.rowCount()): child_item = target_parent_item.child(row) if child_item.text() == source_text: duplicate_exists = True break if duplicate_exists: # 如果重复,取消拖拽操作 event.ignore() return # 2. 设置正确的用户图标并添加项 # 先调用父类的dropEvent完成默认拖拽操作 super().dropEvent(event) # 找到刚添加的项,设置图标 # 因为刚添加,所以取目标父节点的最后一个子项(或者遍历找同名的) new_item = None for row in range(target_parent_item.rowCount()): child_item = target_parent_item.child(row) if child_item.text() == source_text: new_item = child_item break if new_item: new_item.setIcon(QtGui.QIcon(ICON_LVL_USER)) # 展开目标父节点,方便查看新添加的项 self.setExpanded(target_parent_item.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_()
关键修改说明
源项与目标节点的精准判断:
- 首先确认拖拽的源项来自
Master Data下的Users节点,避免其他类型项的误操作 - 确保目标父节点是
Security Model下的Stakeholder层级(也就是你说的client_c/stakeholder_d的子层级)
- 首先确认拖拽的源项来自
重复项检测逻辑:
- 在添加前遍历目标父节点的所有子项,检查是否已有同名项,如果有则直接忽略拖拽操作,避免重复
图标设置时机:
- 先调用父类的
dropEvent完成默认的拖拽添加操作,然后找到刚添加的子项,给它设置ICON_LVL_USER图标,这样就和user_b的图标保持一致了
- 先调用父类的
优化目标节点判断:
- 处理了拖拽到用户项上的情况,此时自动将父节点设为该用户项的父节点(也就是Stakeholder节点),符合你的层级要求
内容的提问来源于stack exchange,提问作者ProfP30
相关产品推荐
相关产品推荐

