Search code examples
pythonpyqt5qtabwidget

PyQt5 resize tabs to its content


Consider this example. I want to make a program where the main window is divided into three parts which can be resized. In the middle I want to have two widgets placed vertially, the bottom one is QTabWidget, where users can change certain properties. Currently I have only one tab and one property there can be more.

I saw similar questions (here and here) but I can't seem to fathom how all the different parts related to size and layout even work together in the first place + they were C++ questions.

Please help me resize QTabWidget to its minimum necessary size to show the contents of the current tab. As side note you can point me to some understandable docs for a beginner in GUI and PyQt5.

import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QLineEdit, QLabel, QSplitter, QWidget, QListWidget, QApplication, QTabWidget, QGroupBox, \
    QFormLayout, QSizePolicy, QLayout
from PyQt5.QtCore import Qt


class Example(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.init_tabs()

        self.main_splitter = QSplitter(Qt.Horizontal)
        some_left_widget = QWidget()
        some_right_widget = QWidget()

        mid = QSplitter(Qt.Vertical)
        mid.addWidget(QListWidget())
        mid.addWidget(self.tabs)

        self.main_splitter.addWidget(some_left_widget)
        self.main_splitter.addWidget(mid)
        self.main_splitter.addWidget(some_right_widget)
        self.setCentralWidget(self.main_splitter)
        self.showMaximized()

    def init_tabs(self):
        self.properties_dict = {}
        self.properties_dict['Text'] = QLineEdit()

        self.tabs = QTabWidget()
        self.properties_groupbox = QGroupBox("Overview")

        layout = QFormLayout()
        for k, v in self.properties_dict.items():
            layout.addRow(QLabel(k + ':'), v)

        self.properties_groupbox.setLayout(layout)
        self.tabs.addTab(self.properties_groupbox, 'Properties')

        # I have no idea how these work
        self.properties_groupbox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.properties_groupbox.resize(self.properties_groupbox.minimumSizeHint())
        self.properties_groupbox.adjustSize()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

Left one is now, right one is desired

Now Desired


Solution

  • A QSplitter uses complex computation to evaluate the sizes it assigns to each of its child widgets, especially when resizing (which is something that also happens as soon as it's first shown, like any other widget).

    The most important aspects it takes into account are the widgets size hints (what the widget suggests it would be the preferable size) and size policy (how the widget can be resized and how it will behave if there's more or less available space).

    To achieve what you want, you'll need to set the size policy stretch (which is the proportion of available space in the layout the widget will try to use).

    Just add the following lines after adding the widgets to the splitter:

        mid.setStretchFactor(0, 1)
        mid.setStretchFactor(1, 0)
    

    The first line indicates that the first widget (the list) will use a stretch factor of 1, while the second (the tab widget) will be 0. The stretch factor is computed based on the sum of all the stretch factors of the widgets.
    In this way the list will try to uccupy the maximum available space (since 1 is the maximum of 1 + 0), while the tab the least.

    Remember that stretch factor also consider the size hints of the widget, so if you set 2 to the list and 1 to the tab, you will not get a list with a height twice than that of the tab.
    Also, as soon as the splitter is resized, the new proportions will be used when the splitter is resized, ignoring the previously set stretch factors.