I have a QScrollArea that contains a widget that can be dynamically expanded down by adding new elements to its layout. I want to automatically scroll the QScrollArea to the bottom when a new element is added to layout.
// widget.hpp
class MainWidget: public QScrollArea {
Q_OBJECT
public:
explicit MainWidget();
public slots:
void add_new_elem(bool);
private:
void initialize_new_item(void);
QWidget* scrolled_widget;
std::unique_ptr<QVBoxLayout> scrolled_widget_layout;
std::unique_ptr<QPushButton> add_new_item_button;
};
// widget.cpp
#include "widget.hpp"
MainWidget::MainWidget() : QScrollArea() {
scrolled_widget = new QWidget(this);
scrolled_widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
scrolled_widget_layout = std::make_unique<QVBoxLayout>(scrolled_widget);
add_new_item_button = std::make_unique<QPushButton>("Add new item!");
add_new_item_button->show();
connect(add_new_item_button.get(), &QPushButton::clicked, this, &MainWidget::add_new_elem);
setWidget(scrolled_widget);
setWidgetResizable(true); // required!
setAlignment(Qt::AlignBottom | Qt::AlignLeft);
}
void MainWidget::initialize_new_item(void) {
auto elem = new Elem();
scrolled_widget_layout->addWidget(elem);
}
void MainWidget::add_new_elem(bool) {
initialize_new_item();
QScrollBar* scrollbar = verticalScrollBar();
scrollbar->setSliderPosition(scrollbar->maximum()); */
}
The issue is that, by the time scrollbar->maximum()
is called, the maximum
property has not yet been updated (maybe because of the event loop), and the scrollbar is set to an incorrect position.
I searched documentation but did not find signals that indicate geometry changes, and I hate the idea of setting a timer.
How can I make this work?
Use QAbstractSlider::rangeChanged
, but take into account that it will get triggered by any size change(*). In this case, I used a simple bool
to switch on autoscrolling when an item is added, and switch it off when the scrolling is done:
#include <QApplication>
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QScrollArea scrollArea;
QWidget scrollAreaWidget;
QVBoxLayout scrollAreaWidgetLayout(&scrollAreaWidget);
QPushButton button("New item");
bool autoScroll = false;
QObject::connect(&button, &QPushButton::clicked, [&scrollAreaWidgetLayout, &autoScroll]()
{
scrollAreaWidgetLayout.addWidget(new QPushButton);
//enable autoscrolling when an item is added
autoScroll = true;
});
QObject::connect(scrollArea.verticalScrollBar(), &QScrollBar::rangeChanged, [&scrollArea, &autoScroll]()
{
if(autoScroll)
{
QScrollBar* scrollbar = scrollArea.verticalScrollBar();
scrollbar->setSliderPosition(scrollbar->maximum());
//disable it when scrolling is done
autoScroll = false;
}
});
scrollArea.setWidget(&scrollAreaWidget);
scrollArea.setWidgetResizable(true);
scrollArea.show();
button.show();
return a.exec();
}
(*): Credits to musicamante for pointing this out.