Qt Grid Layout doesn't fit into Scroll Area [duplicate] - c++

This question already has an answer here:
QScrollArea missing Scrollbar
(1 answer)
Closed 1 year ago.
I am trying to put a grid view that will contain some buttons into a scroll area but I don't know why the grid layout only fits into the initial size of the scroll area (it doesn't go downside so I could use the scroll bar to see all elements).
ui->scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
QGridLayout* lay=new QGridLayout(this);
QPushButton *name[100];
for(int i=0;i<10;i++){
QString str1="project"+QString::number(i);
QString str2="3/6task";
QString str3="ALEX";
name[i]=new QPushButton(str1);
name[i]->setObjectName("btn_1");
name[i]->setStyleSheet("QPushButton#btn_1{background:transparent;Text-align:left;font-family:century gothic;font-size:18px;color:red;}"
"QPushButton#btn_1:hover{color:yellow;Font-size:22px;}");
name[i]->setFixedSize(100,40);
lay->addWidget(name[i]);
}
ui->scrollArea->setLayout(lay);
The result is in the next photo:

I fixed it by this way:
ui->scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
QGridLayout *lay = new QGridLayout(ui->centralwidget);
lay->addWidget(ui->scrollArea, 0, 0, 1, 1);
auto gridLayout = new QGridLayout(ui->scrollAreaWidgetContents);
gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
auto widget = new QWidget(ui->scrollAreaWidgetContents);
widget->setObjectName(QString::fromUtf8("widget"));
auto gridLayout_2 = new QGridLayout(widget);
gridLayout_2->setObjectName(QString::fromUtf8("gridLayout_2"));
gridLayout->addWidget(widget, 0, 0, 1, 1);
for (int i = 0; i < 10; i++)
{
QString str1 = "project" + QString::number(i);
QString str2 = "3/6task";
QString str3 = "ALEX";
QPushButton *btn = new QPushButton(str1);
btn->setObjectName(QString("btn_%1").arg(i));
btn->setStyleSheet("QPushButton#btn_1{background:transparent;Text-align:left;font-family:century gothic;font-size:18px;color:red;}"
"QPushButton#btn_1:hover{color:yellow;Font-size:22px;}");
btn->setFixedSize(100, 40);
widget->layout()->addWidget(btn);
}
}

You have to use the following code:
This code has been tested.
QScrollArea* scroll = new QScrollArea(this);
scroll->setGeometry(50,0,250,300);
scroll->setWidgetResizable(true);
QWidget *container = new QWidget;
scroll->setWidget(container);
QGridLayout* lay = new QGridLayout(container);
QPushButton *name[100];
for(int i = 0; i < 20; ++i) {
name[i]=new QPushButton(scroll);
lay->addWidget(name[i]);
}
Of cource you can modify the button and the scrollArea as well.
There is no problem of using ui->scrollArea

Your applying the QGridLayout to the QScollArea when you actually need to apply it to a QWidget managed by the scroll area using QScrollArea::setWidget...
#include <QApplication>
#include <QGridLayout>
#include <QPushButton>
#include <QScrollArea>
#include <QString>
#include <QWidget>
int main (int argc, char **argv)
{
QApplication app(argc, argv);
QScrollArea scroll_area;
scroll_area.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
QWidget viewport;
QGridLayout layout(&viewport);
for (int i = 0; i < 10; ++i) {
QString str1 = "project" + QString::number(i);
QString str2 = "3/6task";
QString str3 = "ALEX";
auto *pb = new QPushButton(str1);
pb->setObjectName("btn_1");
pb->setStyleSheet("QPushButton#btn_1{background:transparent;Text-align:left;font-family:century gothic;font-size:18px;color:red;}"
"QPushButton#btn_1:hover{color:yellow;Font-size:22px;}");
pb->setFixedSize(100,40);
layout.addWidget(pb);
}
scroll_area.setWidget(&viewport);
scroll_area.show();
return app.exec();
}

