Как мне сделать так, чтобы QScrollArea прикреплялся вверху и рос в зависимости от добавленных в него виджетов?
Когда я добавляю QScrollArea в макет следующим образом: vlayout->addWidget(scrollArea, 0, Qt::AlignTop);, он не подчиняется флагу Qt::AlignTop и плавает посередине:
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();
}
Когда я добавляю его в макет следующим образом: vlayout->addWidget(scrollArea);, он вырастает на всю высоту макета:
Я пробовал все политики размера, например:
scrollArea->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
Я также попробовал изменить sizeAdjustPolicy:
scrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
В документации говорится:
QAbstractScrollArea::AdjustToContents
Область прокрутки всегда будет подстраиваться под область просмотра.
Почему не настраивается? Я делаю что-то неправильно?
Чего я пытаюсь добиться, так это разместить его сверху и заставить его расти в соответствии с добавленными к нему виджетами.
Это будет выглядеть так:
Затем он будет продолжать расти для каждого добавленного виджета до тех пор, пока в родительском макете не останется места, и в этот момент он будет отображать QScrollBars.





Проставки лучше подходят для изменения направления виджетов, поскольку они действуют как потребитель лишнего пространства и их можно регулировать.
Кроме того, основываясь на том, что musicamante сказал:
QAbstractScrollArea сохраняет подсказку о кэшированном размере после инициализации. Если вы добавите в его содержимое дочерние виджеты, то вам необходимо переопределить его
::sizeHint()[...]
И:
Вам нужно вызвать
updateGeometry(), когда изменится размер виджета.
Итак, из исходного кода QAbstractScrollArea(1) я позаимствовал реализацию sizeHint и использовал QScrollArea::widgetsizeHint вместо области просмотра, как в исходной реализации, но вы также можете использовать viewportSizeHint(2).
Учитывайте комментарии в следующем коде:
#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.