Widgets with FixedSize change their size at will - c++

I have a QGridLayout inside a center widget, which contains a supposedly fixed-sized widget (referred to as inner widget) that also has a QGridLayout filled with buttons. Inner widget's size is determined by how many buttons are there in the grid, and is supposed to be an exact fit (no spacing between the buttons, FixedSize policy applied in buttons' constructor), and all buttons have their sizes and policies set in the constructor. Now, if I don't put inner widget into a layout of any kind, it works just fine, I get nice square buttons. But if I put inner widget into a grid layout, all buttons suddenly change their sizes, and widget also doesn't seem like keeping its size. Why?
Edit: MyButtonTable:
MyButtonTable::MyButtonTable(QWidget *parent) : QWidget(parent), array()
{
size_x = 2;
size_y = 2;
QGridLayout* layout = new QGridLayout();
for(size_t x = 0; x < size_x; x++) {
this->array.push_back(std::vector<MyRightClickButton*>());
}
for(size_t x = 0; x < size_x; x++) {
for(size_t y = 0; y < size_y; y++) {
this->array[x].push_back(new button_t());
QObject::connect(array[x][y], SIGNAL(rightClicked()), this, SLOT(internalRightClick()));
QObject::connect(array[x][y], SIGNAL(clicked()), this, SLOT(internalClick()));
layout->addWidget(array[x][y], x, y);
}
}
layout->setSpacing(0);
layout->setContentsMargins(0,0,0,0);
this->setLayout(layout);
this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
this->setMinimumSize(QSize(0,0));
this->resize(QSize(10*size_y,10*size_x));
}
MyRightClickButton(QWidget *parent = 0):QPushButton(parent) {
marked = false;
this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
this->setMinimumSize(QSize(0,0));
this->resize(QSize(10,10));
}

A layout manager is used to arrange child widgets in them. The arrangement designates each layout manager. Layout managers adjust the size of their child widgets based on the resize policies. Even if you are setting a fixed size for a child widget, using resize() or setGeometry(), they will be resized by the layout manager, if you did not set the resize policy of the child widget.
For example
widget->setFixedSize (100, 100);
widget->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
This is how it should be done.

Related

Move layout to another layout in Qt5

I have a custom container widget in Qt5 which has a QFrame without a layout. I use setLayout on QFrame which works fine but sometimes I want to transfer an existing layout which contains widgets and other layouts to that QFrame as layout. Is that even possible in Qt5 to transfer layouts already added with addLayout?
No problem. Just make the layout the layout of the target widget with setLayout(). The widgets contained will be reparented automatically.
void MainWindow::moveLayout()
{
// Move the layout from ui->g0 to ui->g1 and vice versa
// every time you call moveLayout
static int toggle = 0;
QLayout* l;
QWidget* w;
if (toggle == 0)
{
l = ui->g0->layout();
w = ui->g1;
}
else
{
l = ui->g1->layout();
w = ui->g0;
}
if (w->layout())
{
// Hack to clean target widget
QWidget z;
z.setLayout(w->layout());
}
w->setLayout(l);
toggle = 1 - toggle;
}

QTableView bigger than its container

