PySide6 QTableWidget单元格部件拖拽问题:覆盖丢失与无法回位
QTableWidget单元格Widget拖拽交换修复方案
存在问题
当前拖拽实现有两个核心问题:
- 拖拽到已有Widget的单元格时,原单元格Widget会被自动删除,无法保留或交换
- 无法将Widget放回初始拖拽位置
修复后代码
from PySide6.QtWidgets import QApplication, QHBoxLayout, QWidget, QPushButton, QTableWidget from PySide6.QtCore import Qt, QMimeData, QPoint from PySide6.QtGui import QDrag class DragButton(QPushButton): def mouseMoveEvent(self, e): if e.buttons() != Qt.LeftButton: return # 获取当前按钮所在的表格单元格位置 table = self.parentWidget() if not isinstance(table, QTableWidget): return row = table.indexAt(self.pos()).row() col = table.indexAt(self.pos()).column() if row < 0 or col < 0: return # 构造拖拽数据,携带原单元格行列信息 drag = QDrag(self) mime = QMimeData() mime.setData("text/row", str(row).encode()) mime.setData("text/col", str(col).encode()) drag.setMimeData(mime) drag.exec(Qt.MoveAction) class Window(QWidget): def __init__(self): super().__init__() self.setAcceptDrops(True) self.nb_col = 5 self.nb_row = 5 self.tbl = QTableWidget(self.nb_row, self.nb_col) self.tbl.setHorizontalHeaderLabels([str(x) for x in range(self.nb_col)]) self.tbl.setVerticalHeaderLabels([str(x) for x in range(self.nb_row)]) # 表格viewport需要接受拖拽 self.tbl.viewport().setAcceptDrops(True) self.layout = QHBoxLayout() self.layout.addWidget(self.tbl) nom = ['A', 'B', 'C', 'D'] for l in range(len(nom)): new_btn = DragButton(nom[l]) new_btn2 = DragButton(nom[l] + 'j') self.tbl.setCellWidget(0, l, new_btn) self.tbl.setCellWidget(l, 1, new_btn2) self.setLayout(self.layout) def dragEnterEvent(self, e): # 只接受DragButton的拖拽 if isinstance(e.source(), DragButton): e.acceptProposedAction() def dropEvent(self, e): # 获取拖拽携带的原单元格信息 mime = e.mimeData() if not (mime.hasFormat("text/row") and mime.hasFormat("text/col")): return original_row = int(mime.data("text/row").data().decode()) original_col = int(mime.data("text/col").data().decode()) dragged_widget = e.source() # 计算目标单元格位置(基于viewport的坐标) view_pos = self.tbl.viewport().mapFromGlobal(e.globalPosition().toPoint()) target_row = self.tbl.rowAt(view_pos.y()) target_col = self.tbl.columnAt(view_pos.x()) # 校验目标位置合法性 if target_row < 0 or target_col < 0 or target_row >= self.nb_row or target_col >= self.nb_col: return # 如果放回原位置,直接结束 if original_row == target_row and original_col == target_col: e.acceptProposedAction() return # 获取目标单元格的现有Widget target_widget = self.tbl.cellWidget(target_row, target_col) # 交换两个单元格的Widget self.tbl.setCellWidget(original_row, original_col, target_widget) self.tbl.setCellWidget(target_row, target_col, dragged_widget) e.acceptProposedAction() if __name__ == "__main__": app = QApplication([]) w = Window() w.showMaximized() app.exec()
关键修复点
- 拖拽数据携带位置信息:在DragButton的拖拽事件中,获取当前单元格的行列并存入MimeData,确保drop时能定位原位置
- 坐标计算修正:使用表格的viewport转换全局坐标,避免header区域干扰行列计算
- Widget交换逻辑:不再直接覆盖目标单元格Widget,而是将原位置和目标位置的Widget互相交换,保留所有组件
- 合法性校验:仅接受DragButton类型的拖拽源,同时校验目标行列的有效性
- 支持原位置放回:判断目标位置与原位置相同时不执行交换,允许将Widget拖回原位
内容的提问来源于stack exchange,提问作者CSonneck
相关产品推荐
相关产品推荐

