pythonpyside2qtabbar

Force QTabBar tabs to stay as small as possible and ignore sizeHint


I'm trying to have a + button added to a QTabBar. There's a great solution from years ago, with a slight issue that it doesn't work with PySide2. The problem is caused by the tabs auto resizing to fill the sizeHint, which in this case isn't wanted as the extra space is needed. Is there a way I can disable this behaviour?

I've tried QTabBar.setExpanding(False), but according to this answer, the property is mostly ignored:

The bad news is that QTabWidget effectively ignores that property, because it always forces its tabs to be the minimum size (even if you set your own tab-bar).

The difference being in PySide2, it forces the tabs to be the preferred size, where I'd like the old behaviour of minimum size.

Edit: Example with minimal code. The sizeHint width stretches the tab across the full width, whereas in older Qt versions it doesn't do that. I can't really override tabSizeHint since I don't know what the original tab size should be.

import sys
from PySide2 import QtCore, QtWidgets

class TabBar(QtWidgets.QTabBar):
    def sizeHint(self):
        return QtCore.QSize(100000, super().sizeHint().height())

class Test(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)

        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)

        tabWidget = QtWidgets.QTabWidget()
        tabWidget.setTabBar(TabBar())
        layout.addWidget(tabWidget)

        tabWidget.addTab(QtWidgets.QWidget(), 'this shouldnt be stretched')

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    test = Test()
    test.show()
    sys.exit(app.exec_())

Solution

  • I think there may be an easy solution to your problem (see below). Where the linked partial solution calculated absolute positioning for the '+' button, the real intent with Qt is always to let the layout engine do it's thing rather than trying to tell it specific sizes and positions. QTabWidget is basically a pre-built amalgamation of layouts and widgets, and sometimes you just have to skip the pre-built and build your own.

    example of building a custom TabWidget with extra things across the TabBar:

    import sys
    from PySide2 import QtWidgets
    from random import randint
        
    class TabWidget(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            #layout for entire widget
            vbox = QtWidgets.QVBoxLayout(self)
            
            #top bar:
            hbox = QtWidgets.QHBoxLayout()
            vbox.addLayout(hbox)
            
            self.tab_bar = QtWidgets.QTabBar()
            self.tab_bar.setMovable(True)
            hbox.addWidget(self.tab_bar)
            
            spacer = QtWidgets.QSpacerItem(0,0,QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
            hbox.addSpacerItem(spacer)
            
            add_tab = QtWidgets.QPushButton('+')
            hbox.addWidget(add_tab)
            
            #tab content area:
            self.widget_stack = QtWidgets.QStackedLayout()
            vbox.addLayout(self.widget_stack)
            self.widgets = {}
    
            #connect events
            add_tab.clicked.connect(self.add_tab)
            self.tab_bar.currentChanged.connect(self.currentChanged)
            
        def add_tab(self):
            tab_text = 'tab' + str(randint(0,100))
            tab_index = self.tab_bar.addTab(tab_text)
            widget = QtWidgets.QLabel(tab_text)
            self.tab_bar.setTabData(tab_index, widget)
            
            self.widget_stack.addWidget(widget)
            
            self.tab_bar.setCurrentIndex(tab_index)
    
            
        def currentChanged(self, i):
            if i >= 0:
                self.widget_stack.setCurrentWidget(self.tab_bar.tabData(i))
            
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        test = TabWidget()
        test.show()
        sys.exit(app.exec_())
    

    All that said, I think the pre-built QTabWidget.setCornerWidget may be exactly what you're looking for (set a QPushButton to the upper-right widget). The example I wrote should much easier to customize, but also much more effort to re-implement all the same functionality. You will have to re-implement some of the signal logic to create / delete / select / rearrange tabs on your own. I only demonstrated simple implementation, which probably isn't bulletproof to all situations.