Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 412
Daniele Pantaleone Avatar asked Aug 31 '15 18:08

Daniele Pantaleone


People also ask

What is the best way to display scroll bars in Qt?

Qt displays scroll bars in a way that is appropriate for each platform. If you need to provide a scrolling view onto another widget, it may be more convenient to use the QScrollArea class because this provides a viewport widget and scroll bars.

Is there a policy setting for asneeded in the QtQuick scrollbar?

So, the QtQuick Scrollbar has a policy setting for AsNeeded, AlwaysOn and AlwaysOff. Unfortunately, with AsNeeded there is a core problem: The scrollbar only shows while mousing over the flickable it is attached to ( A ListView with CheckDelegates in my case).

Why can't I see the scrollbar in asneeded?

Unfortunately, with AsNeeded there is a core problem: The scrollbar only shows while mousing over the flickable it is attached to ( A ListView with CheckDelegates in my case). This means by just looking at the menu, there might be more checkboxes the user does not see until they mouse over the ListView, which doesn't always happen.

What is a scroll bar in HTML?

A scroll bar is a control that enables the user to access parts of a document that is larger than the widget used to display it. It provides a visual indication of the user's current position within the document and the amount of the document that is visible.


1 Answers

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_())
like image 109
ekhumoro Avatar answered Nov 14 '22 23:11

ekhumoro