The QHeaderView class does not use the drag and drop methods inherited from QAbstractItemView, because it never needs to initiate a drag operation. Drag and drop is only used for rearranging columns, and it is not necessary to use the QDrag mechanism for that.
Given this, it will be necessary to implement custom drag and drop handling (using mousePressEvent, mouseMoveEvent and dropEvent), and also provide functions for encoding and decoding the mime-data format that Qt uses to pass items between views. An event-filter will be needed for the table-widget, so that dropping is still possible when all columns are hidden; and also for the list-widget, to stop it copying items to itself.
The demo script below implements all of that. There are probably some more refinements needed, but it should be enough to get you started:
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_())