Related

Weird behaviour of QWiget after running QPropertyAnimation

I'm running the next animation on two widgets:
QParallelAnimationGroup* animGroup = new QParallelAnimationGroup;
for (int i = 0; i < 2; ++i) {
//widgets is an array of 2 QLabels
QPropertyAnimation* anim = new QPropertyAnimation(widgets[i], "geometry");
anim->setDuration(750);
anim->setStartValue(widgets[i]->geometry());
widgets[i]->setProperty("animating", true);
qDebug() << QString("Animation Start %1: ").arg(widgets[i]->objectName()) << widgets[i]->pos() << " g " << widgets[i]->geometry();
//
anim->setEndValue(widgets[!i]->geometry());
anim->setEasingCurve(QEasingCurve::OutBack);
animGroup->addAnimation(anim);
}
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
So far so good, animation moves both widgets as expected, but as soon as I resize the window, those widgets get automatically moved back to their original position. The current layout holding this both widgets is a QHBoxLayout, and has the following items (same order):
QLabel0 | QSpacerItem0 | QToolTipButton | QSpacerItem1 | QLabel1
after the animation they become:
QLabel1 | QSpacerItem0 | QToolTipButton | QSpacerItem1 | QLabel0
But whenever I change the size of the window the position on the widgets resets to the original layout. I haven't implemented the resizeEvent from my window.
I'm using Windows 10 with Visual Studio | Qt Visual Studio Tools 2.3.1 and Qt 12.0, building with msvc2017_64 version and c++17 enabled.
Complete Test Code (New project):
SwapItemsTest.cpp
#include "SwapItemsTest.h"
#include <array>
#include <QHBoxLayout>
#include <QToolButton>
#include <QSpacerItem>
#include <QLabel>
#include <QDebug>
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>
#include <QTimer>
SwapItemsTest::SwapItemsTest(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
auto layout = new QHBoxLayout;
auto toolButton = new QToolButton;
auto left = new QLabel("Left");
left->setFixedSize(50, size().height());
left->setFrameShape(QFrame::Box);
left->setFrameShadow(QFrame::Plain);
left->setLineWidth(2);
left->setAlignment(Qt::AlignCenter);
auto right = new QLabel("Right");
right->setFixedSize(50, size().height());
right->setAlignment(Qt::AlignCenter);
right->setFrameShape(QFrame::Box);
right->setFrameShadow(QFrame::Plain);
right->setLineWidth(2);
layout->addWidget(left);
layout->addSpacerItem(new QSpacerItem(40, 40));
layout->addWidget(toolButton);
layout->addSpacerItem(new QSpacerItem(40, 40));
layout->addWidget(right);
ui.centralWidget->setLayout(layout);
auto widgets = std::array{left, right};
connect(toolButton, &QToolButton::clicked, [widgets]() {
QParallelAnimationGroup* animGroup = new QParallelAnimationGroup;
for (int i = 0; i < 2; ++i) {
//widgets is an array of 2 QLabels
QPropertyAnimation* anim = new QPropertyAnimation(widgets[i], "geometry");
anim->setDuration(750);
anim->setStartValue(widgets[i]->geometry());
widgets[i]->setProperty("animating", true);
anim->setEndValue(widgets[!i]->geometry());
anim->setEasingCurve(QEasingCurve::OutBack);
animGroup->addAnimation(anim);
}
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
});
}
SwapItemsTest.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_SwapItemsTest.h"
class SwapItemsTest : public QMainWindow
{
Q_OBJECT
public:
SwapItemsTest(QWidget *parent = Q_NULLPTR);
private:
Ui::SwapItemsTestClass ui;
};
The problem is that you place the widgets in a layout but then try to manage their geometry yourself. One workaround would be to update the layout once the animation group has finished by swapping the two widgets. Your code then becomes...
SwapItemsTest::SwapItemsTest(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
auto layout = new QHBoxLayout;
auto toolButton = new QToolButton;
auto left = new QLabel("Left");
left->setFixedSize(50, size().height());
left->setFrameShape(QFrame::Box);
left->setFrameShadow(QFrame::Plain);
left->setLineWidth(2);
left->setAlignment(Qt::AlignCenter);
auto right = new QLabel("Right");
right->setFixedSize(50, size().height());
right->setAlignment(Qt::AlignCenter);
right->setFrameShape(QFrame::Box);
right->setFrameShadow(QFrame::Plain);
right->setLineWidth(2);
layout->addWidget(left);
layout->addSpacerItem(new QSpacerItem(40, 40));
layout->addWidget(toolButton);
layout->addSpacerItem(new QSpacerItem(40, 40));
layout->addWidget(right);
ui.centralWidget->setLayout(layout);
auto widgets = std::array{left, right};
connect(toolButton, &QToolButton::clicked,
/*
* Note that layout has been added to the capture list for this lambda.
*/
[layout, widgets]()
{
QParallelAnimationGroup* animGroup = new QParallelAnimationGroup;
for (int i = 0; i < 2; ++i) {
//widgets is an array of 2 QLabels
QPropertyAnimation* anim = new QPropertyAnimation(widgets[i], "geometry");
anim->setDuration(750);
anim->setStartValue(widgets[i]->geometry());
widgets[i]->setProperty("animating", true);
anim->setEndValue(widgets[!i]->geometry());
anim->setEasingCurve(QEasingCurve::OutBack);
animGroup->addAnimation(anim);
}
/*
* This is the lambda that will be invoked when the
* QPropertyAnimation::finished signal is emitted. It simply
* swaps the positions of the two widgets in the layout.
*/
connect(animGroup, &QPropertyAnimation::finished,
[layout, widgets]()
{
auto index0 = layout->indexOf(widgets[0]);
auto index1 = layout->indexOf(widgets[1]);
if (index0 < index1) {
layout->takeAt(index1);
layout->takeAt(index0);
layout->insertWidget(index0, widgets[1]);
layout->insertWidget(index1, widgets[0]);
} else {
layout->takeAt(index0);
layout->takeAt(index1);
layout->insertWidget(index1, widgets[0]);
layout->insertWidget(index0, widgets[1]);
}
});
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
});
}
Note that you might still see some strange behaviour if the parent widget is resized during the animation.

