Как изменить размер QScrollArea в соответствии с его содержимым

Как мне сделать так, чтобы 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.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
102
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проставки лучше подходят для изменения направления виджетов, поскольку они действуют как потребитель лишнего пространства и их можно регулировать.

Кроме того, основываясь на том, что 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.

Другие вопросы по теме