Layout does not change sizeHint to fit children
-
Hello,
This question is somewhat of a continuation from my last question, but now I tried to add more structure to my widget://notificationwidget.h class NotificationScrollArea : public QScrollArea { Q_OBJECT public: explicit NotificationScrollArea(QWidget *parent = nullptr) : QScrollArea(parent){}; QSize sizeHint() const override { return widget()->sizeHint()+QSize(0,2); } }; class NotificationWidget : public QWidget { Q_OBJECT public: explicit NotificationWidget(QWidget *parent = nullptr); void addNotification(const QString &message); void debugHint() const; QSize sizeHint() const override; protected: void resizeEvent(QResizeEvent *event) override; void _adjustSize(); private: QWidget *tabWidget; QHBoxLayout *tabLayout; QPushButton *collapseButton; QPushButton *closeButton; QVBoxLayout *layout; QScrollArea *scrollArea; QWidget *scrollWidget; QVBoxLayout *scrollLayout; };
//notificationwidget.cpp #include "notificationwidget.h" #include <QLabel> NotificationWidget::NotificationWidget(QWidget *parent) : QWidget(parent), tabWidget(new QWidget(this)), tabLayout(new QHBoxLayout(tabWidget)), layout(new QVBoxLayout(this)), collapseButton(new QPushButton("Collapse", this)), closeButton(new QPushButton("Close", this)), scrollArea(new NotificationScrollArea(this)), scrollWidget(new QWidget), scrollLayout(new QVBoxLayout(scrollWidget)) { tabLayout->addWidget(collapseButton); tabLayout->addWidget(closeButton); tabWidget->setLayout(tabLayout); layout->addWidget(tabWidget); layout->addWidget(scrollArea); setLayout(layout); scrollArea->setWidget(scrollWidget); scrollArea->setWidgetResizable(true); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); scrollWidget->setLayout(scrollLayout); setFixedWidth(300); qDebug() << "INITIAL"; debugHint(); } void NotificationWidget::debugHint() const { qDebug() << "sizeHint:" << sizeHint() << "layout:" << layout->sizeHint() << "scrollWidget:" << scrollWidget->sizeHint() << "scrollArea:" << scrollArea->sizeHint() << "scrollLayout:" << scrollLayout->sizeHint() << "tab:" << tabWidget->sizeHint() << "tabL:" << tabLayout->sizeHint() << "collapse:" << collapseButton->sizeHint() << "close:" << closeButton->sizeHint(); } QSize NotificationWidget::sizeHint() const { return layout->sizeHint(); } void NotificationWidget::addNotification(const QString &message) { QLabel *label = new QLabel(message, this); label->setWordWrap(true); scrollLayout->addWidget(label); _adjustSize(); qDebug() << "AFTER ADD" << message << ":"; debugHint(); } void NotificationWidget::_adjustSize() { scrollWidget->adjustSize(); scrollArea->adjustSize(); adjustSize(); } void NotificationWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); _adjustSize(); }
//main.cpp #include <QApplication> #include <QWidget> #include <QVBoxLayout> #include <QHBoxLayout> #include <QPushButton> #include <QScrollArea> #include <QResizeEvent> #include <QDebug> #include <QMainWindow> #include "NotificationWidget.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); QMainWindow mainWindow; mainWindow.setGeometry(100, 100, 800, 500); NotificationWidget *notificationWidget = new NotificationWidget(&mainWindow); mainWindow.setCentralWidget(notificationWidget); for (int i = 0; i < 10; ++i) { notificationWidget->addNotification(QString("Notification %1").arg(i + 1)); } mainWindow.show(); return app.exec(); }
The problem with this is that the sizeHint of the NotificationWidget's layout and therefore (see sizeHint override) of itself does not get updated with the change of the contents, so even when there is plenty of extra space it ends up taking this seemingly arbitrary small size and forcing the scrollArea to use a scroll bar:
The intended behavior is that it takes a constant horizontal space and as much vertical space as the parent allows, and fits both the tabWidget and the scrollArea in this space, giving as much vertical space as possible to the tab widget.
What am I missing in the layout usage here to make it work correctly?
-
Ok, after a lot of pain and reading up on source code of updateGeometry() in QWidget and activate()/update() in QLayout, and also tracking down the issue of a new label being not shown initially (which is really weird because in another project it was fine), I got the exact behaviour I want:
class NotificationScrollArea : public QScrollArea { public: explicit NotificationScrollArea(QWidget *parent = nullptr) : QScrollArea(parent){}; QSize sizeHint() const override { return widget()->sizeHint()+QSize(0,2); } }; class NotificationWidget : public QWidget { public: int i=0; NotificationWidget(QWidget *parent = nullptr) : QWidget(parent) { QVBoxLayout* vlayout = new QVBoxLayout(this); QPushButton* collapse = new QPushButton("Collapse"); QPushButton* close = new QPushButton("Close"); QHBoxLayout* hlayout = new QHBoxLayout; vlayout->addLayout(hlayout); hlayout->addWidget(collapse); hlayout->addWidget(close); hlayout->setAlignment(Qt::AlignRight); QScrollArea* scrollArea = new NotificationScrollArea; setMaximumWidth(300); vlayout->addWidget(scrollArea); QWidget *scrollContent = new QWidget(scrollArea); QVBoxLayout *scrollLayout = new QVBoxLayout(scrollContent); for (; i < 10; ++i) { QLabel *label = new QLabel(QString("Label %1").arg(i+1), scrollContent); label->setWordWrap(1); scrollLayout->addWidget(label); } scrollContent->setLayout(scrollLayout); scrollArea->setWidget(scrollContent); scrollArea->setWidgetResizable(true); scrollArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); connect(collapse,&QPushButton::clicked,[this,scrollArea,scrollLayout,scrollContent](){ i++; QLabel *label = new QLabel(QString("Label %1").arg(i), scrollContent); label->setWordWrap(1); scrollLayout->addWidget(label); label->show(); scrollContent->updateGeometry(); scrollArea->updateGeometry(); updateGeometry(); }); } }; //MainWindow and main code same as before
I'm pretty sure every single line of code here is necessary for it to function.
The three big issues were thatadjustsize()
seems to not work well withscrollArea
but replacing that withupdateGeometry()
fixed it;- it is necessary to override
sizeHint()
ofscrollArea
to depend on thescrollContent
'ssizeHint()
, otherwise thescrollArea
doesn't resize (second picture of previous post); - the label has to be
show
n, otherwise when manually updating the geometry of the entire ordeal it doesn't include the new label.
-
@mpergand Can you explain how is the code clumsy? Please do tell me in what way it could be made better.
As for what I'm trying to achieve, I'm trying to make the scrollArea, and therefore the NotificationWidget, to take as much vertical space as it needs or can (if NotificationWidget is limited by MaximumHeight for example), whichever is smaller. You can see on the image the behaviour is not correct and is quite broken, that should already be a good starting point.
-
@mpergand Can you explain how is the code clumsy? Please do tell me in what way it could be made better.
As for what I'm trying to achieve, I'm trying to make the scrollArea, and therefore the NotificationWidget, to take as much vertical space as it needs or can (if NotificationWidget is limited by MaximumHeight for example), whichever is smaller. You can see on the image the behaviour is not correct and is quite broken, that should already be a good starting point.
@AntonBogun
There is nothing to the right of the list widget ? -
@AntonBogun
There is nothing to the right of the list widget ?@mpergand I'm not sure what you mean by the "list widget" and "to the right" of it, but if you mean the scrollArea then yeah it isn't taking the entire width (300 pixels) for some reason, and also the entire height it should be able to take.
-
@mpergand I'm not sure what you mean by the "list widget" and "to the right" of it, but if you mean the scrollArea then yeah it isn't taking the entire width (300 pixels) for some reason, and also the entire height it should be able to take.
QVBoxLayout* vlayout = new QVBoxLayout(this); QPushButton* collapse = new QPushButton("Collapse"); QPushButton* close = new QPushButton("Close"); QHBoxLayout* hlayout = new QHBoxLayout; vlayout->addLayout(hlayout); hlayout->addWidget(collapse); hlayout->addWidget(close); QScrollArea* scrollArea = new QScrollArea; scrollArea->setMaximumWidth(300); vlayout->addWidget(scrollArea);
-
QVBoxLayout* vlayout = new QVBoxLayout(this); QPushButton* collapse = new QPushButton("Collapse"); QPushButton* close = new QPushButton("Close"); QHBoxLayout* hlayout = new QHBoxLayout; vlayout->addLayout(hlayout); hlayout->addWidget(collapse); hlayout->addWidget(close); QScrollArea* scrollArea = new QScrollArea; scrollArea->setMaximumWidth(300); vlayout->addWidget(scrollArea);
@mpergand Sorry for the delay, but this isn't what I want because it requires setting the maximum width of the scrollArea directly
What is supposed to happens is this:- Notification Widget: ↔ 300 fixed but no more than parent allows, ↕ as much as children need but no more than parent allows
- HWidget: ↔ As much as parent allows, ↕ as much as children need but no more than half the parent
- Buttons (stack horizontally, align to the right): ↔ As much as need, ↕ As much as need
- ScrollArea: ↔ As much as parent allows, ↕ as much as children need but no more than remains in parent
- Labels (stack vertically): ↔ As much as parent allows, ↕ as much as need, given fixed width
- HWidget: ↔ As much as parent allows, ↕ as much as children need but no more than half the parent
After some tweaking, I got this modified code, that seems to almost do what I want (except buttons being aligned to the right):
class NotificationWidget : public QWidget { public: int i=0; NotificationWidget(QWidget *parent = nullptr) : QWidget(parent) { QVBoxLayout* vlayout = new QVBoxLayout(this); QPushButton* collapse = new QPushButton("Collapse"); QPushButton* close = new QPushButton("Close"); QHBoxLayout* hlayout = new QHBoxLayout; vlayout->addLayout(hlayout); hlayout->addWidget(collapse); hlayout->addWidget(close); QScrollArea* scrollArea = new QScrollArea; setMaximumWidth(300); vlayout->addWidget(scrollArea); QWidget *scrollContent = new QWidget(scrollArea); QVBoxLayout *scrollLayout = new QVBoxLayout(scrollContent); for (; i < 10; ++i) { QLabel *label = new QLabel(QString("Label %1").arg(i+1), scrollContent); label->setWordWrap(1); scrollLayout->addWidget(label); } scrollContent->setLayout(scrollLayout); scrollArea->setWidget(scrollContent); scrollArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); } }; class MainWindow : public QMainWindow { public: MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) { QWidget *centralWidget = new QWidget(this); QVBoxLayout *centralLayout = new QVBoxLayout(centralWidget); NotificationWidget *notificationWidget = new NotificationWidget(centralWidget); notificationWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); centralLayout->addWidget(notificationWidget); centralWidget->setLayout(centralLayout); this->setCentralWidget(centralWidget); } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow mainWindow; mainWindow.resize(800, 600); mainWindow.show(); return app.exec(); }
However, when adding the ability to add new labels like this (attached to collapse button for now):
NotificationWidget(QWidget *parent = nullptr) : QWidget(parent) { //... connect(collapse,&QPushButton::clicked,[this,scrollArea,scrollLayout,scrollContent](){ QLabel *label = new QLabel(QString("Label %1").arg(this->i+1), scrollContent); label->setWordWrap(1); scrollLayout->addWidget(label); (this->i)++; } ); }
This is what ends up happening which is the wrong behaviour:
But when trying approaches like addingscrollArea->setWidgetResizable(true);
it doesn't resize the whole scrollArea on adding a label meaning that the scroll bar appears:
And if I try to add something likescrollArea->adjustSize()
in the label adding function, it breaks the area entirely:
So, I currently still have no idea how to get it working correctly especially when allowing adding new labels to the scrollArea.
- Notification Widget: ↔ 300 fixed but no more than parent allows, ↕ as much as children need but no more than parent allows
-
Ok, after a lot of pain and reading up on source code of updateGeometry() in QWidget and activate()/update() in QLayout, and also tracking down the issue of a new label being not shown initially (which is really weird because in another project it was fine), I got the exact behaviour I want:
class NotificationScrollArea : public QScrollArea { public: explicit NotificationScrollArea(QWidget *parent = nullptr) : QScrollArea(parent){}; QSize sizeHint() const override { return widget()->sizeHint()+QSize(0,2); } }; class NotificationWidget : public QWidget { public: int i=0; NotificationWidget(QWidget *parent = nullptr) : QWidget(parent) { QVBoxLayout* vlayout = new QVBoxLayout(this); QPushButton* collapse = new QPushButton("Collapse"); QPushButton* close = new QPushButton("Close"); QHBoxLayout* hlayout = new QHBoxLayout; vlayout->addLayout(hlayout); hlayout->addWidget(collapse); hlayout->addWidget(close); hlayout->setAlignment(Qt::AlignRight); QScrollArea* scrollArea = new NotificationScrollArea; setMaximumWidth(300); vlayout->addWidget(scrollArea); QWidget *scrollContent = new QWidget(scrollArea); QVBoxLayout *scrollLayout = new QVBoxLayout(scrollContent); for (; i < 10; ++i) { QLabel *label = new QLabel(QString("Label %1").arg(i+1), scrollContent); label->setWordWrap(1); scrollLayout->addWidget(label); } scrollContent->setLayout(scrollLayout); scrollArea->setWidget(scrollContent); scrollArea->setWidgetResizable(true); scrollArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); connect(collapse,&QPushButton::clicked,[this,scrollArea,scrollLayout,scrollContent](){ i++; QLabel *label = new QLabel(QString("Label %1").arg(i), scrollContent); label->setWordWrap(1); scrollLayout->addWidget(label); label->show(); scrollContent->updateGeometry(); scrollArea->updateGeometry(); updateGeometry(); }); } }; //MainWindow and main code same as before
I'm pretty sure every single line of code here is necessary for it to function.
The three big issues were thatadjustsize()
seems to not work well withscrollArea
but replacing that withupdateGeometry()
fixed it;- it is necessary to override
sizeHint()
ofscrollArea
to depend on thescrollContent
'ssizeHint()
, otherwise thescrollArea
doesn't resize (second picture of previous post); - the label has to be
show
n, otherwise when manually updating the geometry of the entire ordeal it doesn't include the new label.
-