Qt arranging ltems in layout - c++

For the last couple of days i am trying to solve a particular problem with the Qt Layout system. I'll try to generalize it:
I have a widget with two rows. On the first row there are tree buttons( or whatever other controls ). Their layout is shown on the picture:
The center button is taking all the extra space available( and is expanding as the widget increases its size ). The thing is that i want to programmatically want to resize the center widget( button ) and maintain the layout. With my current implementation when i resize the center button this is what happens:
I want the right button to be aligned on the right of the center button( without the extra space ). Now when i resize the widget it is going back to position 1 ( center takes all the extra space ), but this is not the effect i want.
Here is my current implementation:
#include "Widget_Old.h"
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QDebug>
WidgetOld::WidgetOld(QWidget *parent) :
QWidget(parent)
{
QVBoxLayout* mainLayout = new QVBoxLayout( this );
QWidget* firstRowWidget = new QWidget( this );
QPushButton* left = new QPushButton;
left->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
left->setText( "left" );
m_center = new QPushButton;
m_center->setText( "centerr");
m_center->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
QPushButton* right = new QPushButton;
right->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
right->setText( "right" );
QHBoxLayout* firstRowLayout = new QHBoxLayout( firstRowWidget );
firstRowLayout->addWidget( left );
firstRowLayout->addWidget( m_center );
firstRowLayout->addWidget( right );
QHBoxLayout* secondRowLayout = new QHBoxLayout;
QPushButton* button = new QPushButton( "resize" );
connect( button, SIGNAL(clicked()), SLOT(decrement()) );
secondRowLayout->addWidget( button );
mainLayout->addWidget( firstRowWidget );
mainLayout->addLayout( secondRowLayout );
}
WidgetOld::~WidgetOld()
{
}
void WidgetOld::decrement()
{
qDebug() << "Changing width from " << m_center->width() << " to " << m_center->width()/2;
m_center->resize( m_center->width()/2, m_center->height() );
}
Notes:
I've tried aligning the center, and right widget Qt::AlignLeft, but no result. Actually when
aligning widget left in a layout it tries to take the minimal size which breaks the expanding functionality:(
I've shared my code if any reference is needed:
https://drive.google.com/file/d/0B-mc4aKkzWlxTWdNNmpuQ0ptQ3M/edit?usp=sharing
Thanks for reading my post, hope you know the solution:)

You have to set the maximum size of your central widget. For the right widget to adapt, set its size policy to MinimumExpanding when you resize the central one:
void WidgetOld::decrement ()
{
qDebug() << "Changing width from " << m_center->width() << " to " << m_center->width()/2;
m_center->setMaximumWidth (m_center->width()/2);
m_right->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
}
Alternatively, if you resize the central widget manually (which is deprecated, as the above commenter noted), you must also update the geometry of the right one manually. Then your decrement method should look like
void WidgetOld::decrement ()
{
qDebug() << "Changing width from " << m_center->width() << " to " << m_center->width()/2;
m_center->resize( m_center->width()/2, m_center->height() );
int rightPosX = m_center->pos().x() + m_center->width() + m_firstRowLayout->spacing ();
int rightWidth = centralWidget()->pos().x() + centralWidget()->width() - m_mainLayout->margin() - rightPosX;
m_right->setGeometry (rightPosX, m_right->pos().y(), rightWidth, m_right->height());
}
In this case, you should also update your custom sizes on resize, as the layout will try to restore default proportions.

