Qt.ScrollBarAsNeeded not showing scrollbar when it's actually needed
Asked Answered
V

1

9

I'm implementing a python application using PyQt5 and I encountered some problems when making use of a QScrollArea. This is the layout of my application:

enter image description here

It's composed of 2 QScrollArea (left and right pane) and a QMdiArea (center widget) arranged into a QHBoxLayout. When I expand the widgets on the left pane by clicking on the controls, and the height of the QWidget of the QScrollArea is bigger than then height of the QScrollArea itself, the scrollbar appears (as expected), but it's overlapping the content of the QScrollArea. To fix this problem I reimplemented the resizeEvent adding the necessary space for the scrollbar (till this point everything works.

enter image description here

Now, when I manually resize the Main Window, the left Pane gets more space and the scrollbar should disappear (but it doesn't) and it overlaps the widgets of the pane:

enter image description here

I also tried to manually toggle the visibility of the scrollbar (when the resizeEvent is received): when I do this, I can successfully hide the scrollbar but then I can't show it again (not matter if I call setVisible(True) on the scrollbar). This results in the space for the scrollbar being added, but the scrollbar is missing and the content of the pane is not scrollable:

enter image description here

Here is the implementation of the pane widget:

class Pane(QScrollArea):

    MinWidth = 186

    def __init__(self, alignment=0, parent=None):
        super().__init__(parent)
        self.mainWidget = QWidget(self)
        self.mainLayout = QVBoxLayout(self.mainWidget)
        self.mainLayout.setAlignment(alignment)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.mainLayout.setSpacing(0)
        self.setContentsMargins(0, 0, 0, 0)
        self.setFrameStyle(QFrame.NoFrame)
        self.setFixedWidth(Pane.MinWidth)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Ignored)
        self.setWidgetResizable(True)
        self.setWidget(self.mainWidget)

    def resizeEvent(self, resizeEvent):
        if self.viewport().height() < self.widget().height():
            self.setFixedWidth(Pane.MinWidth + 18)
            # THIS DOESN'T WORK
            #self.verticalScrollBar().show()
        else:
            self.setFixedWidth(Pane.MinWidth)
            #self.verticalScrollBar().hide()

    def addWidget(self, widget):
        self.mainLayout.addWidget(widget)

    def removeWidget(self, widget):
        self.mainLayout.removeWidget(widget)

    def update(self, *__args):
        for item in itemsInLayout(self.mainLayout):
            item.widget().update()
        super().update(*__args)

What I want to achieve is pretty simple (but practically it seems not as simple): I would like to dynamically show the vertical scrollbar on my left/right pane widgets only when it's needed, and add the necessary space for the scrollbar so it doesn't overlap the widgets in the QScrollArea.

Before someone asks, I already tried to do something like this:

def resizeEvent(self, resizeEvent):
    if self.viewport().height() < self.widget().height():
        self.setFixedWidth(Pane.MinWidth + 18) 
        scrollbar = self.verticalScrollbar()
        scrollbar.setVisible(True)
        self.setVerticalScrollBar(scrollbar) ## APP CRASH
    else:
        self.setFixedWidth(Pane.MinWidth)
        #self.verticalScrollBar().hide()

which results in my application to crash. I hope that someone already faced this issue and is able to help me.

EDIT: I'm using PyQt5.5 compiled against Qt5.5 under OSX Yosemite 10.10.4 using clang.

Vastah answered 31/8, 2015 at 18:36 Comment(2)
try first to set setWidgetResizable to true for your scrollAreaCorded
it's already set to True, so I guess the problem is somewhere elseVastah
D
3

Everything seems to work as expected for me without any need for workarounds. However, I strongly suspect there are additional constraints in your real code that you have not revealed in your question.

UPDATE

Below is a simple example that resizes the scrollareas when the scrollbars are shown/hidden:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class Window(QtWidgets.QMainWindow):
    def __init__(self):
        super(Window, self).__init__()
        widget = QtWidgets.QWidget(self)
        layout = QtWidgets.QHBoxLayout(widget)
        self.mdi = QtWidgets.QMdiArea(self)
        self.leftScroll = Pane(
            QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft, self)
        self.rightScroll = Pane(
            QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft, self)
        layout.addWidget(self.leftScroll)
        layout.addWidget(self.mdi)
        layout.addWidget(self.rightScroll)
        self.setCentralWidget(widget)
        for scroll in self.leftScroll, self.rightScroll:
            for index in range(4):
                widget = QtWidgets.QTextEdit()
                widget.setText('one two three four five')
                scroll.addWidget(widget)

class Pane(QtWidgets.QScrollArea):
    MinWidth = 186

    def __init__(self, alignment=0, parent=None):
        super().__init__(parent)
        self.mainWidget = QtWidgets.QWidget(self)
        self.mainLayout = QtWidgets.QVBoxLayout(self.mainWidget)
        self.mainLayout.setAlignment(alignment)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.mainLayout.setSpacing(0)
        self.setContentsMargins(0, 0, 0, 0)
        self.setFrameStyle(QtWidgets.QFrame.NoFrame)
        self.setFixedWidth(Pane.MinWidth)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
                           QtWidgets.QSizePolicy.Ignored)
        self.setWidgetResizable(True)
        self.setWidget(self.mainWidget)
        self.verticalScrollBar().installEventFilter(self)

    def addWidget(self, widget):
        self.mainLayout.addWidget(widget)

    def removeWidget(self, widget):
        self.mainLayout.removeWidget(widget)

    def eventFilter(self, source, event):
        if isinstance(source, QtWidgets.QScrollBar):
            if event.type() == QtCore.QEvent.Show:
                self.setFixedWidth(Pane.MinWidth + source.width())
            elif event.type() == QtCore.QEvent.Hide:
                self.setFixedWidth(Pane.MinWidth)
        return super(Pane, self).eventFilter(source, event)

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 800, 300)
    window.show()
    sys.exit(app.exec_())