QFormLayout expands column of QGridLayout

I am creating a QFormLayout with some items like this:
QFormLayout *tableLayout = new QFormLayout();
QLineEdit *line1 = new QLineEdit();
QLineEdit *line2 = new QLineEdit();
tableLayout->addRow(tr("LineText1 "), line1);
tableLayout->addRow(tr("LineText2 "), line2);
After that I try to add this Layout to a QGridLayout like this:
QGridLayout *layout = new QGridLayout();
QPushButton *btn1 = new QPushButton();
QPushButton *btn2 = new QPushButton();
layout->addWidget(btn, 1, 1, 3, 3);
layout->addWidget(btn2, 1, 4);
layout->addLayout(tableLayout, 2, 4);
After I added the tableLayout, btn1 as width as 1 column and the tableLayout is as width as 3 columns.
I already tried to put the QFormLayout into a own widget and add the widget to the QGridLayout. But it didn't changed anything. The way I am doing that is the following:
QFormLayout *tableLayout = new QFormLayout();
QLineEdit *line1 = new QLineEdit();
QLineEdit *line2 = new QLineEdit();
tableLayout->addRow(tr("LineText1 "), line1);
tableLayout->addRow(tr("LineText2 "), line2);
QWidget *widget = new QWidget();
widget->setLayout(tableLayout);
QGridLayout *layout = new QGridLayout();
QPushButton *btn1 = new QPushButton();
btn1->setText("btn1");
QPushButton *btn2 = new QPushButton();
btn2->setText("btn2");
layout->addWidget(btn1, 1, 1, 3, 3);
layout->addWidget(btn2, 1, 4);
layout->addWidget(widget, 2, 4);
What is the reason for this strange situation? And how to solve it?
Here is a picture of the result:
And here is wat I want to have:
To build the design you want the first thing is to establish the position of the elements, remember that the position of the rows or columns start at 0, not at 1 as you do. The second part is to set the size policies, some widgets already have some established policy such as the QPushButton that stretches horizontally but not vertically so even if the rowSpan is large it will not change the height of the button, so we must change that behavior and finally the stretch.
#include <QApplication>
#include <QFormLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QSizePolicy>
#include <QWidget>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QGridLayout *layout = new QGridLayout(&w);
QPushButton *btn1 = new QPushButton("Btn1");
btn1->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
QPushButton *btn2 = new QPushButton("Btn2");
btn2->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
QFormLayout *tableLayout = new QFormLayout();
QLineEdit *line1 = new QLineEdit();
QLineEdit *line2 = new QLineEdit();
tableLayout->addRow("LineText1 ", line1);
tableLayout->addRow("LineText2 ", line2);
layout->addWidget(btn1, 0, 0, 3, 3);
layout->addWidget(btn2, 0, 3);
layout->addLayout(tableLayout, 1, 3);
// column 0 x3
layout->setColumnStretch(0, 3);
// column 3 x1
layout->setColumnStretch(3, 1);
w.resize(640, 480);
w.show();
return a.exec();
}
Note that the QFormLayout will make the widgets always on top, so it will not necessarily occupy the height of the space offered by the QGridLayout.