when you use layouts you move responsibility of managing the size to a layout, so you should NEVER resize items manually when they are in layout.
try this:
WidgetOld::WidgetOld(QWidget *parent) :
QWidget(parent)
{
QVBoxLayout* mainLayout = new QVBoxLayout( this );
QWidget* firstRowWidget = new QWidget( this );
QPushButton* left = new QPushButton;
left->setText( "left" );
left->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
m_center = new QPushButton;
m_center->setText( "centerr");
m_center->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
QPushButton* right = new QPushButton;
right->setText( "right" );
right->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
QHBoxLayout* firstRowLayout = new QHBoxLayout( firstRowWidget );
firstRowLayout->addWidget( left );
firstRowLayout->addStretch();
firstRowLayout->addWidget( m_center );
firstRowLayout->addStretch();
firstRowLayout->addWidget( right );
QHBoxLayout* secondRowLayout = new QHBoxLayout;
QPushButton* button = new QPushButton( "resize" );
connect( button, SIGNAL(clicked()), SLOT(decrement()) );
secondRowLayout->addWidget( button );
mainLayout->addWidget( firstRowWidget );
mainLayout->addLayout( secondRowLayout );
}

I don't know if it's a bad idea.
Could you make your firstRowLayout global and then dynamically add a stretch to the layout in the end in your decrement() SLOT function?
void WidgetOld::decrement(){
qDebug() << "Changing width from " << m_center->width() << " to " << m_center->width()/2;
m_center->resize( m_center->width()/2, m_center->height() );
firstRowLayout->addStretch(1);
}
this way the stretch will push all your buttons to the left

Related

How can I receive the widget resizing signal?

the resizeEvent function does not work when I define the window with the size of the window, please help.
I tested this function with Main Window and UI Form and answered, but did not work in my code.
Thanks.
main.cpp
Keypad *keypad;
keypad = new Keypad();
keypad->Widget_Keypad->show();
Keypad.cpp
#include "keypad.h"
Keypad::Keypad()
{
CreateUi();
}
void Keypad::resizeEvent(QResizeEvent *e)
{
qDebug() << e->size().width();
}
void Keypad::CreateUi()
{
Widget_Keypad = new QWidget;
Vertical_Layout = new QVBoxLayout;
Hlayout_ShowRange = new QHBoxLayout;
lbl_ShowRange = new QLabel;
lbl_ShowRange->setText( "0 ~ 999" );
Hlayout_ShowRange->addWidget( lbl_ShowRange );
Vertical_Layout->addLayout( Hlayout_ShowRange );
Widget_Keypad->setLayout( Vertical_Layout );
}

How to compose buttons in QScrollArea?

I'm creating dynamically unknown number of buttons and put them to QScrollArea.
someDialogButton = new QPushButton();
usersDialogs.push_back(someDialogButton);
ui->usersArea->setWidget(someDialogButton);
usersDialogs is QVector<QPushButton*>
usersArea is QScrollArea
But the buttons overlap and can be seen only the last button added. I tried use: setLayout(QLayout* layout) method, but it was useless.
How I can compose the buttons to make all buttons visible?
The Qt documentation says
void QScrollArea::setWidget(QWidget * widget)
Sets the scroll area's widget.
The widget becomes a child of the scroll area, and will be destroyed
when the scroll area is deleted or when a new widget is set.
Therefore, whenever you call QScrollArea::setWidget() you overwrite the previous widget. What you need to do is to fill a QWidget with the buttons and then you call QScrollArea::setWidget() on that container widget.
Here's an example, of what your classes constructor could look like:
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
{
this->resize( 300, 200 );
auto * layout = new QVBoxLayout(this);
auto * scrollArea = new QScrollArea(this);
scrollArea->setWidgetResizable( true );
layout->addWidget( scrollArea );
auto * container = new QWidget();
scrollArea->setWidget( container );
layout = new QVBoxLayout(container);
auto * button1 = new QPushButton( "1", container);
auto * button2 = new QPushButton( "2", container);
auto * button3 = new QPushButton( "3", container);
layout->addWidget( button1 );
layout->addWidget( button2 );
layout->addWidget( button3 );
}
For me it gives this:

Having a QScrollArea dynamically fit its parent but does not expand for its children

