Я (просто) хочу использовать механизм перетаскивания QTableView
s для перемещения существующих строк. Я нашел много источников (например, здесь , здесь или здесь ), которые описывают некоторые аспекты перетаскивания, вставки, вставки и т. Д. c. но я все еще изо всех сил пытаюсь заставить его работать для моего случая.
Вот то, на что должно быть способно решение, которое я ищу:
- работа над 'Qt- свободная структура данных, например, список кортежей.
- оперирует структурой данных. то есть, когда порядок элементов изменяется в представлении, он должен быть изменен в структуре данных
- внешний вид стандартных списков с возможностью перетаскивания:
- выбор / перемещение целых строк
- показать индикатор сброса для всей строки
- Дальнейшие операции, такие как удаление / редактирование ячеек, все еще должны быть возможны, т.е. не должны затрагиваться методом перетаскивания
В этом уроке показано решение, которое на очень близко к тому, что мне нужно, но использует QStandardItemModel
, а не QAbstractTableModel
, которое выглядит для меня полуоптимальным, потому что мне приходится работать над 'зеркальная' структура данных, основанная на QStandardItem
, которая необходима для QStandardItemModel
(я прав?)
Код, представляющий мой текущий прогресс, добавлен ниже.
В настоящее время я вижу два возможных подхода:
Подход 1 : Реализация против QAbstractTableModel
и реализация всех необходимых событий / слотов для изменения базовой структуры данных: * pro: наиболее обобщенный c подход * pro: нет ре dundant data * con: я не знаю, как получить информацию о завершенной операции перетаскивания и какой индекс был перемещен куда
В коде, который я добавил, я отслеживаю все связанные с ним методы и распечатываю все аргументы. Вот что я получаю, когда перетаскиваю строку 2 в строку 3
dropMimeData(data: ['application/x-qabstractitemmodeldatalist'], action: 2, row: -1, col: -1, parent: '(row: 2, column: 0, valid: True)')
insertRows(row=-1, count=1, parent=(row: 2, column: 0, valid: True))
setData(index=(row: 0, column: 0, valid: True), value='^line1', role=0)
setData(index=(row: 0, column: 1, valid: True), value=1, role=0)
removeRows(row=1, count=1, parent=(row: -1, column: -1, valid: False))
Этот вывод вызывает у меня следующие вопросы:
- почему
moveRow
/ moveRows
нет позвонить? когда они будут вызваны? - почему
insertRow
/ removeRow
не вызывается, а только insertRows
/ removeRows
? - что означает индекс строки
-1
? - что я могу сделать с данными MIME, предоставленными в
dropMimeData
? Стоит ли использовать его для последующего копирования данных?
Подход 2 : используйте QStandardItemModel
и изменяйте данные параллельно данным, управляемым QStandardItemModel
. * за: есть рабочий пример * против: вы управляете избыточной структурой данных, которая должна соответствовать другой структуре данных с внутренним управлением. * наоборот: не выяснил, как это сделать точно также
Вот мой текущий подход с использованием QAbstractTableModel
:
from PyQt5 import QtWidgets, QtCore, QtGui
class MyModel(QtCore.QAbstractTableModel):
def __init__(self, data, parent=None, *args):
super().__init__(parent, *args)
self._data = data
def columnCount(self, parent):
return 2
def rowCount(self, parent):
return len(self._data)
def headerData(self, column: int, orientation, role: QtCore.Qt.ItemDataRole):
return (('Regex', 'Category')[column]
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal
else None)
def data(self, index, role: QtCore.Qt.ItemDataRole):
if role not in {QtCore.Qt.DisplayRole, QtCore.Qt.EditRole}:
return None
print("data(index=%s, role=%r)" % (self._index2str(index), self._role2str(role)))
return (self._data[index.row()][index.column()]
if index.isValid()
and role in {QtCore.Qt.DisplayRole, QtCore.Qt.EditRole}
and index.row() < len(self._data)
else None)
def setData(self, index: QtCore.QModelIndex, value, role: QtCore.Qt.ItemDataRole):
print("setData(index=%s, value=%r, role=%r)" % (self._index2str(index), value, role))
return super().setData(index, value, role)
def flags(self, index):
return (
super().flags(index)
| QtCore.Qt.ItemIsDropEnabled
| (QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsDragEnabled)
if index.isValid() else QtCore.Qt.NoItemFlags)
def dropMimeData(self, data, action, row, col, parent: QtCore.QModelIndex):
"""Always move the entire row, and don't allow column 'shifting'"""
print("dropMimeData(data: %r, action: %r, row: %r, col: %r, parent: %r)" % (
data.formats(), action, row, col, self._index2str(parent)))
assert action == QtCore.Qt.MoveAction
return super().dropMimeData(data, action, row, 0, parent)
def supportedDragActions(self):
return QtCore.Qt.MoveAction
def supportedDropActions(self):
return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction
def removeRow(self, row: int, parent=None):
print("removeRow(row=%r):" % (row))
return super().removeRow(row, parent)
def removeRows(self, row: int, count: int, parent=None):
print("removeRows(row=%r, count=%r, parent=%s)" % (row, count, self._index2str(parent)))
return super().removeRows(row, count, parent)
def insertRow(self, index, parent=None):
print("insertRow(row=%r, count=%r):" % (row, count))
return super().insertRow(row, count, parent)
def insertRows(self, row: int, count: int, parent: QtCore.QModelIndex = None):
print("insertRows(row=%r, count=%r, parent=%s)" % (row, count, self._index2str(parent)))
return super().insertRows(row, count, parent)
@staticmethod
def _index2str(index):
return "(row: %d, column: %d, valid: %r)" % (index.row(), index.column(), index.isValid())
@staticmethod
def _role2str(role: QtCore.Qt.ItemDataRole) -> str:
return "%s (%d)" % ({
QtCore.Qt.DisplayRole: "DisplayRole",
QtCore.Qt.DecorationRole: "DecorationRole",
QtCore.Qt.EditRole: "EditRole",
QtCore.Qt.ToolTipRole: "ToolTipRole",
QtCore.Qt.StatusTipRole: "StatusTipRole",
QtCore.Qt.WhatsThisRole: "WhatsThisRole",
QtCore.Qt.SizeHintRole: "SizeHintRole",
QtCore.Qt.FontRole: "FontRole",
QtCore.Qt.TextAlignmentRole: "TextAlignmentRole",
QtCore.Qt.BackgroundRole: "BackgroundRole",
#QtCore.Qt.BackgroundColorRole:
QtCore.Qt.ForegroundRole: "ForegroundRole",
#QtCore.Qt.TextColorRole
QtCore.Qt.CheckStateRole: "CheckStateRole",
QtCore.Qt.InitialSortOrderRole: "InitialSortOrderRole",
}[role], role)
class MyTableView(QtWidgets.QTableView):
class DropmarkerStyle(QtWidgets.QProxyStyle):
def drawPrimitive(self, element, option, painter, widget=None):
"""Draw a line across the entire row rather than just the column we're hovering over.
This may not always work depending on global style - for instance I think it won't
work on OSX."""
if element == self.PE_IndicatorItemViewItemDrop and not option.rect.isNull():
option_new = QtWidgets.QStyleOption(option)
option_new.rect.setLeft(0)
if widget:
option_new.rect.setRight(widget.width())
option = option_new
super().drawPrimitive(element, option, painter, widget)
def __init__(self):
super().__init__()
self.setStyle(self.DropmarkerStyle())
# only allow rows to be selected
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
# disallow multiple rows to be selected
self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.setDragEnabled(True)
self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
self.setDropIndicatorShown(True) # default
self.setAcceptDrops(False) # ?
self.viewport().setAcceptDrops(True) # ?
self.setDragDropOverwriteMode(False)
class HelloWindow(QtWidgets.QMainWindow):
def __init__(self) -> None:
super().__init__()
model = MyModel([("^line0", 0),
("^line1", 1),
("^line2", 2),
("^line3", 3)])
table_view = MyTableView()
table_view.setModel(model)
table_view.verticalHeader().hide()
table_view.setShowGrid(False)
self.setCentralWidget(table_view)
def main():
app = QtWidgets.QApplication([])
window = HelloWindow()
window.show()
app.exec_()
if __name__ == "__main__":
main()