Qt5 paintEvent not called inside QScrollArea

I'm having a bit of a problem with Qt. I'm trying to create a 2D drawing of cells, with QRect, by overloading paintEvent for a custom class which inherits QWidget and which is placed inside a QScrollArea. The problem is, paintEvent does not trigger at all (not on resize events, not when I call repaint() or update(), nor when I launch my program). Here is where I overload paintEvent, in GOL.cpp:
void GOL::paintEvent(QPaintEvent *) {
QPainter painter(this);
//painter.setPen(Qt::black);
int x1Rect = rectPaint.x();
int y1Rect = rectPaint.y();
int x2Rect = x1Rect + rectPaint.width();
int y2Rect = y1Rect + rectPaint.height();
int xCell;
int yCell = 0;
for (int i = 0; i < rows; i++) {
xCell = 0;
for (int j = 0; j < cols; j++) {
if (xCell <= x2Rect && yCell <= y2Rect && xCell + cellSize >= x1Rect &&
yCell + cellSize >= y1Rect) {
if (principalMatrix->get(i,j)) {
painter.fillRect(xCell, yCell, cellSize - 1, cellSize - 1, cellColourAlive);
}
else {
painter.fillRect(xCell, yCell, cellSize - 1, cellSize - 1, cellColourDead);
}
}
xCell += cellSize;
}
yCell += cellSize;
}
}
And my layout is as follows, in DisplayGame.cpp:
DisplayGame::DisplayGame(QWidget *parent, int threads_no, int generations, char* file_in, char* file_out) :
QWidget(parent) {
gol = new GOL(threads_no, generations, file_in, file_out);
QHBoxLayout *title = setupTitle();
QHBoxLayout *buttons = setupButtons();
QVBoxLayout *layout = new QVBoxLayout();
scrlArea = new QScrollArea;
scrlArea->setWidget(gol);
layout->addLayout(title);
layout->addWidget(scrlArea);
layout->addLayout(buttons);
setLayout(layout);
}
I honestly have no idea why it does not draw anything. Any ideas?
I've fixed it by modifying as follows:
DisplayGame::DisplayGame(QWidget *parent, int threads_no, int generations, char* file_in, char* file_out) :
QWidget(parent) {
gol = new GOL(this, threads_no, generations, file_in, file_out);
QSize *adjustSize = new QSize(gol->cellSize, gol->cellSize); //QSize object that is as big as my QRect matrix
adjustSize->setWidth(gol->cellSize * gol->rows);
adjustSize->setHeight(gol->cellSize * gol->cols);
gol->setMinimumSize(*adjustSize);
QVBoxLayout *layout = new QVBoxLayout;
QHBoxLayout *title = setupTitle();
layout->addLayout(title);
QHBoxLayout *buttons = setupButtons();
layout->addLayout(buttons);
QPalette pal(palette()); //Setting the background black, so the white spaces between QRect items cannot be seen (though I could have modified the margins?)
pal.setColor(QPalette::Background, Qt::black);
scrlArea = new QScrollArea(this);
scrlArea->setAutoFillBackground(true);
scrlArea->setPalette(pal);
scrlArea->setWidget(gol);
layout->addWidget(scrlArea);
setLayout(layout);
}
And I've left the paintEvent as it was. It was, ultimately, a size problem, as AlexanderVX said.