I seem to be having an issue with getting the behavior I want from a QScrollArea. As it stands, whenever I add something to the layout of the widget set as the target of the scroll area it will rather opt to expand the entire window rather than fit to scroll.
Here's my current setup:
QSplitter * mainArea = new QSplitter( Qt::Vertical );
QWidget * containment = new QWidget;
containment->setLayout( new QVBoxLayout );
currentStructures = new QWidget;
currentStructures->setLayout( new QVBoxLayout );
currentStructures->layout()->setAlignment( Qt::AlignTop );
QScrollArea * scroll = new QScrollArea();
scroll->setWidget( currentStructures );
containment->layout()->addWidget( currentStructures );
mainArea->addWidget( containment );
mainArea->addWidget( new QWidget ); //TODO: create preview bar
this->layout()->addWidget( mainArea );
This makes it so that the scroll area only ever expands and never shows the scroll bars.
By inserting this line:
containment->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Ignored );
I can get the area to ignore the size of its children but it also doesn't take up the space needed nor does it show scroll bars -- it just crunches the widget inside of it.
I'm a bit of a greenhorn to using Qt, but I was wondering how I would achieve the behavior that I need: I would like the scroll area to greedily take up the area it has available from its parent layout but not expand the the containing layouts vertically when adding but instead actually shows scroll bars. I'm planning on allowing a lot of resizing, so it needs to actually scale to the parent instead of just being a fixed size. I am at a loss as to how I should proceed in solving this aspect of my GUI. Thank you for your time.
If you would like to tackle this problem visually, here's a harness that you can use. I greatly appreciate your help.
#include <QtWidgets\qapplication.h>
#include <QtWidgets\qsplitter.h>
#include <QtWidgets\qlayout.h>
#include <QtWidgets\qscrollarea.h>
#include <QtWidgets\qpushbutton.h>
#include <QtWidgets\qlabel.h>
#include <QtWidgets\qsizepolicy.h>
int main( int argc, char * argv[] )
{
QApplication app( argc, argv );
QWidget * testWidget = new QWidget;
testWidget->setLayout( new QVBoxLayout );
//////////////////CODE IN QUESTIION//////////////////////
QSplitter * mainArea = new QSplitter( Qt::Vertical );
QWidget * containment = new QWidget;
containment->setLayout( new QVBoxLayout );
//containment->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Ignored );
QWidget * currentStructures = new QWidget;
currentStructures->setLayout( new QVBoxLayout );
currentStructures->layout()->setAlignment( Qt::AlignTop );
QScrollArea * scroll = new QScrollArea();
scroll->setWidget( currentStructures );
containment->layout()->addWidget( currentStructures );
mainArea->addWidget( containment );
///////////////////////////////////////////////////////////
QPushButton * pushIntoLayout = new QPushButton( "Add Element to Widget" );
QWidget::connect( pushIntoLayout, &QPushButton::clicked, [currentStructures](){ currentStructures->layout()->addWidget( new QLabel( "A generated label" ) ); } );
mainArea->addWidget( pushIntoLayout );
currentStructures->setStyleSheet(
"QWidget {"
"background-color: #FAA;"
"}"
);
testWidget->layout()->addWidget( mainArea );
testWidget->show();
return app.exec();
}
Even after copying and pasting my code to make a test harness I had not noticed my grievous error. I had mistakenly pushed the currentStructures widget to the container's layout rather than scroll, the scrolling area after giving it its child.
An excerpt from the Qt docs on void QScrollArea::setWidget( QWidget * widget ) for those unfamiliar:
The widget becomes a child of the scroll area, and will be destroyed when the scroll area is deleted or when a new widget is set.
Thanks to everyone who looked it over.
For those wondering, the fixed code would look like the following:
QSplitter * mainArea = new QSplitter( Qt::Vertical );
QWidget * containment = new QWidget;
containment->setLayout( new QVBoxLayout );
QWidget * currentStructures = new QWidget;
currentStructures->setLayout( new QVBoxLayout );
currentStructures->layout()->setAlignment( Qt::AlignTop );
QScrollArea * scroll = new QScrollArea();
scroll->setWidget( currentStructures );
scroll->setWidgetResizable( true );
containment->layout()->addWidget( scroll );
mainArea->addWidget( containment );
mainArea->addWidget( new QWidget ); //TODO: create preview bar
this->layout()->addWidget( mainArea );
Cheers!

QScrollArea with dynamically resizing subWidgets

So I have something like the following layout in my Qt Application.
QScroll Area
- QSrollArea's InternalWidget
-QVBoxLayout
-Layout 1
- some items
- QTableView
-Layout 2
- some items
- QTableView
The contents of the QTableViews is changing dynamically, and what i want is that each table view to be as large as it has to be( without progressbars and without empty space ). I have written a function to calculate the appropriate size of a table. The problem is that when i dynamically resize one of the TableViews it goes behind the second view( and what should happen is that the whole second layout be moved bellow the first ). Furthermore when shrink the table view there is an empty space left between it and the second layout.
Here is the code when i arrange the widgets:
#include "Widget.h"
#include <QVBoxLayout>
#include <QLabel>
#include <QTableView>
#include <QStringBuilder>
#include <QHeaderView>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent),
m_tableView1( 0 ),
m_tableView2( 0 ),
m_model1( 0 ),
m_model2( 0 ),
m_numberOfRowsEdit( 0 )
{
this->resize( 300, 520 );
QVBoxLayout* mainLayout = new QVBoxLayout( this );
QScrollArea* mainArea = new QScrollArea();
//mainArea->setWidgetResizable( true );
QWidget* scrollAreaWidget = new QWidget;
scrollAreaWidget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
QVBoxLayout* scrollAreaWidgetLayout = new QVBoxLayout( scrollAreaWidget );
scrollAreaWidgetLayout->setSizeConstraint( QLayout::SetMinAndMaxSize );
QVBoxLayout* firstSubLayout = new QVBoxLayout;
QLabel* label = new QLabel( "Label 1" );
m_tableView1 = new QTableView;
firstSubLayout->addWidget( label );
firstSubLayout->addWidget( m_tableView1 );
scrollAreaWidgetLayout->addLayout( firstSubLayout );
QVBoxLayout* secondSubLayout = new QVBoxLayout;
QLabel* label2 = new QLabel( "Label 2" );
m_tableView2 = new QTableView;
secondSubLayout->addWidget( label2 );
secondSubLayout->addWidget( m_tableView2 );
scrollAreaWidgetLayout->addLayout( secondSubLayout );
mainArea->setWidget( scrollAreaWidget );
mainLayout->addWidget( mainArea );
// Utility for dynamically changing rows
QHBoxLayout* hLayout = new QHBoxLayout;
QLabel* numberOfRowsLabel = new QLabel( "Number of rows" );
m_numberOfRowsEdit = new QLineEdit;
QPushButton* numberOfRowsButton = new QPushButton( "Apply" );
connect( numberOfRowsButton, SIGNAL(clicked()), SLOT(onApplyButtonPressed()) );
hLayout->addWidget( numberOfRowsLabel );
hLayout->addWidget( m_numberOfRowsEdit );
hLayout->addWidget( numberOfRowsButton );
m_model1 = new QStandardItemModel( this );
m_tableView1->setModel( m_model1 );
m_model2 = new QStandardItemModel( this );
m_tableView2->setModel( m_model2 );
mainLayout->addLayout( hLayout );
}
Widget::~Widget()
{
}
QSize Widget::calculateTableDesiredSize( QTableView* const table ) {...}
void Widget::onApplyButtonPressed()
{
bool ok = false;
const int rowCount = m_numberOfRowsEdit->text().toInt( &ok );
if ( !ok )
{
return;
}
this->initModel( m_model1, rowCount );
}
// inits model with rowCount rows
void Widget::initModel( QStandardItemModel* const model, const int rowCount )
void Widget::resizeTable( QTableView* const table )
You should use setFixedHeight() instead of resize() to set table height. Also you should addStretch() to scrollAreaWidgetLayout after all its items.

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