I am working on Qt applicaction. There I have QMainWindow. Inside it I have added QTableView. When I run the application I see that I need to scroll to display the whole table and also blank space shows up below it.
I would like main window to resize horizontally in order to use space needed by the table. Also I would like it to resize vertically to not having space unused. How could I achieve that?
This is my code so far:
void MainWindow::initUi() {
setWindowTitle(tr("Main Window"));
QWidget* centralWidget = new QWidget(this);
QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
QFormLayout *upperLayout = new QFormLayout;
// Default layout appearance of QMacStyle
upperLayout->setRowWrapPolicy(QFormLayout::DontWrapRows);
upperLayout->setFieldGrowthPolicy(QFormLayout::FieldsStayAtSizeHint);
upperLayout->setFormAlignment(Qt::AlignHCenter | Qt::AlignTop);
upperLayout->setLabelAlignment(Qt::AlignLeft);
QVBoxLayout *resultsLayout = new QVBoxLayout;
QTableView* table = new QTableView(centralWidget);
table->verticalHeader()->hide();
QStandardItemModel* model= new QStandardItemModel(4, 4);
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
QStandardItem *item = new QStandardItem(QString("row %0, column %1").arg(row).arg(column));
model->setItem(row, column, item);
}
}
table->setModel(model);
QLabel* upperLabel = new QLabel(tr("Label:"), centralWidget);
upperLabel->setAlignment(Qt::AlignLeft);
resultLabel = new QLabel(tr("Result goes here"), centralWidget);
mainLayout->addLayout(resultsLayout);
resultsLayout->addLayout(upperLayout);
resultsLayout->addWidget(table);
upperLayout->addRow(upperLabel, resultLabel);
centralWidget->setLayout(mainLayout);
setCentralWidget(centralWidget);
this->adjustSize();
}
Set the sizeAdjustPolicy of the table to AdjustToContents view, then set the size policy to Fixed in both horizontal and vertical directions.
AdjustToContents might incur a slight performance penalty for dynamic contents in the view, since every data change may change the layout.
The Qt Designer is a really nifty tool to figure layout issues out quickly; the {table,list,tree} widgets behave exactly the same as the views do (because they're the same) and the widgets can be quickly filled with dummy data in Qt Designer.

QGridLayout 3x3 grid resize widgets

I have 9 QWidgets. Sometimes they are invisible. They are all in a 3x3 grid.
[0][3][6]
[1][4][7]
[2][5][8]
Now if for example widget 3 is made invisible, other widgets should should resize:
[ 0 ][ 6 ]
[ 1 ][ 4 ][ 7 ]
[ 2 ][ 5 ][ 8 ]
The same should happen vertically, basically all the empty space should be filled but I'm not sure how to do that. When I set a widget invisible, the empty space is not taken.
QGroupBox *parent = getParent();
QGridLayout *layout = new QGridLayout;
layout->setMargin(0);
layout->setMargin(5);
int w = parent->geometry().width();
int h = parent->geometry().height();
QSize min(w/3-2*layout->spacing(),h/3-2*layout->spacing());
QSize max(w,h);
for (int n = 0; n < 6; ++n)
{
layout->setRowStretch(n,6);
layout->setColumnStretch(n,6);
}
for (size_t n = 0, t = widgets.size(); n < t; ++n)
{
QWidget *widget = widgets[n];
widget->setMinimumSize(min);
widget->setMaximumSize(max);
widget->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
layout->addWidget(widget);
}
Thanks in advance for the help.
I have the similar experience. I used such widget to visualize dynamically changed configuration of device.
The required behavior can be achieved by using QVBoxLayout as row and QWidget ad cell (column). In my case I used custom widget derived from QLabel as cell. Everything works from the box without tuning of geometry.
Pseudo code:
// Inside widget's constructor (or createLayout function).
topLayout = new QVBoxLayout ();
aLayout = new QHBoxLayout ();
bLayout = new QHBoxLayout ();
cLayout = new QHBoxLayout ();
topLayout->addLayout (aLayout);
topLayout->addLayout (bLayout);
topLayout->addLayout (cLayout);
setLayout (topLayout);
...
// Add cell to corresponding row.
void add_cell (QWidget * cell)
{
aLayout->addWidget (cell);
}
First, remove the widget from the layout by iterating the layout and removing the item that holds the widget. While doing that you also collect the information about available cells. Then reinsert the widget with proper position and spans.
As far as I know, there is no way to do that automatically. I would suggest you to keep a list of the widgets and reposition them by clearing the grid layout and populating it again each time a widget changes its visibility. I found out that only this way I can solve a similar task that I had (for rearranging the widgets in the grid) without creating a custom layout by subclassing QLayout.

Add widgets to QFileDialog

I need to add a widget (QTableWidget) into QFileDialog's layout. I know that it is QGridLayout with sizes (3,4). The table must be in 3-rd row and span all columns.
QTableWidget* tableWidget = new QTableWidget(this);
QGridLayout *layout = static_cast<QGridLayout*>(QFileDialog::layout());
layout->addWidget(tableWidget, 2, 0, 1, 4);
With this code the original 3-rd row which contains lineEdit and save/open pushButton disappears. How can I add widgets between already existing widgets of QGridLayout so that original widgets remain in the layout.
I strongly recommend you not to rely on QFileDialog's implementation. The layout can be different on different platforms or different versions of Qt. It may be more correct to place your table under the dialog or to the right of it. This can be done easily without altering the layout of the QFileDialog itself. Just create a QVBoxLayout and put QFileDialog and QTableWidget inside it.
However, the question has been asked, and the solution exists. QGridLayout has no functionality such as QBoxLayout::insertItem. So we need to implement this behavior manually. The plan is:
Obtain the list of layout items placed in 3rd and 4th rows.
Calculate new positions of items.
Take elements out of item and add them back at new positions.
Working code:
QFileDialog* f = new QFileDialog();
f->setOption(QFileDialog::DontUseNativeDialog, true); //we need qt layout
QGridLayout *layout = static_cast<QGridLayout*>(f->layout());
QList< QPair<QLayoutItem*, QList<int> > > moved_items;
f->show();
for(int i = 0; i < layout->count(); i++) {
int row, column, rowSpan, columnSpan;
layout->getItemPosition(i, &row, &column, &rowSpan, &columnSpan);
if (row >= 2) {
QList<int> list;
list << (row + 1) << column << rowSpan << columnSpan;
moved_items << qMakePair(layout->takeAt(i), list);
i--; // takeAt has shifted the rest items
}
}
for(int i = 0; i < moved_items.count(); i++) {
layout->addItem(moved_items[i].first,
moved_items[i].second[0],
moved_items[i].second[1],
moved_items[i].second[2],
moved_items[i].second[3]);
}
QTableWidget* tableWidget = new QTableWidget();
layout->addWidget(tableWidget, 2, 0, 1, 4);

Minimum size/width of a QPushButton that is created from code

I created 2 rows of push buttons, each row is inside a QHBoxLayout.
I create the buttons in the code:
static const char* buttonText = "23456789TJQKA";
for (int ii = 0; buttonText[ii]; ii++)
{
QPushButton* pushButton = new QPushButton(this);
pushButton->setText(QString(buttonText[ii]));
ui->horizontalLayout_1->addWidget(pushButton);
}
for (int ii = 0; buttonText[ii]; ii++)
{
QPushButton* pushButton = new QPushButton(this);
pushButton->setText(QString(buttonText[ii]));
ui->horizontalLayout_2->addWidget(pushButton);
}
The problem is that they can't shrink (when the user resizes the dialog) beyond that size, even though their text would fit in a much smaller width. If I create the buttons manually in the resource editor instead of in the code, they can have smaller width than that.
This happens because the minimumSizeHint of the QPushButton does not allow the QLayout to resize it :
The default implementation of minimumSizeHint() returns an invalid
size if there is no layout for this widget, and returns the layout's
minimum size otherwise. Most built-in widgets reimplement
minimumSizeHint().
QLayout will never resize a widget to a size smaller than the minimum
size hint unless minimumSize() is set or the size policy is set to
QSizePolicy::Ignore. If minimumSize() is set, the minimum size hint
will be ignored.
The simple solution is to set the minimum width explicitly:
static const char* buttonText = "23456789TJQKA";
for (int ii = 0; buttonText[ii]; ii++)
{
QPushButton* pushButton = new QPushButton(this);
pushButton->setMinimumWidth(5);
pushButton->setText(QString(buttonText[ii]));
ui->horizontalLayout_1->addWidget(pushButton);
}
for (int ii = 0; buttonText[ii]; ii++)
{
QPushButton* pushButton = new QPushButton(this);
pushButton->setMinimumWidth(5);
pushButton->setText(QString(buttonText[ii]));
ui->horizontalLayout_2->addWidget(pushButton);
}
As pnezis wrote, you probably want to override the default minimum size calculated by the button. Here's a way you can do it while avoiding to choose an arbitrary size that might not work when conditions vary (different font or font size, UI style, etc):
QWidget* parent = /* some widget */
auto button = new QPushButton(QLatin1String("X"), parent);
auto textSize = button->fontMetrics().size(Qt::TextShowMnemonic, button->text());
QStyleOptionButton opt;
opt.initFrom(button);
opt.rect.setSize(textSize);
button->setMinimumSize(
button->style()->sizeFromContents(QStyle::CT_PushButton,
&opt,
textSize,
button));
The above was adapted and simplified from QPushButton's own code. You may want to look at the source of QPushButton::sizeHint for all the details.
setMaximumWidth works for me.
sample code is in pyqt, but it should translate directly to C++ without any problems.
from PyQt4 import QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QtGui.QHBoxLayout()
texts = [":)",
"&Short",
"&Longer",
"&Different && text",
"More && text",
"Even longer button text", ]
for text in texts:
btn = QtGui.QPushButton(text)
double = text.count('&&')
text = text.replace('&', '') + ('&' * double)
width = btn.fontMetrics().boundingRect(text).width() + 7
btn.setMaximumWidth(width)
layout.addWidget(btn)
self.setLayout(layout)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
mainWin = Window()
mainWin.show()
sys.exit(app.exec_())