Creating and laying out widgets using a for loop in Qt

So i wanted to create 5 buttons in Qt but instead, create just one button and put it in a for loop so i don't have to create each of the 5 buttons manually. I tried different ways but all proved futile. I'm new to C++ and Qt.
Here are the codes;
show.h
#ifndef SHOW_H
#define SHOW_H
#include <QDialog>
#include <QPushButton>
#include <QVBoxLayout>
class Show : public QDialog {
Q_OBJECT
public:
explicit Show(QWidget *parent = 0);
~Show();
private:
QPushButton *button;
};
#endif // SHOW_H
show.cpp
#include "show.h"
#include "ui_show.h"
Show::Show(QWidget *parent) : QDialog(parent) {
int a = 5;
button = new QPushButton[a];
button->setText("Ok");
QVBoxLayout *layout = new QVBoxLayout[a];
for (int i = 0; i < sizeof(button)/4; i++) {
/*here, i wanted to do something like this;
'layout[i].addWidget(button[i]);' but didn't work*/
layout[i].addWidget(button);
}
setLayout(layout);
}
Show::~Show() {
}
main.cpp
#include "show.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
Show *dialog = new Show;
dialog->show();
return a.exec();
}
After i run the code, i only see one button.
Your help is deeply appreciated. Thank you!!
QPushButton* pButton = new QPushButton("Ok");
This creates a single instance of a QPushButton.
You can add the button to a layout with a call to Layout::addWidget, which internally calls addItem.
As the documentation states for addItem: -
Note: The ownership of item is transferred to the layout, and it's the layout's responsibility to delete it
So your current code creates a single button and as it gets added to each successive layout, it is removed from the layout in which it was previously added.
You're creating one button and adding the same button 5 times. If you want 5 buttons in 5 layouts using a loop, then you need 5 separate instances of the button: -
for (int i=0; i<5; ++i)
{
QPushButton* pButton = new QPushButton("Ok");
layout[i].addWidget(pButton);
}
In your show.cpp change the lines
Show::Show(QWidget *parent) : QDialog(parent) {
int a = 5;
// You only need one layout for all buttons, not one per button.
QVBoxLayout *layout = new QVBoxLayout( this );
for (int i = 0; i < a; i++) {
QPushButton * newButton = new QPushButton( this );
newButton ->setText( "Ok" );
layout->addWidget( newButton );
}
setLayout(layout);
}
Show::~Show() {
}
The layout expects a pointer and you only need one layout instead one per button.

QPushButton does not honor horizontal expanding size policy

