Перетащите столбцы между QHeaderView и QListWidget
У меня проблемы с использованием QHeaderView
функция перетаскивания. Когда я подкласс QHeaderView
Я могу принять капли без проблем. Однако, когда я нажимаю на QHeaderView
и попробуйте перетащить из одного из столбцов, кажется, ничего не происходит.
Ниже я повторно реализовал несколько событий перетаскивания, чтобы просто напечатать, если они были вызваны. Однако только dragEnterEvent
успешно. Нет других событий, таких как startDrag
когда-либо называется. Моя конечная цель - иметь QTableView
где я могу перетащить столбцы из и в QListWidget
(по существу, скрывая столбец), и пользователь может затем перетащить QListWidget
предмет обратно на QTableView
если они хотят, чтобы столбец и его данные снова были видны. Однако я не могу двигаться вперед, пока не пойму, почему QHeaderView
не позволяет мне тащить Любая помощь будет принята с благодарностью.
class MyHeader(QHeaderView):
def __init__(self, parent=None):
super().__init__(Qt.Horizontal, parent)
self.setDragEnabled(True)
self.setAcceptDrops(True)
def startDrag(self, *args, **kwargs):
print('start drag success')
def dragEnterEvent(self, event):
print('drag enter success')
def dragLeaveEvent(self, event):
print('drag leave success')
def dragMoveEvent(self, event):
print('drag move success')
class Form(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
listWidget = QListWidget()
listWidget.setDragEnabled(True)
listWidget.setAcceptDrops(True)
listWidget.addItem('item #1')
listWidget.addItem('item #2')
tableWidget = QTableWidget()
header = MyHeader()
tableWidget.setHorizontalHeader(header)
tableWidget.setRowCount(5)
tableWidget.setColumnCount(2)
tableWidget.setHorizontalHeaderLabels(["Column 1", "Column 2"])
splitter = QSplitter(Qt.Horizontal)
splitter.addWidget(listWidget)
splitter.addWidget(tableWidget)
layout = QHBoxLayout()
layout.addWidget(splitter)
self.setLayout(layout)
if __name__=='__main__':
import sys
app = QApplication(sys.argv)
form= Form()
form.show()
sys.exit(app.exec_())
1 ответ
QHeaderView
класс не использует методы перетаскивания, унаследованные от QAbstractItemView
потому что ему никогда не нужно инициировать операцию перетаскивания. Перетаскивание используется только для перестановки столбцов, и нет необходимости использовать QDrag
механизм для этого.
Учитывая это, будет необходимо реализовать пользовательскую обработку перетаскиванием (используя mousePressEvent
, mouseMoveEvent
а также dropEvent
), а также предоставляют функции для кодирования и декодирования формата mime-данных, который Qt использует для передачи элементов между представлениями. Фильтр событий понадобится для таблицы-виджета, так что отбрасывание все еще возможно, когда все столбцы скрыты; а также для списка-виджета, чтобы остановить его копирование элементов в себя.
Демонстрационный скрипт ниже реализует все это. Вероятно, есть еще некоторые уточнения, но этого должно быть достаточно, чтобы вы начали:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MyHeader(QHeaderView):
MimeType = 'application/x-qabstractitemmodeldatalist'
columnsChanged = pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(Qt.Horizontal, parent)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self._dragstartpos = None
def encodeMimeData(self, items):
data = QByteArray()
stream = QDataStream(data, QIODevice.WriteOnly)
for column, label in items:
stream.writeInt32(0)
stream.writeInt32(column)
stream.writeInt32(2)
stream.writeInt32(int(Qt.DisplayRole))
stream.writeQVariant(label)
stream.writeInt32(int(Qt.UserRole))
stream.writeQVariant(column)
mimedata = QMimeData()
mimedata.setData(MyHeader.MimeType, data)
return mimedata
def decodeMimeData(self, mimedata):
data = []
stream = QDataStream(mimedata.data(MyHeader.MimeType))
while not stream.atEnd():
row = stream.readInt32()
column = stream.readInt32()
item = {}
for count in range(stream.readInt32()):
key = stream.readInt32()
item[key] = stream.readQVariant()
data.append([item[Qt.UserRole], item[Qt.DisplayRole]])
return data
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self._dragstartpos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if (event.buttons() & Qt.LeftButton and
self._dragstartpos is not None and
(event.pos() - self._dragstartpos).manhattanLength() >=
QApplication.startDragDistance()):
column = self.logicalIndexAt(self._dragstartpos)
data = [column, self.model().headerData(column, Qt.Horizontal)]
self._dragstartpos = None
drag = QDrag(self)
drag.setMimeData(self.encodeMimeData([data]))
action = drag.exec(Qt.MoveAction)
if action != Qt.IgnoreAction:
self.setColumnHidden(column, True)
def dropEvent(self, event):
mimedata = event.mimeData()
if mimedata.hasFormat(MyHeader.MimeType):
if event.source() is not self:
for column, label in self.decodeMimeData(mimedata):
self.setColumnHidden(column, False)
event.setDropAction(Qt.MoveAction)
event.accept()
else:
event.ignore()
else:
super().dropEvent(event)
def setColumnHidden(self, column, hide=True):
count = self.count()
if 0 <= column < count and hide != self.isSectionHidden(column):
if hide:
self.hideSection(column)
else:
self.showSection(column)
self.columnsChanged.emit(count - self.hiddenSectionCount())
class Form(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.listWidget = QListWidget()
self.listWidget.setAcceptDrops(True)
self.listWidget.setDragEnabled(True)
self.listWidget.viewport().installEventFilter(self)
self.tableWidget = QTableWidget()
header = MyHeader(self)
self.tableWidget.setHorizontalHeader(header)
self.tableWidget.setRowCount(5)
self.tableWidget.setColumnCount(4)
labels = ["Column 1", "Column 2", "Column 3", "Column 4"]
self.tableWidget.setHorizontalHeaderLabels(labels)
for column, label in enumerate(labels):
if column > 1:
item = QListWidgetItem(label)
item.setData(Qt.UserRole, column)
self.listWidget.addItem(item)
header.hideSection(column)
header.columnsChanged.connect(
lambda count: self.tableWidget.setAcceptDrops(not count))
self.tableWidget.viewport().installEventFilter(self)
splitter = QSplitter(Qt.Horizontal)
splitter.addWidget(self.listWidget)
splitter.addWidget(self.tableWidget)
layout = QHBoxLayout()
layout.addWidget(splitter)
self.setLayout(layout)
def eventFilter(self, source, event):
if event.type() == QEvent.Drop:
if source is self.tableWidget.viewport():
self.tableWidget.horizontalHeader().dropEvent(event)
return True
else:
event.setDropAction(Qt.MoveAction)
return super().eventFilter(source, event)
if __name__=='__main__':
app = QApplication(sys.argv)
form = Form()
form.setGeometry(600, 50, 600, 200)
form.show()
sys.exit(app.exec_())