Qscrollarea with Dynamically Changing Contents

QScrollArea with dynamically changing contents

The essential steps are:

  1. The container widget that holds the buttons (your CheckableButtonGroup) must have a QLayout::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 a QScrollArea, not into another layout.

  2. 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.

Screenshot with two buttons

Screenshot with multiple buttons

// 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"

QScrollArea with dynamically resizing subWidgets

You should use setFixedHeight() instead of resize() to set table height. Also you should addStretch() to scrollAreaWidgetLayout after all its items.

QScrollArea with dynamically added widget

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:

Sample Image

How to update QScrollArea size when contents change size?

You can set maximum height for scrollarea equals to widget contents (layout size + margins). This should be done after layout completes it's calculations (for example asyncronously with zero-timer).

import sys
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton, QScrollArea, QVBoxLayout

class MyWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)

scroll = QScrollArea(self)
scroll.setWidgetResizable(True)

# Contents
w = QWidget()
lay = QVBoxLayout(w)
#lay.addWidget(QPushButton('Button'))
scroll.setWidget(w)

def updateSize():
left, top, right, bottom = lay.getContentsMargins()
hint = lay.sizeHint()
scroll.setMaximumHeight(hint.height() + top + bottom + 1)

def addButton():
lay.addWidget(QPushButton('Button'))
QTimer.singleShot(0, updateSize)

def removeButton():
if lay.count() > 0:
lay.takeAt(0).widget().deleteLater()
QTimer.singleShot(0, updateSize)

addButton()

# Controls
button_add = QPushButton('Add')
button_add.clicked.connect(addButton)
button_del = QPushButton('Del')
button_del.clicked.connect(removeButton)

# Main Layout
vlay = QVBoxLayout()
vlay.addWidget(scroll)
vlay.addStretch(1)
vlay.addWidget(button_add)
vlay.addWidget(button_del)
vlay.setStretch(0,1000)

w = QWidget(self)
w.setLayout(vlay)
self.setCentralWidget(w)
self.resize(200, 300)



if __name__ == '__main__':

app = QApplication([])
mainWin = MyWindow()
mainWin.show()
app.exec()

How to dynamically add a QScrollArea to a StackedWidget page and add widgets to it?

You have to set a widget to your scroll area and add the contents to that widget:

QScrollArea *scrollArea = new QScrollArea;
QWidget *scrollWidget = new QWidget;
scrollWidget->setLayout(new QVBoxLayout);
scrollArea->setWidget(scrollWidget);
for (int j=0; j<100; j++)
{
scrollWidget->layout()->addWidget(new PaIndicator(0, "This is a test", 0)); // my custom widgets
}

Then add your scroll area to your stacked widget's page:

valuesLayout->addWidget(scrollArea);

QScrollArea and QVBoxLayout issues with dynamically added widgets

It seems like I also have to remove the QLayoutItem AND the Widget, just removing the widget or the QLayoutItem (which I did try) doesn't work, both have to be removed, like so:

    QLayoutItem *child = ui->verticalLayout->takeAt(0);
if (child)
{
delete child->widget();
delete child;
}

Qt: Resize QScrollArea to show 4 widgets at most

Instead of setting fixed height, you should override sizeHint() to return the size your widget would like to be (so that a layout could give it some extra, or a bit less, if it needs to). You will want to call invalidate() whenever any of those first 4 child widgets are changed, to tell the containing layout that any previous cached values should be recomputed.

And if your widgets are all in a QVBoxLayout (I'm guessing, but seems reasonable to assume), then you should be retrieving its spacing and top margin in your computation.

PyQt5 QScrollArea widget with dynamically created GroupBoxes

You have to set the scrollArea to MyScrollWidget using a layout.

from PyQt5 import QtWidgets
import sys

class MyScrollWidget(QtWidgets.QWidget):

def __init__(self):
super(MyScrollWidget, self).__init__()
lay = QtWidgets.QVBoxLayout(self)

scrollArea = QtWidgets.QScrollArea()
lay.addWidget(scrollArea)
top_widget = QtWidgets.QWidget()
top_layout = QtWidgets.QVBoxLayout()

for i in range(10):
group_box = QtWidgets.QGroupBox()

group_box.setTitle('GroupBox For Item {0}'.format(i))

layout = QtWidgets.QHBoxLayout(group_box)

label = QtWidgets.QLabel()
label.setText('Label For Item {0}'.format(i))
layout.addWidget(label)

push_button = QtWidgets.QPushButton(group_box)
push_button.setText('Run Button')
push_button.setFixedSize(100, 32)
layout.addWidget(push_button)

top_layout.addWidget(group_box)

top_widget.setLayout(top_layout)
scrollArea.setWidget(top_widget)
self.resize(200, 500)

if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
widget = MyScrollWidget()
widget.show()
sys.exit(app.exec_())

How do I go about creating a dynamic grid QScrollarea in PyQt5?

My issue is now resolved. While flow layout was useful, it was much more intuitive to simply use QListWidget. Many thanks to both eyllanesc and musicamante for their help.

For anyone who has the same issue, the desired result can be achieved by simply creating a QlistWidget and adding items to it:

size = QSize(100,100)
self.listWidget = QListWidget()
size = QtCore.QSize(80,80)
self.listWidget.setResizeMode(QListView.Adjust)
self.listWidget.setIconSize(size)
self.listWidget.itemDoubleClicked.connect(self.itemClicked)
self.listWidget.setViewMode(QListView.IconMode)


Related Topics



Leave a reply



Submit