PyQt5中QAbstractTableModel整行拖拽功能实现求助
实现QAbstractTableModel的整行拖拽功能
你已经搭好了基础框架,设置了表格的选择行为和拖拽模式,现在只需要把MIME相关的三个函数改对就行——默认的MIME处理是针对单个单元格的,完全不适合整行移动的场景。下面是修改后的完整代码,我会一步步给你解释关键改动:
关键修改拆解
1. 自定义MIME类型
首先得定义一个专属的MIME类型,用来区分我们传递的整行数据和默认的单元格数据:
def mimeTypes(self): # 用自定义标识来标记整行数据 return ['application/vnd.tableview.rowdata']
2. 打包整行拖拽的数据(mimeData)
当拖拽开始时,我们需要把选中行的所有数据序列化后打包,这里用Qt的QDataStream来处理,确保数据在组件间传递的正确性:
def mimeData(self, indexes): mime_data = QtCore.QMimeData() if not indexes: return mime_data # 因为设置了单行选中,直接取第一个选中项的行索引 original_row = indexes[0].row() row_data = self.materials[original_row] # 序列化数据:先写列数,再逐列存数据,最后存原行索引(方便后续定位要删除的行) byte_array = QtCore.QByteArray() stream = QtCore.QDataStream(byte_array, QtCore.QIODevice.WriteOnly) stream.writeInt32(len(row_data)) for item in row_data: stream.writeQVariant(item) stream.writeInt32(original_row) mime_data.setData('application/vnd.tableview.rowdata', byte_array) return mime_data
3. 解析并处理拖拽数据(dropMimeData)
拖拽完成时,我们要解析传递过来的数据,把行移动到目标位置,还要处理移动动作对应的原行删除:
def dropMimeData(self, data, action, row, col, parent): if action == QtCore.Qt.IgnoreAction: return True # 先检查是不是我们自定义的MIME类型 if not data.hasFormat('application/vnd.tableview.rowdata'): return False # 解析序列化的数据 byte_array = data.data('application/vnd.tableview.rowdata') stream = QtCore.QDataStream(byte_array, QtCore.QIODevice.ReadOnly) col_count = stream.readInt32() row_data = [] for _ in range(col_count): row_data.append(stream.readQVariant()) original_row = stream.readInt32() # 确定插入位置:如果row是-1,说明插入到parent行的位置;否则插入到指定row处 insert_row = row if row != -1 else parent.row() # 处理拖拽到表格末尾的情况 if insert_row < 0 or insert_row > self.rowCount(QtCore.QModelIndex()): insert_row = self.rowCount(QtCore.QModelIndex()) # 避免把行插入到原位置 if original_row == insert_row: return True # 插入新行并填充数据 self.insertRows(insert_row, 1) for col_idx, value in enumerate(row_data): idx = self.index(insert_row, col_idx) self.setData(idx, value, QtCore.Qt.EditRole) # 如果是移动动作,删除原行(注意:如果原行在插入行之后,插入后原行索引会+1,要调整) if action == QtCore.Qt.MoveAction: if original_row > insert_row: original_row += 1 self.removeRows(original_row, 1) return True
完整修改后的代码
下面是整合了所有改动的可运行代码:
from PyQt5 import QtCore, QtGui, QtWidgets import sys class Mainwindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.table = QtWidgets.QTableView() self.setCentralWidget(self.table) headers = [None, 'Matreial', 'γ, kg/m3', 'λa', 'λb'] data = [ [1, 'Blocks γ=500 GOST 31359-2007', 500, 0.18, 0.22], [2, 'Blocks γ=600 GOST 31359-2008', 600, 0.25, 0.27], [3, 'Insulation', '80-125', 0.041, 0.042] ] model = Materials(data, headers) self.table.setModel(model) self.table.setSelectionBehavior(self.table.SelectRows) self.table.setSelectionMode(self.table.SingleSelection) self.table.setDragDropMode(self.table.InternalMove) self.table.setDropIndicatorShown(True) self.table.setDragEnabled(True) self.table.setAcceptDrops(True) class Materials(QtCore.QAbstractTableModel): def __init__(self, materials = [[]], headers = [], parent = None): super(Materials, self).__init__() self.materials = materials self.headers = headers def rowCount(self, parent): return len(self.materials) def columnCount(self, parent): return len(self.headers) def data(self, index, role): if role == QtCore.Qt.DisplayRole: row = index.row() column = index.column() value = self.materials[row][column] return value def setData(self, index, value, role = QtCore.Qt.EditRole): if role == QtCore.Qt.EditRole: row = index.row() column = index.column() text = str(value) if type(text)==str: self.materials[row][column] = text self.dataChanged.emit(index, index) return True return False def headerData(self, section, orientation, role): if role == QtCore.Qt.DisplayRole: if orientation == QtCore.Qt.Horizontal: return self.headers[section] def flags(self, index): return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled def insertRows(self, position, rows, parent = QtCore.QModelIndex()): self.beginInsertRows(parent, position, position + rows - 1) for i in range(rows): defaultValues = ['' for i in range(self.columnCount(None))] self.materials.insert(position, defaultValues) self.endInsertRows() return True def removeRows(self, position, rows, parent = QtCore.QModelIndex()): self.beginRemoveRows(parent, position, position + rows - 1) for i in range(rows): value = self.materials[position] self.materials.remove(value) self.endRemoveRows() return True def supportedDropActions(self): return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction def supportedDragActions(self): return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction def mimeTypes(self): return ['application/vnd.tableview.rowdata'] def mimeData(self, indexes): mime_data = QtCore.QMimeData() if not indexes: return mime_data original_row = indexes[0].row() row_data = self.materials[original_row] byte_array = QtCore.QByteArray() stream = QtCore.QDataStream(byte_array, QtCore.QIODevice.WriteOnly) stream.writeInt32(len(row_data)) for item in row_data: stream.writeQVariant(item) stream.writeInt32(original_row) mime_data.setData('application/vnd.tableview.rowdata', byte_array) return mime_data def dropMimeData(self, data, action, row, col, parent): if action == QtCore.Qt.IgnoreAction: return True if not data.hasFormat('application/vnd.tableview.rowdata'): return False byte_array = data.data('application/vnd.tableview.rowdata') stream = QtCore.QDataStream(byte_array, QtCore.QIODevice.ReadOnly) col_count = stream.readInt32() row_data = [] for _ in range(col_count): row_data.append(stream.readQVariant()) original_row = stream.readInt32() insert_row = row if row != -1 else parent.row() if insert_row < 0 or insert_row > self.rowCount(QtCore.QModelIndex()): insert_row = self.rowCount(QtCore.QModelIndex()) if original_row == insert_row: return True self.insertRows(insert_row, 1) for col_idx, value in enumerate(row_data): idx = self.index(insert_row, col_idx) self.setData(idx, value, QtCore.Qt.EditRole) if action == QtCore.Qt.MoveAction: if original_row > insert_row: original_row += 1 self.removeRows(original_row, 1) return True if __name__ == '__main__': app = QtWidgets.QApplication([]) application = Mainwindow() application.show() sys.exit(app.exec())
额外说明
- 我在
mimeData里加入了原行索引的传递,这样就不用依赖表格的选中状态来定位要删除的行,避免拖拽过程中选中状态意外变化的问题。 dropMimeData里处理了拖拽到表格末尾的边界情况,确保行能正确插入。- 如果你需要支持多行拖拽,只需要修改
mimeData来批量打包多行数据,再在dropMimeData里批量插入和删除就行。
内容的提问来源于stack exchange,提问作者Nasty_gri
相关产品推荐
相关产品推荐

