Search code examples
pythonpyqtpyqt5qscrollarea

QScrollArea.ensureWidgetVisible method does not show target widget


I am trying to make the last QPushButton visible by using method QScrollArea().ensureWidgetVisible(), but as you can see this method doesn't scroll till the last QPushButton.

Example

Could you please assist and solve my issue perhaps issue with setFrameStyle? thank you in advance.

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


class Widget(QWidget):

    def __init__(self, parent= None):
        super(Widget, self).__init__()
        self.setFixedHeight(200)

        #Container Widget        
        widget = QWidget()
        #Layout of Container Widget
        layout = QVBoxLayout(self)
        for _ in range(20):
            btn = QPushButton("test")
            layout.addWidget(btn)
        widget.setLayout(layout)


        #Scroll Area Properties
        scroll = QScrollArea()
        scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scroll.setWidgetResizable(False)
        scroll.setWidget(widget)

        # print(scroll.verticalScrollBar().maximum())
        # vbar = scroll.verticalScrollBar()
        # vbar.setValue(vbar.maximum())
        #vbar.setValue(vbar.maximum())


        #Scroll Area Layer add 
        vLayout = QVBoxLayout(self)
        vLayout.addWidget(scroll)
        self.setLayout(vLayout)


        # items = (layout.itemAt(i) for i in range(layout.count())) 
        # for w in items:
        #     print(w)
        print(layout.count())
        #scroll.ensureWidgetVisible(layout.itemAt(layout.count()-5).widget(), xMargin=10, yMargin=10 )
        scroll.ensureWidgetVisible(layout.itemAt(layout.count()-1).widget() )
        print(layout.itemAt(layout.count()-1).widget(),"last widget")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    dialog = Widget()
    dialog.show()

    app.exec_()

Solution

  • The problem is that for efficiency reasons widgets sizes are not calculated or updated until they are displayed, in your case the viewport of QScrollArea has not updated its size and therefore moves the scroll to an intermediate position. A possible solution is to use QTimer::singleShot() to call the function ensureWidgetVisible() a moment after it has been displayed:

    import sys
    from functools import partial
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent= None):
            super(Widget, self).__init__(parent)
            self.setFixedHeight(200)
    
            #Container Widget        
            widget =QtWidgets.QWidget()
            #Layout of Container Widget
            layout = QtWidgets.QVBoxLayout(widget)
            for _ in range(20):
                btn = QtWidgets.QPushButton("test")
                layout.addWidget(btn)
    
            scroll = QtWidgets.QScrollArea()
            scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
            scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
            scroll.setWidgetResizable(False)
            scroll.setWidget(widget)
    
            #Scroll Area Layer add 
            vLayout = QtWidgets.QVBoxLayout(self)
            vLayout.addWidget(scroll)
    
            last_widget = layout.itemAt(layout.count()-1).widget()
            QtCore.QTimer.singleShot(0, partial(scroll.ensureWidgetVisible, last_widget))
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        dialog = Widget()
        dialog.show()
        sys.exit(app.exec_())
    

    or simply call show() before:

    ...
    last_widget = layout.itemAt(layout.count()-1).widget() 
    self.show()
    scroll.ensureWidgetVisible(last_widget)