Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Drag and Drop QLabel to QHeaderView
QtWS25 Last Chance

Drag and Drop QLabel to QHeaderView

Scheduled Pinned Locked Moved Unsolved Qt for Python
4 Posts 2 Posters 373 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • T Offline
    T Offline
    TheBlackStig
    wrote on last edited by
    #1

    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:

    ffc9a9ef-80af-4778-bdeb-fd3aae26f565-image.png

    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.

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      Why not call the header model's setHeaderData method when dropping ?

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      1 Reply Last reply
      0
      • T Offline
        T Offline
        TheBlackStig
        wrote on last edited by
        #3

        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)
        
        1 Reply Last reply
        0
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          Since you have also a custom headerData method, I would expect that you would store the new value yourself in setHeaderData.

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          1 Reply Last reply
          0

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved