How to size QMainWindow to fit a QTableWidget that has setVerticalHeaderLabels

Here is the sample code:

from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem, 
    QMainWindow
from PyQt5.QtCore import QSize
import sys

DATA = {
    f'col{i}': [f'{i * j}' for j in range(1, 10)] for i in range(1, 10)
}


class Table(QTableWidget):
    def __init__(self, d):
        m = len(d[next(iter(d))])
        n = len(DATA)
        super().__init__(m, n)

        hor_headers = []
        for n, (key, values) in enumerate(DATA.items()):
            hor_headers.append(key)
            for m, item in enumerate(values):
                qtitem = QTableWidgetItem(item)
                self.setItem(m, n, qtitem)
        self.setHorizontalHeaderLabels(hor_headers)
        # the sizeHint works fine if I disable this line
        self.setVerticalHeaderLabels(f'row{i}' for i in range(1, m + 2))

        self.resizeColumnsToContents()
        self.resizeRowsToContents()

    # improves the situation but still the window is smaller than the table
    def sizeHint(self):
        hh = self.horizontalHeader()
        vh = self.verticalHeader()
        fw = self.frameWidth() * 2
        return QSize(
            hh.length() + vh.width() + fw,
            vh.length() + hh.height() + fw)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('<TITLE>')
        table = Table(DATA)
        self.setCentralWidget(table)
        # did not work
        # self.setFixedSize(self.layout().sizeHint())


def main(args):
    app = QApplication(args)
    main_win = MainWindow()
    main_win.show()
    raise SystemExit(app.exec_())


if __name__ == "__main__":
    main(sys.argv)

Here is the result:

with setVerticalHeaderLabels

The 9th row and the 9th column are not shown and there are scroll bars.

If I comment out the self.setVerticalHeaderLabels(f'row{i}' for i in range(1, m + 2)) line then it will work:

without setVerticalHeaderLabels

How can I perfectly fit the main window to the table widget while having vertical header labels?

As you can see in code comments, I have tried the solutions suggested at python qt : automatically resizing main window to fit content but they are not are not working.

Answer

The problem is that when a complex widget like an item view is not yet “mapped”, the actual size of its children (headers and scroll bars) is not yet updated. Only when the view is finally shown and possibly added to a layout, then it will resize itself again in order to properly resize its children using updateGeometries.

This means that, until that point, the size of each header is based on its default basic contents (the row number for a vertical header).

The solution is simple: don’t use the header size, but their hints, which are computed using the actual text that is going to be displayed:

def sizeHint(self):
    hh = self.horizontalHeader()
    vh = self.verticalHeader()
    fw = self.frameWidth() * 2
    return QSize(
        hh.length() + vh.sizeHint().width() + fw,
        vh.length() + hh.sizeHint().height() + fw)