QScrollArea with dynamically changing contents
I have a QScrollArea with some buttons in it, like shown on the picture.
The idea of the layout is: 1. The left and right button should be used for scrolling the buttons when they are too wide
2.The numbers of buttons in the scroll area can be changed dynamically 3. Any free space should be used to expand the scroll area as much as possible. If no such space exist navigation buttons should be used for scrolling.
With my current implementation when i increase the buttons i have this:
But there is free space on the right, so this should look like:
If i increase once more to 10 for example, then scrollbar should appear( because the layout is constained by the widget ).
I want to know if there is any other way aside from manual resizing of the widgets( because ui can be translated and buttons can change size hint also the real design is more complicated :(
Here is my implementation of the ScrollAreaTest widget:
#include "MainWidget.h"
#include <QLineEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QScrollArea>
#include <QPushButton>
#include <QDebug>
#include "ButtonWidget.h"
#include "CheckableButtonGroup.h"
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent),
m_scrollArea( 0 ),
m_lineEdit( 0 ),
m_buttons( 0 )
{
QVBoxLayout* mainLayout = new QVBoxLayout( this );
QWidget* firstRow = new QWidget;
QHBoxLayout* firstRowLayout = new QHBoxLayout( firstRow );
QPushButton* left = new QPushButton;
QPushButton* right = new QPushButton;
m_buttons = new CheckableButtonGroup( Qt::Horizontal );
m_buttons->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
m_buttons->setButtonsCount( 5 );
m_buttons->setStyleSheet( "border: none" );
QWidget* const buttonsContainer = new QWidget;
QHBoxLayout* const buttonsContainerLayout = new QHBoxLayout( buttonsContainer );
buttonsContainerLayout->setSpacing( 0 );
buttonsContainerLayout->setSizeConstraint( QLayout::SetMinAndMaxSize );
buttonsContainerLayout->setMargin( 0 );
buttonsContainerLayout->addWidget( m_buttons, 0, Qt::AlignLeft );
qDebug() << m_buttons->buttons()[ 0 ]->size();
m_scrollArea = new QScrollArea;
m_scrollArea->setContentsMargins( 0, 0, 0, 0 );
m_scrollArea->setWidget( buttonsContainer );
m_scrollArea->setWidgetResizable( true );
m_scrollArea->setStyleSheet( "border: 1px solid blue" );
m_scrollArea->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
firstRowLayout->addWidget( left , 0, Qt::AlignLeft );
firstRowLayout->addWidget( m_scrollArea, 1, Qt::AlignLeft );
firstRowLayout->addWidget( right , 0, Qt::AlignLeft );
m_lineEdit = new QLineEdit;
QPushButton* button = new QPushButton;
QHBoxLayout* secondRowLayout = new QHBoxLayout;
secondRowLayout->addWidget( m_lineEdit );
secondRowLayout->addWidget( button );
connect( button, SIGNAL(clicked()), SLOT(setButtonsCount()) );
mainLayout->addWidget( firstRow, 1, Qt::AlignLeft );
mainLayout->addLayout( secondRowLayout );
button->setText( "Set buttons count" );
buttonsContainer->resize( m_buttons->buttonsOptimalWidth(), buttonsContainer->height() );
m_buttons->resize( m_buttons->buttonsOptimalWidth(), m_buttons->height() );
//area->resize( 100, area->height() );
//area->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
}
MainWidget::~MainWidget()
{
}
void MainWidget::setButtonsCount()
{
m_buttons->setButtonsCount( m_lineEdit->text().toInt() );
}
And here is the whole Qt project containing the problem: https://drive.google.com/file/d/0B-mc4aKkzWlxQzlPMEVuNVNKQjg/edit?usp=sharing
The essential steps are:
The container widget that holds the buttons (your
CheckableButtonGroup
) must have aQLayout::SetMinAndMaxSize
size constraint set. Then it will be exactly large enough to hold the buttons. Its size policy doesn't matter, since you're simply putting it into aQScrollArea
, not into another layout.The scroll area needs to set its maximum size according to the size of the widget it holds. The default implementation doesn't do it, so one has to implement it by spying on resize events of the embedded widget.
The code below is a minimal example that works under both Qt 4.8 and 5.2.
// https://github.com/KubaO/stackoverflown/tree/master/questions/scrollgrow-21253755
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
class ButtonGroup : public QWidget {
Q_OBJECT
QHBoxLayout m_layout{this};
public:
ButtonGroup(QWidget * parent = 0) : QWidget{parent} {
m_layout.setSizeConstraint(QLayout::SetMinAndMaxSize); // <<< Essential
}
Q_SLOT void addButton() {
auto n = m_layout.count();
m_layout.addWidget(new QPushButton{QString{"Btn #%1"}.arg(n+1)});
}
};
class AdjustingScrollArea : public QScrollArea {
bool eventFilter(QObject * obj, QEvent * ev) {
if (obj == widget() && ev->type() == QEvent::Resize) {
// Essential vvv
setMaximumWidth(width() - viewport()->width() + widget()->width());
}
return QScrollArea::eventFilter(obj, ev);
}
public:
AdjustingScrollArea(QWidget * parent = 0) : QScrollArea{parent} {}
void setWidget(QWidget *w) {
QScrollArea::setWidget(w);
// It happens that QScrollArea already filters widget events,
// but that's an implementation detail that we shouldn't rely on.
w->installEventFilter(this);
}
};
class Window : public QWidget {
QGridLayout m_layout{this};
QLabel m_left{">>"};
AdjustingScrollArea m_area;
QLabel m_right{"<<"};
QPushButton m_add{"Add a widget"};
ButtonGroup m_group;
public:
Window() {
m_layout.addWidget(&m_left, 0, 0);
m_left.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
m_left.setStyleSheet("border: 1px solid green");
m_layout.addWidget(&m_area, 0, 1);
m_area.setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
m_area.setStyleSheet("QScrollArea { border: 1px solid blue }");
m_area.setWidget(&m_group);
m_layout.setColumnStretch(1, 1);
m_layout.addWidget(&m_right, 0, 2);
m_right.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
m_right.setStyleSheet("border: 1px solid green");
m_layout.addWidget(&m_add, 1, 0, 1, 3);
connect(&m_add, SIGNAL(clicked()), &m_group, SLOT(addButton()));
}
};
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
Window w;
w.show();
return a.exec();
}
#include "main.moc"