主要功能概述
允许用户在QTreeWidget内部拖拽项目
拖拽时显示确认对话框
程序环境
Python 3.8.9
pyside6==6.1.3
pip install pyside6==6.1.3
实现效果
demo代码获取
Gitee:treewidget-demo
百度网盘:https://pan.baidu.com/s/1rDrZUyrjrqmDrFVSdUgbQA?pwd=hp9b
代码实现
以下是完整的实现代码:
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QMessageBox, QAbstractItemView
import sys
from untitled2 import *class MainWindow(QMainWindow, Ui_Form):def __init__(self):super().__init__()central_widget = QWidget()self.setCentralWidget(central_widget)self.setupUi(central_widget)# 设置拖放属性self.file_list.setDragEnabled(True)self.file_list.setAcceptDrops(True)self.file_list.setDragDropMode(QAbstractItemView.InternalMove)# 重写 dropEventdef new_drop_event(event: QDropEvent):source = event.source()if source is not self.file_list:event.ignore()return# 获取目标位置target_index = self.file_list.indexAt(event.position().toPoint())if not target_index.isValid():event.ignore()return# 获取所有选中的项目(不只是 currentItem)selected_items = self.file_list.selectedItems()if not selected_items:event.ignore()return# 获取目标项目target_item = self.file_list.itemFromIndex(target_index)# 显示确认对话框target_name = target_item.text(0) if target_item else "列表末尾"reply = QMessageBox.question(self,"确认移动",f"确定要将项目: {len(selected_items)}个项目\n移动到: {target_name} 之前吗?",QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)if reply != QMessageBox.StandardButton.Yes:event.ignore()returnif event.proposedAction() == Qt.MoveAction:print(f"移动 {len(selected_items)} 个项目")# 如果是移动到目标项目上(覆盖)if target_item:print(f"覆盖到 {target_item.text(0)}")# 获取目标位置的行号target_row = target_index.row()# 克隆所有选中的项目并插入到目标位置parent = self.file_list.invisibleRootItem()for i, item in enumerate(selected_items):new_item = item.clone() # 复制项目parent.insertChild(target_row + i, new_item)else:# 直接移动所有项目到末尾parent = self.file_list.invisibleRootItem()for item in selected_items:parent.addChild(item.clone()) # 或者直接移动 itemevent.accept() # 接受事件,阻止默认行为else:event.ignore()self.file_list.dropEvent = new_drop_event# 正确连接信号的方式self.file_list.model().rowsAboutToBeRemoved.connect(self.on_rows_about_to_be_removed)# 用于跟踪移动的临时变量self.moved_items_cache = []def on_rows_about_to_be_removed(self, parent: QModelIndex, start: int, end: int):"""在项目被移除前缓存被移动的项目"""if not parent.isValid(): # 只处理顶级项目self.moved_items_cache = []for i in range(start, end + 1):item = self.file_list.topLevelItem(i)if item:self.moved_items_cache.append(item.text(0))print(self.moved_items_cache)print("移动至位置start",start,end)if __name__ == "__main__":app = QApplication(sys.argv)window = MainWindow()window.show()sys.exit(app.exec())
代码解析
1. 初始化设置
# 设置拖放属性
self.file_list.setDragEnabled(True)
self.file_list.setAcceptDrops(True)
self.file_list.setDragDropMode(QAbstractItemView.InternalMove)
这三行代码启用了QTreeWidget的拖放功能,并设置为内部移动模式(InternalMove
),这意味着只允许在控件内部进行拖拽操作。
2. 重写dropEvent
重写了QTreeWidget的dropEvent
方法,以实现自定义的拖放逻辑:
def new_drop_event(event: QDropEvent):# 检查来源是否是当前控件source = event.source()if source is not self.file_list:event.ignore()return
首先检查拖拽事件的来源,如果不是来自当前QTreeWidget,则忽略该事件。
3. 获取目标位置
通过indexAt
方法获取鼠标释放位置对应的项目索引,如果位置无效则忽略事件。
# 获取目标位置
target_index = self.file_list.indexAt(event.position().toPoint())
if not target_index.isValid():event.ignore()return
4. 获取选中项目
获取所有被选中的项目,而不是仅获取当前项目(currentItem
),这样可以支持多选拖拽。
# 获取所有选中的项目(不只是 currentItem)
selected_items = self.file_list.selectedItems()
if not selected_items:event.ignore()return
5. 显示确认对话框
在移动前显示确认对话框,显示将要移动的项目数量和目标位置。
# 显示确认对话框
target_name = target_item.text(0) if target_item else "列表末尾"reply = QMessageBox.question(self,"确认移动",f"确定要将项目: {len(selected_items)}个项目\n移动到: {target_name} 之前吗?",QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)if reply != QMessageBox.StandardButton.Yes:event.ignore()return
6. 处理移动逻辑
根据拖拽动作类型处理移动逻辑:
- 如果是移动动作(
MoveAction
),则克隆选中的项目并插入到目标位置 - 如果目标位置无效,则将项目添加到末尾
if event.proposedAction() == Qt.MoveAction:print(f"移动 {len(selected_items)} 个项目")# 如果是移动到目标项目上(覆盖)if target_item:print(f"覆盖到 {target_item.text(0)}")# 获取目标位置的行号target_row = target_index.row()# 克隆所有选中的项目并插入到目标位置parent = self.file_list.invisibleRootItem()for i, item in enumerate(selected_items):new_item = item.clone() # 复制项目parent.insertChild(target_row + i, new_item)else:# 直接移动所有项目到末尾parent = self.file_list.invisibleRootItem()for item in selected_items:parent.addChild(item.clone()) # 或者直接移动 itemevent.accept() # 接受事件,阻止默认行为
else:event.ignore()
7. 跟踪项目移动
连接rowsAboutToBeRemoved
信号到自定义槽函数,用于跟踪被移动的项目。
# 连接信号
self.file_list.model().rowsAboutToBeRemoved.connect(self.on_rows_about_to_be_removed)# 跟踪变量
self.moved_items_cache = []
在项目被移除前,缓存这些项目的文本内容,可用于后续的操作。
def on_rows_about_to_be_removed(self, parent: QModelIndex, start: int, end: int):"""在项目被移除前缓存被移动的项目"""if not parent.isValid(): # 只处理顶级项目self.moved_items_cache = []for i in range(start, end + 1):item = self.file_list.topLevelItem(i)if item:self.moved_items_cache.append(item.text(0))print(self.moved_items_cache)print("移动至位置start",start,end)