【发布时间】:2021-11-01 15:37:51
【问题描述】:
我在下面有一个有效的拖放示例,用于使用 PyQt5 为 qtableview 重新排序具有相同列长度的行(在此 StackOverflow 问题here 的帮助下)。但是,我希望在 qtableview 表上执行相同的操作,其中一或两行合并了跨越总列数的单元格(如下图中的第二行)。
最好的方法是什么?我应该在拖放点删除合并(clearSpans),然后根据单元格值进行重新合并(尽管当我尝试这样做时它不起作用),还是有办法使用单元格拖放重新排序原封不动地合并?
这是适用于相等列的行数据的代码,但在合并行时会失败
from 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]
return None
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
def relocateRow(self, row_source, row_target) -> None:
row_a, row_b = max(row_source, row_target), min(row_source, row_target)
self.beginMoveRows(QModelIndex(), row_a, row_a, QModelIndex(), row_b)
self._data.insert(row_target, self._data.pop(row_source))
self.endMoveRows()
class myTableView(QTableView):
def __init__(self, parent):
super().__init__(parent)
self.verticalHeader().hide()
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()
#self.clearSpans()
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):
self.model().relocateRow(from_index, to_index)
event.accept()
super().dropEvent(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)
self.show()
if __name__ == '__main__':
app = QApplication([])
test = sample_data()
raise SystemExit(app.exec_())
【问题讨论】:
-
垂直表头can be made movable的部分,所以不需要自己实现这个功能。这显然意味着垂直标题将可见,但这可以通过将部分设为空白来缓解,这将导致标题相对较窄。您还可以添加键盘快捷键来切换垂直标题的可见性。
-
@ekhumoro 这只是将 self.verticalHeader().hide() 替换为 self.verticalHeader().setSectionsMovable(True) 的情况吗?虽然我试过了,但垂直标题仍然隐藏。
-
不,还有更多。如果您有兴趣,我可以提供一个工作示例的答案。我可能应该提到的一件事是移动部分纯粹是视觉 - 底层模型永远不会被修改。不过,这在实践中并不重要,因为标题提供了从逻辑索引转换为视觉索引的方法。而且它确实带来了一些额外的好处——例如,恢复原始状态非常容易。
-
我对一个工作示例非常感兴趣,非常感谢。我只是对模型/视图设置有所了解。
-
好的 - 我已将我的 cmets 转换为答案并包含一个工作演示。
标签: python pyqt drag-and-drop pyqt5 qtableview