Search code examples
pythonpyqtpyqt5qscrollarea

How to implement an own QScrollArea


I am trying to understand the way a QScrollArea is working to implement my own MyQScrollArea widget. MyQScrollArea should make use of setViewportMargins. For this I wrote a minimum working example shown below:

from PyQt5 import QtWidgets
import sys

class MyScrollArea(QtWidgets.QAbstractScrollArea):
    def __init__(self):
        super().__init__()

        self.label = QtWidgets.QLabel(", ".join(map(str, range(100))), self)

        hScrollBar = self.horizontalScrollBar()

        hScrollBar.setRange(0, self.label.sizeHint().width() - self.sizeHint().width())
        hScrollBar.valueChanged.connect(self._HScrollBarValueChanged)

        self.setViewportMargins(100, 0, 0, 0)
        self._HScrollBarValueChanged(0)

    def _HScrollBarValueChanged(self, value):
        self.label.move(-value + self.viewportMargins().left(), 0)

def main():
    app = QtWidgets.QApplication(sys.argv)
    scroll = MyScrollArea()
    scroll.show()
    app.exec_()

if __name__ == "__main__":
    main()

The result of the code is shown below:

Widget before scrolling

However, after I scroll the inner widget moves out of the viewport and paints itself in an area I do not want it to be painted:

Widget after scrolling

What am I doing wrong and how can I get the setViewportMargins functionality working?


Solution

  • You have to set it the QLabel as a child of the viewport, in addition to modifying the properties of the QScrollBar every time the geometry of the widgets is modified:

    import sys
    
    from PyQt5 import QtWidgets
    
    
    class MyScrollArea(QtWidgets.QAbstractScrollArea):
        def __init__(self):
            super().__init__()
    
            self.label = QtWidgets.QLabel(", ".join(map(str, range(100))), self.viewport())
    
            self.setViewportMargins(100, 0, 0, 0)
            self.horizontalScrollBar().valueChanged.connect(
                self.on_hscrollbar_value_changed
            )
            self.update_scrollbar()
    
        def on_hscrollbar_value_changed(self, value):
            self.label.move(-value, 0)
    
        def update_scrollbar(self):
            self.horizontalScrollBar().setRange(
                0, self.label.sizeHint().width() - self.viewport().width()
            )
            self.horizontalScrollBar().setPageStep(self.viewport().width())
    
        def resizeEvent(self, event):
            self.update_scrollbar()
            super().resizeEvent(event)
    
    
    def main():
        app = QtWidgets.QApplication(sys.argv)
        scroll = MyScrollArea()
        scroll.show()
        app.exec_()
    
    
    if __name__ == "__main__":
        main()