I'm trying to put several QPushButton entities inside a QVBoxLayout such that they are centered and expanding. The expanding tag works fine until I tell the QVBoxLayout to use AlignHCenter, after which the QPushButton's all jump to the minimum size and stay there. What am I doing wrong?
QVBoxLayout *vBoxLayout = new QVBoxLayout(this);
setLayout(vBoxLayout);
//Create title and add to layout
QLabel *titleLabel = new QLabel(this);
titleLabel->setText(menuTitle);
titleLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
titleLabel->setMaximumHeight(35);
titleLabel->setStyleSheet(QString("QLabel { font-size: 16pt; }"));
vBoxLayout->addWidget(titleLabel);
vBoxLayout->setStretchFactor(titleLabel, 1);
//Create buttons and add to layout
QMap<int, QString>::const_iterator it;
for (it = m_buttonMapping.cbegin(); it != m_buttonMapping.cend(); ++it)
{
QPushButton *button = new QPushButton(it.value(), this);
connect(button, SIGNAL(clicked()), sigMapper, SLOT(map()));
sigMapper->setMapping(button, it.key());
button->setMinimumHeight(40);
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
button->setMaximumWidth(800);
button->setMinimumWidth(300);
vBoxLayout->addWidget(button);
vBoxLayout->setAlignment(button, Qt::AlignHCenter); //<-- without this, expanding works fine!
vBoxLayout->setStretchFactor(button, 1);
}
vBoxLayout->setContentsMargins(10, 0, 10, 0);
By specifying the alignment on the layout, you keep your QPushButtons from being able to expand. Available new space will be used to keep the QPushButtons centered, instead of allowing them to resize and for an amount of space around them to be utilized for centering. Stretch factors fulfill your requirement for a proportional resizing and centering of a layout's contents.
To get around this, create a wrapper widget and layout (or just a layout), and add the widget that is laid out by your vBoxLayout to the wrapper layout with a stretch factor applied. Before and after adding your widget, you'll add QSpacerItems to the wrapper layout with QHBoxLayout::addStretch. You can then adjust the stretch factors of your widget and the spacers to get the effect you want.
Here's some sample code that should solve your problem:
MainWindow.cpp
#include "MainWindow.hpp"
#include <QPushButton>
#include <QLabel>
#include <QBoxLayout>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent) {
QWidget* centralWidget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(centralWidget);
// Create a wrapper widget that will align horizontally
QWidget* alignHorizontalWrapper = new QWidget(centralWidget);
layout->addWidget(alignHorizontalWrapper);
// Layout for wrapper widget
QHBoxLayout* wrapperLayout = new QHBoxLayout(alignHorizontalWrapper);
// Set its contents margins to 0 so it won't interfere with your layout
wrapperLayout->setContentsMargins(0, 0, 0, 0);
wrapperLayout->addStretch(1);
QWidget* widget = new QWidget(alignHorizontalWrapper);
wrapperLayout->addWidget(widget, 3);
wrapperLayout->addStretch(1);
QVBoxLayout* vBoxLayout = new QVBoxLayout(widget);
QLabel* titleLabel = new QLabel(this);
titleLabel->setText(QStringLiteral("Menu"));
titleLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
titleLabel->setMaximumHeight(35);
titleLabel->setStyleSheet(QStringLiteral("QLabel { font-size: 16pt; }"));
vBoxLayout->addWidget(titleLabel);
vBoxLayout->setStretchFactor(titleLabel, 1);
for (int i = 0; i < 3; ++i) {
const QString& value = QStringLiteral("Button ") + QString::number(i);
QPushButton* button = new QPushButton(value, this);
button->setMinimumHeight(40);
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
//button->setMaximumWidth(800);
button->setMinimumWidth(300);
vBoxLayout->addWidget(button);
//vBoxLayout->setAlignment(button, Qt::AlignHCenter); // without this, expanding works fine!
vBoxLayout->setStretchFactor(button, 3);
}
vBoxLayout->setContentsMargins(10, 0, 10, 0);
this->setCentralWidget(centralWidget);
}
MainWindow.hpp
#ifndef MAINWINDOW_HPP
#define MAINWINDOW_HPP
#include <QMainWindow>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
};
#endif // MAINWINDOW_HPP
main.cpp
#include "MainWindow.hpp"
#include <QApplication>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}