Search code examples
pythonqtpyqtqtabwidget

PyQt Hide Contents of QTabWidget but keep QTabBar visible


I was wondering if it is possible in PyQt, when trying to hide QTabWidget, to hide the contents themselves, but keep the QTabBar visible.

As of now, I have custom TabBar and TabWidget that inherit from QTabBar and QTabWidget. My TabBar is vertical and on the west side of the TabWidget.

I have created the button and connected a slot to it that is supposed to hide the TabWidget. Issue is, hiding the TabWidget hides the TabBar, which I do not want.

I have tried overriding .hide() method, tried

self.tab_widget.hide()
self.tab_widget.tabBar().show()

But nothing works, since the TabWidget is the parent of TabBar, hiding TabWidget, automatically hides TabBar.

Of course I could probably construct my own poor man's TabWidget from two widgets so that I could hide and show the widgets whenever I want, but inhereting from QTabWidget is very convenient and I do not want to do it from scratch.

I also upon hiding could just resize tab widget to the width of the TabBar, but I feel that is stupid solution as well.

My Idea is to have an expandable/shrinkable and hideable sidebar, such as in VSCode. enter image description here

Solutions in C++ QT will be helpful as well, as I understand both, and it is not necessarily Python issue, but Qt.


Solution

  • QTabWidget is a composite widget, made of a QTabBar and a QStackedWidget.

    Most importantly, it overrides basic aspects that are required for layout management, such as sizeHint(), minimumSizeHint() and sizePolicy().

    You cannot just toggle the visibility of the tab bar (nor the stacked widget contents):

    1. as you already found out, hiding the QTabWidget and showing the tab bar is fundamentally a no-op, since the parent visibility has precedence: calling show() or setVisible(True) on a nested child (a QWidget without a Qt.Window window flag) will only make it visible as long as the parent is shown;
    2. trying to hide the stacked widget is irrelevant, because the QTabWidget size hint functions will always consider the stacked widget hints, no matter the visibility;

    This is a possible implementation that toggles visibility of the tab "pages" whenever a tab is clicked on the currently visible page (if any): clicking on a currently selected and visible tab will always hide the contents, while contents will be made visible when clicking on any tab, even if no contents are visible.

    from PyQt5.QtWidgets import *
    
    class ToggleTabWidget(QTabWidget):
        _contentsVisible = True
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            self.tabBarClicked.connect(self.toggleContentsVisibleFromClick)
            self._stackedWidget = self.findChild(QStackedWidget)
    
        def toggleContentsVisibleFromClick(self, index):
            if not self._contentsVisible or index == self.currentIndex():
                self.toggleContentsVisibility()
    
        def toggleContentsVisibility(self):
            self.setContentsVisible(not self._contentsVisible)
    
        def setContentsVisible(self, visible):
            if self._contentsVisible != visible:
                self._contentsVisible = visible
                self._stackedWidget.setVisible(visible)
                self.setDocumentMode(not visible)
    
                tabHint = self.tabBar().sizeHint()
                maxSize = 16777215 # default maximum size
                if not self.tabPosition() & self.West:
                    # horizontal
                    if not visible:
                        maxSize = tabHint.height()
                    self.setMaximumHeight(maxSize)
                else:
                    if not visible:
                        maxSize = tabHint.width()
                    self.setMaximumWidth(maxSize)
    
    
    class Test(QWidget):
        def __init__(self):
            super().__init__()
            self.tabWidget = ToggleTabWidget()
            self.tabWidget.addTab(QTextEdit('hello world'), 'Hello')
            self.tabWidget.addTab(QTextEdit('bye world'), 'Bye')
            self.table = QTableWidget(10, 2)
    
            layout = QVBoxLayout(self)
            layout.addWidget(self.tabWidget)
            layout.addWidget(self.table)
    
    
    app = QApplication([])
    test = Test()
    test.show()
    app.exec()
    

    Note that:

    • this doesn't consider style changes (including further calls to setStyleSheet()) or font changes that might affect the size of the tab bar;
    • a proper implementation should not use setMaximum<size-or-dimension>(), but override sizeHint() and minimumSizeHint() instead, and it should also consider possible changes in the tab widget size policy;
    • due to the above, explicit changes in the maximum<size-or-dimension> done outside of the class should be taken into account;
    • the setDocumentMode is used to completely "hide" the contents of the tab pages, and will ignore any previous or further explicit call; if you need to use that property, you have to implement a further property that manages the explicitly set value and the internal one set by the visibility toggling;