继这里的上一个问题之后,我希望通过向我的模型添加removeRows函数,在PyQt5中向我的qtableview表添加键按功能上的删除行。然而,由于添加了这个函数,它破坏了我的拖放功能,因此,当拖放在qtableview表的其他地方时,被拖放的行消失了。是否有办法防止拖动行消失?
注:有趣的是,当选择垂直标题"列"时,拖放功能工作,但我渴望找到拖放行选择的解决方案。
下面是我的代码,在模型中添加了removeRows函数,在视图中也添加了keyPressEventfrom PyQt5.QtGui import QBrush
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex
class myModel(QAbstractTableModel):
def __init__(self, data, parent=None, *args):
super().__init__(parent, *args)
self._data = data or []
self._headers = ['Type', 'result', 'count']
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._headers)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
if section < 0 or section >= len(self._headers):
return ""
else:
return self._headers[section]
else:
return ''
return None
def removeRows(self, position, rows, QModelIndex):
self.beginRemoveRows(QModelIndex, position, position + rows - 1)
for i in range(rows):
del (self._data[position])
self.endRemoveRows()
self.layoutChanged.emit()
return True
def data(self, index, role=None):
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter
if role == Qt.ForegroundRole:
return QBrush(Qt.black)
if role == Qt.BackgroundRole:
if (self.index(index.row(), 0).data().startswith('second')):
return QBrush(Qt.green)
else:
if (self.index(index.row(), 1).data()) == 'abc':
return QBrush(Qt.yellow)
if (self.index(index.row(), 1).data()) == 'def':
return QBrush(Qt.blue)
if (self.index(index.row(), 1).data()) == 'ghi':
return QBrush(Qt.magenta)
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
return Qt.ItemIsDropEnabled | Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
def supportedDropActions(self) -> bool:
return Qt.MoveAction | Qt.CopyAction
class myTableView(QTableView):
def __init__(self, parent):
super().__init__(parent)
header = self.verticalHeader()
header.setSectionsMovable(True)
header.setSectionResizeMode(QHeaderView.Fixed)
header.setFixedWidth(10)
QShortcut('F7', self, self.getLogicalRows)
QShortcut('F6', self, self.toggleVerticalHeader)
QShortcut('Alt+Up', self, lambda: self.moveRow(True))
QShortcut('Alt+Down', self, lambda: self.moveRow(False))
self.setSelectionBehavior(self.SelectRows)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(self.InternalMove)
self.setDragDropOverwriteMode(False)
def dropEvent(self, event):
if (event.source() is not self or
(event.dropAction() != Qt.MoveAction and
self.dragDropMode() != QAbstractItemView.InternalMove)):
super().dropEvent(event)
selection = self.selectedIndexes()
from_index = selection[0].row() if selection else -1
to_index = self.indexAt(event.pos()).row()
if (0 <= from_index < self.model().rowCount() and
0 <= to_index < self.model().rowCount() and
from_index != to_index):
header = self.verticalHeader()
from_index = header.visualIndex(from_index)
to_index = header.visualIndex(to_index)
header.moveSection(from_index, to_index)
event.accept()
super().dropEvent(event)
def toggleVerticalHeader(self):
self.verticalHeader().setHidden(self.verticalHeader().isVisible())
def moveRow(self, up=True):
selection = self.selectedIndexes()
if selection:
header = self.verticalHeader()
row = header.visualIndex(selection[0].row())
if up and row > 0:
header.moveSection(row, row - 1)
elif not up and row < header.count() - 1:
header.moveSection(row, row + 1)
def getLogicalRows(self):
header = self.verticalHeader()
for vrow in range(header.count()):
lrow = header.logicalIndex(vrow)
index = self.model().index(lrow, 0)
print(index.data())
def keyPressEvent(self, event):
if event.key() == Qt.Key_Delete:
index = self.currentIndex()
try:
self.model().removeRows(index.row(), 1, index)
except IndexError:
pass
else:
super().keyPressEvent(event)
class sample_data(QMainWindow):
def __init__(self):
super().__init__()
tv = myTableView(self)
tv.setModel(myModel([
["first", 'abc', 123],
["second"],
["third", 'def', 456],
["fourth", 'ghi', 789],
]))
self.setCentralWidget(tv)
tv.setSpan(1, 0, 1, 3)
if __name__ == '__main__':
app = QApplication(['Test'])
test = sample_data()
test.setGeometry(600, 100, 350, 185)
test.show()
app.exec_()
主要问题;默认情况下,QAbstractItemModel的removeRows
不做任何事情(并返回False
)。
技术问题更微妙。
项目视图中的拖动操作总是以startDrag()
开始,它创建一个QDrag对象并调用其exec()
。当用户删除数据时,当接受的删除操作是MoveAction
时,该实现还调用私有clearOrRemove
函数,最终覆盖数据或删除行。
您已经使用了setDragDropOverwriteMode(False)
,所以它将调用removeRows
。您之前的代码以前可以工作,因为,如前所述,默认实现不做任何事情,但现在您已经重新实现了它,并且在这种情况下它实际上删除了行。
解决方案是在掉落事件是移动时改变掉落动作(这有点不直观,但由于操作已经执行,这应该不是问题)。使用IgnoreAction
将避免不必要的行为,因为在这种情况下,clearOrRemove
将不再被调用:
def dropEvent(self, event):
# ...
if (0 <= from_index < self.model().rowCount() and
0 <= to_index < self.model().rowCount() and
from_index != to_index):
# ...
header.moveSection(from_index, to_index)
event.accept()
event.setDropAction(Qt.IgnoreAction)
super().dropEvent(event)
当项目没有填满整个视口时,可能会在项目范围之外发生删除事件,导致indexAt()
的QModelIndex无效,不仅在删除最后一行时,而且在删除最后一列时也是如此。
由于您只对垂直移动感兴趣,因此解决方案是从垂直标题中获取to_index
,并最终将其设置为最后一行,无论它仍然无效(-1):
def dropEvent(self, event):
if (event.source() is not self or
(event.dropAction() != Qt.MoveAction and
self.dragDropMode() != QAbstractItemView.InternalMove)):
super().dropEvent(event)
selection = self.selectedIndexes()
from_index = selection[0].row() if selection else -1
globalPos = self.viewport().mapToGlobal(event.pos())
header = self.verticalHeader()
to_index = header.logicalIndexAt(header.mapFromGlobal(globalPos).y())
if to_index < 0:
to_index = header.logicalIndex(self.model().rowCount() - 1)
if from_index != to_index:
from_index = header.visualIndex(from_index)
to_index = header.visualIndex(to_index)
header.moveSection(from_index, to_index)
event.accept()
event.setDropAction(Qt.IgnoreAction)
super().dropEvent(event)