Destroyer answered 4/9, 2015 at 22:26 Comment(10)
I tried your code, and the problem persists: i.imgur.com/O2o0iil.png. On which platform did you tried the code? maybe it's a OSX only issue.Vastah
@DanielePantaleone. Yes, I suspected there were other factors involved. I'm on Linux, and I cannot test on OSX. You should probably edit your question to make it clear which specific versions of OSX and Qt you are using. It would also help a lot if you added the simplest possible example that reproduces the problem for you on OSX: i.e. perhaps just a single QScrollArea containing one widget.Destroyer
I modified your code, adding 1 widget per Pane (bigger than the scrollarea) and the problem still persists. I will give it a try under Linux in the next few days.Vastah
Tested on Windows 8.1 and Linux Ubuntu 14.04 and the problem still happens.Vastah
@DanielePantaleone. Tested what? I just tried my script on Windows, and it works just fine. To be specific: when resizing the window vertically, the widgets resize horizontally to accommodate the scrollbars, and their text is re-wrapped accordingly.Destroyer
Which is the correct behavior. However it does not happen on my system (which is quite odd). I tested your code using python 3.4 and pyqt5 compiled using mingw32.Vastah
@DanielePantaleone. That is indeed completely baffling - it makes no sense at all. There must surely be some other factors at work here. Can you test on a different machine? It's hard to see any way to make progress with this unless someone else can reproduce the problem.Destroyer
I will try on a different machine indeed. Thanks for your advices :)Vastah
I tried again your code on a Windows machine and it seems to work (not as expected but it works): imgur.com/Al5QAuA,KGDELdB As you can see in the screenshots the scollbar appears and the space for the scrollbars is taken from the Panes widgets (it removes pixels to fit the scrollbar instead of adding them). How can I achieve the other way around? I tried to mess a with QSizePolicy without luck. I'll give you the bounty for the effort though :)Vastah
@DanielePantaleone. I have updated the example to give some idea how to do it. It works for me on Linux - but there is some slight flicker when resizing the window, so you might need to tweak it a little.Destroyer

© 2022 - 2024 — McMap. All rights reserved.