dvilela
dvilela

Reputation: 1262

QTableWidget with relative-width, stretchable columns

I'm trying to achieve a pyqt5 table similar to the following one:

enter image description here

First of all: I am not even sure that QTableWidget is the most appropiate widget, as I do not have much experience with Qt. QGridLayout could fit better. I tried it first but somehow I didn't know how to make it work to fit the requirements, any advice would be very much appreciated.

The table requirements are:

My current problem is that the use of setSectionResizeMode conflicts with resizeSection. I can have different column widths OR stretchable table width, but I don't know how to achieve both.

The example code looks like this:

from PyQt5.QtWidgets import QApplication, QWidget, QHeaderView, QTableWidget, QTableWidgetItem, QVBoxLayout
import sys

data = {'col1':['1','2','3','4'],
        'col2':['1','2','1','3'],
        'col3':['1','1','2','1']}


class MainWindow(QWidget):

    def __init__(self):

        super().__init__()

        self.table = QTableWidget()
        self.table.setRowCount(4)
        self.table.setColumnCount(3)
        self.table.verticalHeader().hide()
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        # self.table.horizontalHeader().resizeSection(1, 200) <-- no effect if stretch is activated

        self.table.setHorizontalHeaderLabels([colName for colName in data])
        self.fillTable()

        self.layout = QVBoxLayout()
        self.layout.addWidget(self.table)
        self.setLayout(self.layout)


    def fillTable(self):
        for colNumber, colName in enumerate(data):
            for rowNumber, value in enumerate(data[colName]):
                self.table.setItem(rowNumber, colNumber, QTableWidgetItem(value))


if __name__=="__main__":

    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

Here's my other attempt, using a QGridLayout. The problem here is that the columns do not expand when the window is resized.

import sys
from PyQt5.QtWidgets import (QWidget, QGridLayout, QLabel, QPushButton, QApplication, QScrollArea, QVBoxLayout)
from PyQt5.QtCore import Qt

class MainWindow(QWidget):

    def __init__(self):

        super().__init__()

        # Label
        self.label = QLabel('Test')

        # Grid
        self.grid = QWidget()
        self.gridLayout = QGridLayout()
        self.grid.setLayout(self.gridLayout)
        self.grid.setMinimumWidth(600) # Without this, the width relations are not kept

        # Grid elements
        for i in range(20):
            self.gridLayout.addWidget(QPushButton('A'), i, 0, 1, 1)
            self.gridLayout.addWidget(QPushButton('B'), i, 1, 1, 3)
            self.gridLayout.addWidget(QPushButton('C'), i, 4, 1, 1)

        # Scroll area
        self.scrollArea = QScrollArea()
        self.scrollArea.setWidget(self.grid)
        self.scrollArea.setMinimumWidth(self.grid.sizeHint().width())
        self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        # Window layout
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.scrollArea)
        self.setLayout(self.layout)


if __name__ == '__main__':

    app = QApplication(sys.argv)
    windowExample = MainWindow()
    windowExample.show()
    sys.exit(app.exec_())

Upvotes: 2

Views: 2237

Answers (1)

musicamante
musicamante

Reputation: 48231

Using a grid layout is probably the best choice, as item views are better suited for showing item models and not to contain GUI elements.

What you're missing is the widgetResizable property:

If this property is set to false (the default), the scroll area honors the size of its widget.

[...] If this property is set to true, the scroll area will automatically resize the widget in order to avoid scroll bars where they can be avoided, or to take advantage of extra space.

Then, you should not use the column span to ensure that widgets expand themselves, but setColumnStretch instead.

Finally, to ensure that the scroll area always displays the contents without "cutting" the right side, you should not disable the horizontal scroll bar, but, instead, properly set the minimum width based on the contents of the area, the vertical scroll bar width and the scroll area frame width.

Consider that with all this you obviously don't need to set the grid minimum width anymore.

    # Grid elements
    for i in range(20):
        self.gridLayout.addWidget(QPushButton('A'), i, 0)
        self.gridLayout.addWidget(QPushButton('B'), i, 1)
        self.gridLayout.addWidget(QPushButton('C'), i, 2)
    # ensure that the second column uses a stretch factor bigger than the
    # default 0 value (which will only honor the size hints and policies of
    # each widget
    self.gridLayout.setColumnStretch(1, 1)

    # Scroll area
    self.scrollArea = QScrollArea()
    self.scrollArea.setWidget(self.grid)
    self.scrollArea.setWidgetResizable(True)
    # compute the correct minimum width
    width = (self.grid.sizeHint().width() + 
        self.scrollArea.verticalScrollBar().sizeHint().width() + 
        self.scrollArea.frameWidth() * 2)
    self.scrollArea.setMinimumWidth(width)

Upvotes: 1

Related Questions