这似乎是非常奇怪的默认行为。无论如何,按照你提供的错误报告中的代码,我已经成功地将一些东西移植到了 PyQt。它可能不像那个代码那样健壮,但至少在你提供的简单测试用例中似乎能够工作!
下面实现可能存在的问题:
当前选定的行不遵循拖放(因此如果您移动第三行,则第三行在移动后仍被选定)。这可能不太难解决!
它可能不适用于具有子行的行。我甚至不确定一个QTableWidgetItem
是否可以有子项,所以也许没问题。
我没有测试选择多行,但我认为它应该可以工作
出于某种原因,尽管在表格中插入了一行,我并没有删除正在移动的行。对我来说,这似乎非常奇怪。它几乎看起来像在除末尾之外的任何地方插入行不会增加表格的rowCount()
。
我对GetSelectedRowsFast
的实现与他们有点不同。它可能不快,并且可能存在一些错误(我不检查项目是否可用或可选择),就像他们做的那样。我认为这也很容易解决,但只有在禁用选定行时,某人执行拖放操作时才会出现问题。在这种情况下,我认为更好的解决方案可能是取消选择已禁用的行,但这取决于您对其的使用方式!
如果您将此代码用于生产环境,则可能需要仔细检查并确保所有内容都很合理。我的 PyQt 移植很可能存在问题,原始的基于 c++ 的算法也可能存在问题。但是,它可以作为使用QTableWidget
实现所需功能的证明。
更新:注意,下面有一个PyQt5的额外答案,它也解决了我上面提到的一些问题。您可能想要查看一下!
代码:
import sys, os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class TableWidgetDragRows(QTableWidget):
def __init__(self, *args, **kwargs):
QTableWidget.__init__(self, *args, **kwargs)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.viewport().setAcceptDrops(True)
self.setDragDropOverwriteMode(False)
self.setDropIndicatorShown(True)
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setDragDropMode(QAbstractItemView.InternalMove)
def dropEvent(self, event):
if event.source() == self and (event.dropAction() == Qt.MoveAction or self.dragDropMode() == QAbstractItemView.InternalMove):
success, row, col, topIndex = self.dropOn(event)
if success:
selRows = self.getSelectedRowsFast()
top = selRows[0]
dropRow = row
if dropRow == -1:
dropRow = self.rowCount()
offset = dropRow - top
for i, row in enumerate(selRows):
r = row + offset
if r > self.rowCount() or r < 0:
r = 0
self.insertRow(r)
selRows = self.getSelectedRowsFast()
top = selRows[0]
offset = dropRow - top
for i, row in enumerate(selRows):
r = row + offset
if r > self.rowCount() or r < 0:
r = 0
for j in range(self.columnCount()):
source = QTableWidgetItem(self.item(row, j))
self.setItem(r, j, source)
event.accept()
else:
QTableView.dropEvent(event)
def getSelectedRowsFast(self):
selRows = []
for item in self.selectedItems():
if item.row() not in selRows:
selRows.append(item.row())
return selRows
def droppingOnItself(self, event, index):
dropAction = event.dropAction()
if self.dragDropMode() == QAbstractItemView.InternalMove:
dropAction = Qt.MoveAction
if event.source() == self and event.possibleActions() & Qt.MoveAction and dropAction == Qt.MoveAction:
selectedIndexes = self.selectedIndexes()
child = index
while child.isValid() and child != self.rootIndex():
if child in selectedIndexes:
return True
child = child.parent()
return False
def dropOn(self, event):
if event.isAccepted():
return False, None, None, None
index = QModelIndex()
row = -1
col = -1
if self.viewport().rect().contains(event.pos()):
index = self.indexAt(event.pos())
if not index.isValid() or not self.visualRect(index).contains(event.pos()):
index = self.rootIndex()
if self.model().supportedDropActions() & event.dropAction():
if index != self.rootIndex():
dropIndicatorPosition = self.position(event.pos(), self.visualRect(index), index)
if dropIndicatorPosition == QAbstractItemView.AboveItem:
row = index.row()
col = index.column()
elif dropIndicatorPosition == QAbstractItemView.BelowItem:
row = index.row() + 1
col = index.column()
else:
row = index.row()
col = index.column()
if not self.droppingOnItself(event, index):
return True, row, col, index
return False, None, None, None
def position(self, pos, rect, index):
r = QAbstractItemView.OnViewport
margin = 2
if pos.y() - rect.top() < margin:
r = QAbstractItemView.AboveItem
elif rect.bottom() - pos.y() < margin:
r = QAbstractItemView.BelowItem
elif rect.contains(pos, True):
r = QAbstractItemView.OnItem
if r == QAbstractItemView.OnItem and not (self.model().flags(index) & Qt.ItemIsDropEnabled):
r = QAbstractItemView.AboveItem if pos.y() < rect.center().y() else QAbstractItemView.BelowItem
return r
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QHBoxLayout()
self.setLayout(layout)
self.table_widget = TableWidgetDragRows()
layout.addWidget(self.table_widget)
self.table_widget.setColumnCount(2)
self.table_widget.setHorizontalHeaderLabels(['Colour', 'Model'])
items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle')]
for i, (colour, model) in enumerate(items):
c = QTableWidgetItem(colour)
m = QTableWidgetItem(model)
self.table_widget.insertRow(self.table_widget.rowCount())
self.table_widget.setItem(i, 0, c)
self.table_widget.setItem(i, 1, m)
self.show()
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())