Search code examples
pythonpyqtqtabwidgetqsplitter

Setting initial size of QTabWidget in QSplitter PyQt application


I have a vertical splitter with a QTabWidget at the top and a QPlainTextEdit widget below (used as a logging window). In the real application, the tabs are filled with QWidgets, containing a matplotlib canvas and a QFrame with some control elements:

    QSplitter
      QPlainTextEdit
      QVBoxLayout
        QTabWidget
          QWidget
            QVBoxLayout
              FigureCanvas (QSizePolicy.Expanding, QSizePolicy.Expanding)
              QFrame (optional)

I would like the application to start with a nice vertical ratio of say 4:1 between the tabs and the logging window. However, using mysplitter.setStretchFactor(4,1) doesn't work here as the sizeHint() of the QTabWidget only is (4,4), causing the QPlainTextEdit with sizeHint() = (256,192) to gobble up nearly all available vertical space. As a workaround, I'm currently setting a fixed height for the QPlainTextWidget but I know that this widget is not the culprit.

I guess I need to fiddle around with sizePolicies or with the layout / sizes of the individual tabs but so far I haven't been successful. I've attached a MWE, the full code is available at https://github.com/chipmuenk/pyFDA/blob/master/pyfda/pyfdax.py :

# -*- coding: utf-8 -*-
from __future__ import print_function
from PyQt5.QtWidgets import (QWidget, QTabWidget, QPlainTextEdit, QSplitter, 
                             QMainWindow, QVBoxLayout, QApplication)
from PyQt5.QtGui import QFontMetrics
from PyQt5 import QtCore

#------------------------------------------------------------------------------
class TabWidgets(QTabWidget):
    def __init__(self, parent):
        super(TabWidgets, self).__init__(parent)
        self.wdg1 = QWidget(self)
        self.wdg2 = QWidget(self)
        self._construct_UI()
#------------------------------------------------------------------------------
    def _construct_UI(self):
        """ Initialize UI with tabbed subplots """
        self.tabWidget = QTabWidget(self)
        self.tabWidget.addTab(self.wdg1, 'Wdg 1')
        self.tabWidget.addTab(self.wdg2, 'Wdg 2')

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.tabWidget)
        self.setLayout(layVMain)
        # When user has switched the tab, call self.current_tab_redraw
        self.tabWidget.currentChanged.connect(self.current_tab_redraw)
#------------------------------------------------------------------------------

    def current_tab_redraw(self):
        pass
        #self.tabWidget.currentWidget().resize()

class MWin(QMainWindow):
    """
    Main window consisting of a tabbed widget and a status window.
    QMainWindow is used as it understands GUI elements like central widget
    """
    def __init__(self, parent=None):
        super(QMainWindow,self).__init__()
 #---------------------------------------------------------------       
        statusWin = QPlainTextEdit(self)  # status window
        tabWin    = TabWidgets(self) # tabbed window
        print('size status win: {0}'.format(statusWin.sizeHint()))
        print('size_tab win: {0}'.format(tabWin.sizeHint()))
        mSize = QFontMetrics(statusWin.font())
        rowHt = mSize.lineSpacing()
        # fixed height for statusWin needed as the sizeHint of tabWin is very small
        statusWin.setFixedHeight(4*rowHt+4)
        # add status window underneath plot Tab Widgets:
        spltVMain = QSplitter(QtCore.Qt.Vertical)
        spltVMain.addWidget(tabWin)
        spltVMain.addWidget(statusWin)
        # relative initial sizes of subwidgets, this doesn't work here
        spltVMain.setStretchFactor(4,1)        

        spltVMain.setFocus()
        # make spltVMain occupy the main area of QMainWindow and set inheritance
        self.setCentralWidget(spltVMain)   
#----------------------------------------------------------------------------
def main():
    import sys
    app = QApplication(sys.argv)

    mainw = MWin(None)
    mainw.resize(300,400)
    app.setActiveWindow(mainw) 
    mainw.show()

    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

Solution

  • I've found an easy workaround: Setting the splitter in absolute units instead of a ratio does the job. Stating with the total height of the splitter widget, makes the solution work with different resolutions etc. The code snippet below shows the updated __init__() part:

    def __init__(self, parent=None):
        super(QMainWindow,self).__init__()
        #---------------------------------------------------------------       
        statusWin = QPlainTextEdit(self)  # status window
        tabWin    = TabWidgets(self) # tabbed window
        print('size status win: {0}'.format(statusWin.sizeHint()))
        print('size_tab win: {0}'.format(tabWin.sizeHint()))
        # fixed height for statusWin no longer needed here
        # mSize = QFontMetrics(statusWin.font())
        # rowHt = mSize.lineSpacing()
        # statusWin.setFixedHeight(4*rowHt+4)
        # add status window underneath plot Tab Widgets:
        spltVMain = QSplitter(QtCore.Qt.Vertical)
        spltVMain.addWidget(tabWin)
        spltVMain.addWidget(statusWin)
        # relative initial sizes of subwidgets, this doesn't work here
        # spltVMain.setStretchFactor(4,1)
        # Use absolute values instead:
        spltVMain.setSizes([spltVMain.size().height() * 0.8, 
                            spltVMain.size().height() * 0.2])
    
        spltVMain.setFocus()
        # make spltVMain occupy the main area of QMainWindow and set inheritance
        self.setCentralWidget(spltVMain)   
    #-----------------------------------------------------------------------