How could I make a QScrollArea
stick to the top and grow according to the widgets added to it?
When I add the QScrollArea
to the layout like this: vlayout->addWidget(scrollArea, 0, Qt::AlignTop);
, it doesn't obey the Qt::AlignTop
flag and is floating in the middle:
class Widget : public QWidget // source: Qt6
{
Q_OBJECT
public:
Widget() : QWidget(nullptr)
{
QVBoxLayout* vlayout = new QVBoxLayout(this);
QScrollArea* scrollArea = new ScrollArea(this);
QWidget* scrollAreaWidget = new QWidget(scrollArea);
QVBoxLayout* scrollAreaLayout = new QVBoxLayout(scrollAreaWidget);
scrollAreaLayout->setContentsMargins(0, 0, 0, 0);
scrollAreaLayout->setSizeConstraint(QLayout::SetFixedSize);
scrollAreaWidget->setLayout(scrollAreaLayout);
scrollArea->setWidget(scrollAreaWidget);
scrollArea->setWidgetResizable(true);
scrollArea->setAlignment(Qt::AlignTop);
scrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
//scrollArea->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
QPushButton* add = new QPushButton("add", this);
connect(add, &QPushButton::clicked, [=]
{
QWidget* widget = new QWidget;
QHBoxLayout* hlayout = new QHBoxLayout(widget);
hlayout->setContentsMargins(0, 0, 0, 0);
QPushButton* button = new QPushButton("button_" + QString::number(scrollAreaLayout->count()), this);
button->setFixedWidth(300);
QPushButton* remove = new QPushButton("remove", this);
hlayout->addWidget(button);
hlayout->addWidget(remove);
connect(remove, &QPushButton::clicked, [=]{ widget->deleteLater(); });
scrollAreaLayout->addWidget(widget, 0, Qt::AlignTop);
});
vlayout->addWidget(add, 0, Qt::AlignTop);
//vlayout->addWidget(scrollArea);
vlayout->addWidget(scrollArea, 0, Qt::AlignTop);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget widget;
widget.show();
}
When I add it to the layout this way: vlayout->addWidget(scrollArea);
, it grows to the entire layout height:
I have tried all size policies, e.g.:
scrollArea->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
I also tried changing sizeAdjustPolicy
:
scrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
The documentation says:
QAbstractScrollArea::AdjustToContents
The scroll area will always adjust to the viewport
Why is it not adjusting? Am I doing something wrong?
What I'm trying to achieve is to have it on the top and make it grow according to the widgets added to it.
It would look like this:
Then it would continue growing for each widget added until there's no more space on the parent layout, and at this point, it would then display the QScrollBars
.
Spacers are better suited to force widgets into a direction, as they act as an excess space consumer, and they are adjustable.
Also, based on what musicamante said:
QAbstractScrollArea keeps a cached size hint once it's initialized. If you add child widgets to its contents, then you need to override its
::sizeHint()
[...]
And:
You need to call
updateGeometry()
when the size hint of a widget changes.
So, from the source code of QAbstractScrollArea(1), I borrowed the implementation of sizeHint
, and used QScrollArea::widget
's sizeHint
instead of the viewport's as in the original implementation, but you could also use viewportSizeHint
(2).
Take into account the comments in the following code:
#include <QApplication>
#include <QtWidgets>
class ScrollArea : public QScrollArea
{
public:
QSize sizeHint() const override
{
if (sizeAdjustPolicy() == QAbstractScrollArea::AdjustIgnored)
return QSize(256, 192);
//I made a change here where I check for the existence of QScrollArea::widget
if (widget() && sizeAdjustPolicy() == QAbstractScrollArea::AdjustToContents)
{
QSize newSizeHint;
const int f = 2 * frameWidth();
const QSize frame(f, f);
const bool vbarHidden = !verticalScrollBar()->isVisibleTo(this) || verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff;
const bool hbarHidden = !horizontalScrollBar()->isVisibleTo(this) || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff;
const QSize scrollbars(vbarHidden ? 0 : verticalScrollBar()->sizeHint().width(),
hbarHidden ? 0 : horizontalScrollBar()->sizeHint().height());
//here we take into account QScrollArea::widget size hint
newSizeHint = frame + scrollbars + widget()->sizeHint()/*viewportSizeHint()*/;
return newSizeHint;
}
return QScrollArea::sizeHint();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget widget;
QVBoxLayout* vlayout = new QVBoxLayout(&widget);
ScrollArea scrollArea;
QWidget scrollAreaWidget(&scrollArea);
QVBoxLayout scrollAreaLayout(&scrollAreaWidget);
scrollArea.setWidget(&scrollAreaWidget);
scrollArea.setWidgetResizable(true);
//this allows us to get into the section
//that calculates the scrollArea's size based on its contents in its sizeHint
scrollArea.setSizeAdjustPolicy(QScrollArea::AdjustToContents);
//we need to set sizePolicy to Preferred for the height
//to avoid the scrollarea expanding needlessly when it has no content
scrollArea.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
//visuals to demonstrate the size of the scrollarea
scrollArea.setStyleSheet("QScrollArea{border: 2px solid red}");
QVBoxLayout innerScrollAreaLayout;
//to avoid using layout-alignment flags
//use another layout which will contain the added widgets
//and add a spacer sibling underneath it to push the content upwards
scrollAreaLayout.addLayout(&innerScrollAreaLayout);
scrollAreaLayout.addStretch();
QPushButton add("add");
QObject::connect(&add, &QPushButton::clicked, [&scrollArea, &scrollAreaWidget, &innerScrollAreaLayout]
{
QWidget* widget = new QWidget;
QHBoxLayout* hlayout = new QHBoxLayout(widget);
hlayout->setContentsMargins(0, 0, 0, 0);
QPushButton* button = new QPushButton("button_" + QString::number(innerScrollAreaLayout.count()));
button->setFixedWidth(300);
QPushButton* remove = new QPushButton("remove");
hlayout->addWidget(button);
hlayout->addWidget(remove);
QObject::connect(remove, &QPushButton::clicked, [widget, &scrollArea]
{
widget->deleteLater();
//to make the scrollArea ruduce its size
//notify it using this
scrollArea.updateGeometry();
});
//add it to the inner layout which has a spacer as a sibling
//and is contained inside the QScrollArea::widget's layout
innerScrollAreaLayout.addWidget(widget);
//this makes sure the layout takes into account the new size hint
//without it, sizeHint will not be called
scrollArea.updateGeometry();
});
vlayout->addWidget(&add);
vlayout->addWidget(&scrollArea);
//this is necessary as it does not allow the scrollarea to expand
//and take more space than it actually needs
//it basically adds a spacer that takes the space excess
vlayout->addStretch();
widget.show();
return a.exec();
}
(1): note that there is a bug currently in line 1436: const bool hbarHidden = !d->vbar->isVisibleTo(this) || d->hbarpolicy == Qt::ScrollBarAlwaysOff;
, where instead of d->hbar
, there's d->vbar
. I reported this here: QTBUG-123886.
(2): this does not mean QScrollArea::viewport
and QScrollArea::widget
are the same, as the first is the "window" we're looking through at the content, and the second is the inner container that expands beyond the viewport when needed.