Search code examples
c++qtqt5qscrollareaqlayout

QScrollArea with dynamically added widget


I have some problem placing custom widget in QScrollArea. My custom widget contains 4 labels in QGridLayout filling it. Now I want to keep this widget in QScrollArea and be able to add more labels to it but I want only 4 of them to be shown in viewport.

So that's how widget with 4 labels looks like in QScrollArea: enter image description here

And here's the widget in QScrollArea after adding two more labels, where the red rectangle is the viewport. enter image description here

How can I achieve such result?

===================================

UPDATE

I eventually resolved my issue with the following code. It may need some minor spacing fixes.

#include "QtGuiApplication2.h"
#include "qscrollarea.h"
#include "CustomWidget.h"

QtGuiApplication2::QtGuiApplication2(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);

QScrollArea * qScrollArea = new QScrollArea();

CustomWidget * customWidget = new CustomWidget(this);
qScrollArea->setWidget(customWidget);
qScrollArea->setWidgetResizable(true);
qScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

ui.mainLayout->addWidget(qScrollArea, 1, 1, 1, 1);
}

CustomWidget class:

#include "CustomWidget.h"

#include "qlabel.h"

CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{
labelsNum = 4;
rows = 2;
layout = new QGridLayout();
this->setLayout(layout);
QMargins * margin = new QMargins(10, 10, 10, 10);
layout->setContentsMargins(*margin);
layout->setHorizontalSpacing(5);
layout->setVerticalSpacing(5);
initLabels();
addLabels();
}

CustomWidget::~CustomWidget()
{
}

void CustomWidget::initLabels()
{
int cols = labelsNum / rows;

for (int i = 0; i < labelsNum; i++)
{
    CustomLabel * label = new CustomLabel(this);
    label->setText(QString::number(i));
    label->setFrameShape(QFrame::Box);

    labels.append(label);
}

for (int i = 0; i < rows; i++)
{
    for (int j = 0; j < cols; j++)
    {
        layout->addWidget(labels.at(i * cols + j), i + 1, j + 1, 1, 1);
    }
}
}

void CustomWidget::addLabels()
{

int numLabels = labels.size();

for (int i = 0; i < 2; i++)
{
    CustomLabel * label = new CustomLabel(this);
    label->setText(QString::number(numLabels + i));
    label->setFrameShape(QFrame::Box);

    labels.append(label);
}

labelsNum += rows;
int cols = labelsNum / rows;

for (int i = 0; i < rows; i++)
{
    for (int j = 0; j < cols; j++)
    {
        layout->addWidget(labels.at(i * cols + j), i + 1, j + 1, 1, 1);
    }
}
}

void CustomWidget::resizeEvent(QResizeEvent * e)
{
QWidget::resizeEvent(e);
QSize size = viewportSize;

//Substract all the spacing from view size
int horizontalSpacing = ((4 / rows) - 1) * layout->horizontalSpacing();
int verticalSpacing = (rows - 1) * layout->verticalSpacing();
size -= QSize(layout->margin() * 2 + horizontalSpacing, layout->margin()      * 2 + verticalSpacing);

size *= 0.5;
for (int i = 0; i < labels.size(); i++)
{
    labels.at(i)->resizeEvent(e, size);
}
} 

And custom label:

#include "CustomLabel.h"

CustomLabel::CustomLabel(QWidget *parent): QLabel(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setMinimumSize(QSize(50, 50));
}

CustomLabel::~CustomLabel()
{
}

void CustomLabel::resizeEvent(QResizeEvent * e, QSize size)
{
this->setFixedSize(size);
}

Solution

  • To get the behavior you want you must set the sizes, for this I have created the following class that inherits from QScrollArea and internally has a QGridLayout. to this class you must establish the number of visible rows and columns and the fixed size of the widget, in your case the QLabel.

    #ifndef HORIZONTALSCROLLAREA_H
    #define HORIZONTALSCROLLAREA_H
    
    #include <QGridLayout>
    #include <QResizeEvent>
    #include <QScrollArea>
    #include <QScrollBar>
    
    class HorizontalScrollArea : public QScrollArea
    {
        QWidget *contentWidget;
        QGridLayout *grid;
        int nRows;
        int nColumns;
    public:
        HorizontalScrollArea(int rows, int cols, QWidget *parent = Q_NULLPTR)
            :QScrollArea(parent), nRows(rows), nColumns(cols)
        {
            setWidgetResizable(true);
            contentWidget = new QWidget(this);
            setWidget(contentWidget);
            grid = new QGridLayout(contentWidget);
            setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        }
    
        void addWidget(QWidget *w, int row, int col){
            grid->addWidget(w, row, col);
            adaptSize();
        }
    
        int columnCount() const{
            if(grid->count() == 0){
                return 0;
            }
            return grid->columnCount();
        }
    
    private:
        void adaptSize(){
            if(columnCount() >= nColumns ){
                int w = 1.0*(width() - grid->horizontalSpacing()*(nColumns+1.6))/nColumns;
                int wCorrected = w*columnCount() + grid->horizontalSpacing()*(columnCount()+2);
                contentWidget->setFixedWidth(wCorrected);
            }
            contentWidget->setFixedHeight(viewport()->height());
        }
    protected:
        void resizeEvent(QResizeEvent *event){
            QScrollArea::resizeEvent(event);
            adaptSize();
        }
    };
    
    #endif // HORIZONTALSCROLLAREA_H
    

    The following part is an example:

    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        QWidget w;
    
        w.setLayout(new QVBoxLayout);
    
        QPushButton btn("Add", &w);
    
        int nrows = 2;
        int ncols = 2;
    
        HorizontalScrollArea scroll(nrows, ncols);
    
        w.layout()->addWidget(&btn);
        w.layout()->addWidget(&scroll);
    
        QObject::connect(&btn, &QPushButton::clicked, [&scroll, &nrows](){
            int column = scroll.columnCount();
            for(int row=0; row < nrows; row++){
                QLabel *label = new QLabel(QString("label: %1 %2")
                                           .arg(row)
                                           .arg(column));
                label->setFrameShape(QFrame::Box);
                label->setAlignment(Qt::AlignCenter);
                QColor color(qrand() % 256, qrand() % 256, qrand() % 256);
                label->setStyleSheet(QString("QLabel { background-color : %1;}")
                                     .arg(color.name()));
                scroll.addWidget(label, row, column);
            }
        });
        w.show();
        return a.exec();
    }
    

    In the following link is the complete example.

    Output:

    enter image description here