qtqtableviewqtablewidgetpyside6qheaderview

Scroll with headers - QTableView / QTableWidget


Vertical and horizontal headers are frozen by default when using the scrollbars if the table is not visible. I would like the scroll to apply to the headers as well, so when scrolling, they are not visible at all time.

Here is the example:

import string
import random
from typing import Optional

from PySide6.QtCore import Qt
from PySide6.QtWidgets import QTableWidget, QWidget, QAbstractItemView, QHeaderView, QTableWidgetItem, QHBoxLayout, \
    QApplication


class DataTable(QTableWidget):

    # region Constructor

    def __init__(self, parent: Optional[QWidget] = None):
        super(DataTable, self).__init__(parent)

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.setSelectionBehavior(QAbstractItemView.SelectItems)
        self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)

        self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.horizontalHeader().setTextElideMode(Qt.ElideMiddle)

        for j in range(20):
            if self.columnCount() <= j:
                self.insertColumn(j)
            for i in range(20):
                if self.rowCount() <= i:
                    self.insertRow(i)
                self.setItem(i, j, QTableWidgetItem('Hello'))

        word = ''.join(random.choice(string.ascii_letters) for _ in range(6))
        self.setHorizontalHeaderLabels([str(word) for i in range(self.columnCount())])
        self.setVerticalHeaderLabels([str(word) for i in range(self.rowCount())])


if __name__ == '__main__':
    app = QApplication()

    widget = QWidget()
    widget.resize(400, 600)
    layout = QHBoxLayout(widget)
    layout.addWidget(DataTable())
    widget.show()

    app.exec()

This is how it looks (see scrollbar is at the right): enter image description here

How i'd like to look (basically the horizontal and vertical headers should move with the scroll): enter image description here


Solution

  • To anyone interested, I the code below works. So I basically wrapped the QTableWidget in a QScrollArea, disabled the scrollbars from the QTableWidget and updated the minimum size.

    import string
    import random
    from typing import Optional
    
    from PySide6.QtCore import Qt, QSize
    from PySide6.QtWidgets import QTableWidget, QWidget, QAbstractItemView, QHeaderView, QTableWidgetItem, QHBoxLayout, \
        QApplication, QScrollArea
    
    
    class DataTable(QScrollArea):
    
        # region Constructor
    
        def __init__(self, parent: Optional[QWidget] = None):
            super(DataTable, self).__init__(parent)
            self.setWidget(DataTableWidget(self))
            self.setWidgetResizable(True)
    
    
    class DataTableWidget(QTableWidget):
    
        def __init__(self, parent: Optional[QWidget] = None):
            super(DataTableWidget, self).__init__(parent)
            self.setContextMenuPolicy(Qt.CustomContextMenu)
            self.setSelectionBehavior(QAbstractItemView.SelectItems)
            self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
    
            self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
            self.horizontalHeader().setTextElideMode(Qt.ElideMiddle)
    
            self.setSizeAdjustPolicy(QAbstractItemView.AdjustToContents)
    
            self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
    
            for j in range(20):
                if self.columnCount() <= j:
                    self.insertColumn(j)
                for i in range(20):
                    if self.rowCount() <= i:
                        self.insertRow(i)
                    self.setItem(i, j, QTableWidgetItem('Hello'))
    
            word = ''.join(random.choice(string.ascii_letters) for _ in range(6))
            self.setHorizontalHeaderLabels([str(word) for i in range(self.columnCount())])
            self.setVerticalHeaderLabels([str(word) for i in range(self.rowCount())])
    
            self.setMinimumSize(self.get_table_widget_size())
    
        def get_table_widget_size(self):
            w = self.verticalHeader().width() + 4  # +4 seems to be needed
            for i in range(self.columnCount()):
                w += self.columnWidth(i)  # seems to include gridline (on my machine)
            h = self.horizontalHeader().height() + 4
            for i in range(self.rowCount()):
                h += self.rowHeight(i)
            return QSize(w, h)
    
    
    if __name__ == '__main__':
        app = QApplication()
    
        widget = QWidget()
        widget.resize(400, 600)
        layout = QHBoxLayout(widget)
        layout.addWidget(DataTable())
        widget.show()
    
        app.exec()