2

I have a QScrollArea and I would like when a I push my "Add" button that adds Widgets to the widgets contained in the QScrollArea for the scroll to scroll all the way to the bottom

I made several attempts to Scroll to the bottomw ith code like

   scrollWidget.update()
   bar = scrollWidget.verticalScrollBar()
   bar.setValue(bar.maximum())

or even using ensureWidgetVisible But what appears to be happening is it scrolls to the bottom of the scroll "Before" the resize occurs, then it resizes so I am not quite at the bottom.

I verfied this by writing code that checks the bar size, and the maximum bar size and the child count This shows there are new children but the bar size has not yet been updated.

I then tried to give Qt time to "recaluclate" sizes first by calling:

   QApplication.processEvents()
   scrollWidget.update()

I do not want the scroll area to ALWAYS be on the bottom but only after pushing my button

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Jim
  • 2,034
  • 2
  • 15
  • 29

1 Answers1

1

I just had to deal with the same issue, I figured out a solution that I think is good, although I'm a Qt newbie so take it with a grain of salt:

class MyMainWindow(QWidget):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        
        self.scrollarea = QScrollArea()

        # [...]

        self.vscrollbar = self.scrollarea.verticalScrollBar()
        self.vscrollbar.rangeChanged.connect(self.scrollToBottom)

    @Slot(int, int)
    def scrollToBottom(self, minimum, maximum):
        self.vscrollbar.setValue(maximum)

During construction we connect the rangeChanged Signal to our custom Slot scrollToBottom that sets the scrolling value to the maximum value, thereby scrolling down to the bottom every time the contents grow vertically.

I went a step further and made it only scroll to the bottom if the view was scrolled all the way to the bottom before the contents grew:

class MyMainWindow(QWidget):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        
        self.scrollarea = QScrollArea()

        # [...]

        self.vscrollbar = self.scrollarea.verticalScrollBar()
        self.vscrollbar.rangeChanged.connect(self.scrollToBottomIfNeeded)
        self.vscrollbar.valueChanged.connect(self.storeAtBottomState)
        self.atbottom = True

    @Slot(int)
    def storeAtBottomState(self, value):
        self.atbottom = value == self.vscrollbar.maximum()

    @Slot(int, int)
    def scrollToBottomIfNeeded(self, minimum, maximum):
        if self.atbottom:
            self.vscrollbar.setValue(maximum)

In the context of my application, this is the preferred behaviour, as the contents can grow while the user is looking at something in the ScrollArea, so autoscroll would prevent them from staying where they are. If your application only grows the contents after a user action, use the approach in the first snippet.

In response to your comment, this is how to only scroll down when adding an element:

class MyMainWindow(QWidget):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        
        self.scrollarea = QScrollArea()
        self.addbutton = QPushButton()

        self.addbutton.clicked.connect(self.addElement)

        # [...]

        self.vscrollbar = self.scrollarea.verticalScrollBar()
        self.vscrollbar.rangeChanged.connect(self.scrollToBottomIfNeeded)
        self.adding = False

    @Slot()
    def addElement(self):
        self.adding = true
        # ... actually add an element ...

    @Slot(int, int)
    def scrollToBottomIfNeeded(self, minimum, maximum):
        if self.adding:
            self.vscrollbar.setValue(maximum)
            self.adding = False
NeatNit
  • 526
  • 1
  • 4
  • 14
  • so I guess to do this on a button push thats adding an element you would then connect a lambda to rangeChange, add the element, when the range change firres scroll to the bottom and remove the rangeChange lambad so it doesnt do it again – Jim Aug 12 '22 at 13:51
  • If the contents can change at a time when you're not adding an element and you only want to autoscroll when adding an element, you should set a flag before adding the element (like `self.atbottom` in my second example) and unset it after scrolling down in rangeChanged. I don't recommend constantly registering and unregistering lambdas, it feels messier to me. – NeatNit Aug 12 '22 at 16:19
  • @Jim, I added an example of what I meant to the answer :) – NeatNit Aug 12 '22 at 16:34