Drag and Drop QLabel to QHeaderView
-
Hi,
I am currently trying to create a gui to allow a user to assign predetermined headers to a specific column in some data presented in a QTableView. Something like this:
The headers available to the user will be available in a QListWidget and the data in the QTableView is coming from a modified version of QAbstractTableModel which allows a direct import of a pandas dataframe:
import pandas as pd from PyQt5.QtWidgets import QApplication, QTableView from PyQt5.QtCore import QAbstractTableModel, Qt class pandasModel(QAbstractTableModel): def __init__(self, data): QAbstractTableModel.__init__(self) self._data = data def rowCount(self, parent=None): return self._data.shape[0] def columnCount(self, parent=None): return self._data.shape[1] def data(self, index, role=Qt.DisplayRole): if index.isValid(): if role == Qt.DisplayRole: return str(self._data.iloc[index.row(), index.column()]) return None def headerData(self, col, orientation, role): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return self._data.columns[col] return None
I would like to do this using the Drag and Drop functionality where the user can drag a containing the header name onto a specific column in the QTableView and then have the column header take the text of the QListWidgetItem.
This is the closest I have found (https://stackoverflow.com/questions/47291754/drag-and-drop-columns-between-qheaderview-and-qlistwidget) and this simply hides the column but I can't get the header text to change to the QListWidgetItem which is beign dragged:
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_())
Any help on how to change the column header text is appreciated on this thank you.
-
Hi,
Why not call the header model's setHeaderData method when dropping ?
-
Hi so I tried that and reimplemented the setHeaderData method and explictly emited the headerChangedSignal as below but this will always return false even if I try to do it explcitly by calling it in the code.
def setHeaderData(self, section, orientation, value, role=Qt.EditRole): self.headerDataChanged.emit(orientation,section,section+1) return super().setHeaderData(section, orientation, value, role)
-
Since you have also a custom headerData method, I would expect that you would store the new value yourself in setHeaderData.