C++: QGridLayout - Whitespace between widgets - c++

I currently have a problem with a QGridLayout.
Each square is a widget and I have a loop like this
for(int i = 0; i < 100; i++;)
{
ui->layout->addWidget(new Square(this),rowNr,colNr);
}
The QGridLayout is part of a QFrame.
My question is: Why is there so much whitespace between each square (horizontally)
This is the code for a square
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(QBrush("#c56c00"));
painter.drawRect(0, 0, 30, 30);
Where is my problem? I want to have each cell 1 by 1 without any space between them. I dont know why it is vertically correct.. Im completely new to C++ and Qt..

Try that:
#include "mainwindow.h"
#include <QGridLayout>
#include <QPushButton>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
resize(800, 800);
auto widget = new QWidget(this);
setCentralWidget(widget);
auto gl = new QGridLayout(widget);
gl->setSpacing(0);
gl->setAlignment(Qt::AlignTop | Qt::AlignLeft);
widget->setLayout(gl);
for (int i = 0; i < 10; i++)
for (int j = 0; j < 10; j++)
gl->addWidget(new QPushButton(QString::number(i*10 + j), this), i, j);
}

Since you have fixed size widgets (30x30), each widget is exactly 30x30 pixels. If your layout is larger horizontally/vertically, the spacing is increased to allow even distribution.
Example:
Let's say you have a frame with a width of 100px with 3 squares aligned horizontally. 3 times 30 equals 90 so you have 10px remaining. Since layouts in general try to evenly distribute the components that they align you will get approx. 3px spacing between each square.
You either have to play with the sizing of your QFrame (my guess is that it is not fixed size and increases/decreases in size when you resize it) or avoid using fixed size widgets inside it.
In general I would recommend either sticking with fixed size for all of your components or make the children (here: squares) to properly resize.
PS: For the task at hand it's quite easy to provide a minimal working example. ;)

Related

Widgets with FixedSize change their size at will

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.

Scrollable view as TabWidget

Here is my problem, I display Buttons and Labels manually with setGeometry() method.
MyClass which inherits from QWidget
Here is my code :
void MyClass::function() {
QLabel *imgP;
QLabel *name;
QPushButton *newConv;
QPixmap *profilPic;
std::stringstream ss;
int j = 0;
int i = 0;
for (int tmp = 0; tmp < 15; tmp++)
{
if (i % 7 == 0 && i != 0)
{
i = 0;
j++;
}
ss << "Username " << tmp;
name = new QLabel(tr(ss.str().c_str()), this);
name->setAlignment(Qt::AlignCenter);
name->setGeometry((30 * (i + 1) + 240 * i), (30 + 390 * j),
240, 60);
profilPic = new QPixmap("./gui/img/avatar1.png");
imgP = new QLabel(this);
imgP->setPixmap(profilPic->scaled(240, 240));
imgP->setGeometry((30 * (i + 1) + 240 * i), (90 + 390 * j),
240, 240);
newConv = new QPushButton(tr("Chat"), this);
newConv->setGeometry((30 * (i + 1) + 240 * i), (330 + 390 * j),
240, 60);
newConv->setFocusPolicy(Qt::NoFocus);
connect(newConv, SIGNAL(released()), this, SLOT(addTab()));
ss.str("");
ss.clear();
i++;
}
}
It may be a bit more complicated than it should, but it works just the way I wanted ..
it looks like this :
As you can see, the result is good, I have my contacts displayed, but the 15th element is hidden because the window is too small. So my question is :
How can I make a ScrollView when this happens ?
I already know QScrollArea, but I would have to work with QBoxLayout, and I don't think this will do the job.
Thanks for your help !
EDIT
This is my MainWidget class which displays the window :
class QTabBar;
MainWidget::MainWidget(QWidget *parent) : QWidget(parent)
{
setFixedSize(1920, 1200);
setWindowTitle(tr("Babel"));
QVBoxLayout *mainLayout = new QVBoxLayout;
QTabBar *tb;
UiContact *contact = new UiContact(this);
QScrollArea *contactScrollArea = new QScrollArea();
contactScrollArea->setWidget(contact);
_tabWidget = new QTabWidget;
tb = _tabWidget->tabBar();
_tabWidget->addTab(new Home(), tr("Home"));
_tabWidget->addTab(contactScrollArea, tr("Contact"));
std::ostringstream oss;
_tabWidget->setTabsClosable(true);
connect(_tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));
tb->tabButton(0, QTabBar::RightSide)->hide();
tb->tabButton(1, QTabBar::RightSide)->hide();
_tabWidget->setFocusPolicy(Qt::NoFocus);
mainLayout->addWidget(_tabWidget);
setLayout(mainLayout);
}
QScrollArea is certainly the way to go. It is not necessary to use a layout, it's only necessary to force the widget to be the size it needs to be. A QScrollArea can handle any QWidget derived class, and it will display its scrollbars as needed.
Practically: if you can calculate how much space you need (which I think you can do), and set the size constraints of the containing widget accordingly, the QScrollArea will show the scrollbars automatically. You can use QWidget::setMinimumSize for that purpose, a simple setMinimumSize(itemWidth*7,itemHeight*(count%7)) should suffice.
This method does allow the widget to grow as large as to fill the QScrollArea. Also, it's possible to disable the frame around the QScrollArea if preferred.
edit:
You probably have something like this:
MyClass *myClass = new MyClass();
...
tabs->insertTab(1,myClass,"Contact");
An example implementation in that situation would be:
MyClass *myClass = new MyClass();
...
QScrollArea* contactScrollArea = new QScrollArea();
contactScrollArea->setWidget(myClass);
tabs->insertTab(1,contactScrollArea,"Contact");
It will place a scroll area inside the tab widget and put the instance of MyClass inside it
A QScrollArea is used as a relatively high container class. Think of a QScrollArea as a widget which can embed another widget inside it. By default it creates an empty widget, which can get a layout. But by using setWidget, you can literally place anything you want in it. A possible "combination" is a QLabel with a large licence text or any other widget that can potentially grow too large (like your widget).
Now, just some extra information:
When you draw stuff yourself (not creating several widgets and code your own layout stuff), you may use QAbstractScrollArea. It would give you full control. It gives scrollbars and a separate middle widget to which you can draw during paintEvent. But I think that's beyond the scope of this.
Now, for sake of clean coding. I would actually suggest you create a separate class ContactItem. It would consist of that label, image and pushbutton, held together with a QVBoxLayout (which makes them neatly packed above eachother). This "item" can be put inside a QGridLayout. This way, you don't need to concern yourself with arranging the items. If you set a minimum size on the picture, it will make sure the items are your preferred width/height. The label size constraints make sure that font differences don't affect the presentation (same goes for the buttons). Last but not least, your list is suddenly resizable. (By using setRowStretch and setColumnStretch you can make sure your list is centered, or top aligned, in case the window is larger than your list takes up.
Functional interpretation
Here I have a possible implementation (it isn't my best code) from what I got from the screenshot and given code.
It consists of a 'Widget' which is responsible for the tab layout:
mainwidget.h
#include <QWidget>
#include "uicontact.h"
class QTabWidget;
class MainWidget : public QWidget
{
Q_OBJECT
QTabWidget *_tabWidget;
public:
MainWidget(QWidget *parent = 0);
~MainWidget();
public slots:
void addChatTab();
};
mainwidget.cpp
#include "mainwidget.h"
#include <QScrollArea>
#include <QTabWidget>
#include <QTabBar>
#include <QVBoxLayout>
#include "home.h"
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent)
{
setFixedSize(1920,1200);
setWindowTitle(tr("Babel"));
QVBoxLayout *mainLayout = new QVBoxLayout;
QTabBar *tb;
UiContact *contact = new UiContact(this);
QScrollArea *contactScrollArea = new QScrollArea();
contactScrollArea->setWidget(contact);
_tabWidget = new QTabWidget;
tb = _tabWidget->tabBar();
_tabWidget->addTab(new Home(), tr("Home"));
_tabWidget->addTab(contactScrollArea, tr("Contact"));
_tabWidget->setTabsClosable(true);
connect(_tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));
tb->tabButton(0, QTabBar::RightSide)->hide();
tb->tabButton(1, QTabBar::RightSide)->hide();
_tabWidget->setFocusPolicy(Qt::NoFocus);
mainLayout->addWidget(_tabWidget);
setLayout(mainLayout);
}
MainWidget::~MainWidget()
{
}
void MainWidget::addChatTab()
{
_tabWidget->addTab(new QWidget, tr("Number %1").arg(_tabWidget->count()-2));
}
The class MyClass is responsible for creating the 'contact' tab:
uicontact.cpp
#include "uicontact.h"
#include "mainwidget.h"
#include <QLabel>
#include <QPushButton>
UiContact::UiContact(MainWidget *owner,QWidget *parent) : QWidget(parent)
{
QLabel *imgP;
QLabel *name;
QPushButton *newConv;
QPixmap *profilPic;
int j = 0;
int i = 0;
for (int tmp = 0; tmp < 15; tmp++)
{
if (i % 7 == 0 && i != 0)
{
i = 0;
j++;
}
name = new QLabel(tr("Username %1").arg(tmp), this);
name->setAlignment(Qt::AlignCenter);
name->setGeometry((30 * (i + 1) + 240 * i), (30 + 390 * j),
240, 60);
profilPic = new QPixmap("./gui/img/avatar1.png");
imgP = new QLabel(this);
imgP->setPixmap(profilPic->scaled(240, 240));
imgP->setGeometry((30 * (i + 1) + 240 * i), (90 + 390 * j),
240, 240);
newConv = new QPushButton(tr("Chat"), this);
newConv->setGeometry((30 * (i + 1) + 240 * i), (330 + 390 * j),
240, 60);
newConv->setFocusPolicy(Qt::NoFocus);
connect(newConv, SIGNAL(clicked(bool)), owner, SLOT(addChatTab()));
i++;
}
setMinimumSize(270*7,420*(15/7+1));
}
The uicontact.h file is very trivial so omitted.
A few things to note: MyClass receives an 'owner' pointer, this allows it to talk directly to the top level widget responsible for adding tabs. Probably you want to look at QSignalMapper to be able to map the individual QPushButtons to a more known value. With QSignalMapper, you can map the button to an integer, string, QWidget* or QObject*.
Also note the tr("Contact %1").arg(tmp) which is the correct way to make your program locale aware.

Scaling items and rendering

I am making a small game in C++11 with Qt. However, I am having some issues with scaling.
The background of my map is an image. Each pixel of that image represents a tile, on which a protagonist can walk and enemies/healthpacks can be.
To set the size of a tile, I calculat the maximum amount like so (where imageRows & imageCols is amount of pixels on x- and y-axis of the background image):
QRect rec = QApplication::desktop()->screenGeometry();
int maxRows = rec.height() / imageRows;
int maxCols = rec.width() / imageCols;
if(maxRows < maxCols){
pixSize = maxRows;
} else{
pixSize = maxCols;
}
Now that I have the size of a tile, I add the background-image to the scene (in GameScene ctor, extends from QGraphicsScene):
auto background = new QGraphicsPixmapItem();
background->setPixmap(QPixmap(":/images/map.png").scaledToWidth(imageCols * pixSize));
this->addItem(background);
Then for adding enemies (they extend from a QGraphicsPixMapItem):
Enemy *enemy = new Enemy();
enemy->setPixmap(QPixmap(":/images/enemy.png").scaledToWidth(pixSize));
scene->addItem(enemy);
This all works fine, except that on large maps images get scaled once (to a height of lets say 2 pixels), and when zooming in on that item it does not get more clear, but stays a big pixel. Here is an example: the left one is on a small map where pixSize is pretty big, the second one has a pixSize of pretty small.
So how should I solve this? In general having a pixSize based on the screen resolution is not really useful, since the QGrapicsScene is resized to fit the QGraphicsView it is in, so in the end the view still determines how big the pixels show on the screen.
MyGraphicsView w;
w.setScene(gameScene);
w.fitInView(gameScene->sceneRect(), Qt::KeepAspectRatio);
I think you might want to look at the chip example from Qt (link to Qt5 but also works for Qt4).
The thing that might help you is in the chip.cpp file:
in the paint method:
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
where painter is simply a QPainter and option is of type QStyleOptionGraphicsItem. This quantity gives you back a measure of the current zoom level of your QGraphicsView and thus as in the example you can adjust what is being drawn at which level, e.g.
if (lod < 0.2) {
if (lod < 0.125) {
painter->fillRect(QRectF(0, 0, 110, 70), fillColor);
return;
}
QBrush b = painter->brush();
painter->setBrush(fillColor);
painter->drawRect(13, 13, 97, 57);
painter->setBrush(b);
return;
}
[...]
if (lod >= 2) {
QFont font("Times", 10);
font.setStyleStrategy(QFont::ForceOutline);
painter->setFont(font);
painter->save();
painter->scale(0.1, 0.1);
painter->drawText(170, 180, QString("Model: VSC-2000 (Very Small Chip) at %1x%2").arg(x).arg(y));
painter->drawText(170, 200, QString("Serial number: DLWR-WEER-123L-ZZ33-SDSJ"));
painter->drawText(170, 220, QString("Manufacturer: Chip Manufacturer"));
painter->restore();
}
Does this help?

Spacing between widgets in QHBoxLayout

I'm trying to create a GUI with QtCreator. For this GUI, I need to display several images with different sizes next to each other. These images should be touching each other.
I use a QWidget with a QHBoxLayout, where I add the labels (with different sizes) containing the images.
According to related questions, I should use setSpacing and setContentsMargin to remove these spaces, but that won't work; I tried several times.
Here's the code:
QWidget *widget = new QWidget(ui->tagcloud);
QHBoxLayout * l = new QHBoxLayout(widget);
ui->tagcloud->setWidget(widget);
for(int i=0;i<list.size();++i)
{
QLabel *lab = new QLabel;
QPixmap pic((list[i].imgPath).c_str()); //This fetches the image
int sizeChange = 50 + (2*list[i].percent); //Calculates the size of the image
lab->setFixedSize(QSize(sizeChange, sizeChange));
lab->setPixmap(pic);
lab->setScaledContents(true);
l->addWidget(lab);
l->setSpacing(0);
}
However, when I run this, the spacing remains the same (i.e. definitely not zero).
If I add more labels to the layout, the spacing seems to get smaller.
Can anyone explain or help me? Thanks!
Setting spacing to 0 and adding stretch before and after works for me :
l->addStretch();
for(int i = 0; i < list.size(); ++i)
{
QLabel *lab = new QLabel;
QPixmap pic((list[i].imgPath).c_str()); //This fetches the image
int sizeChange = 50 + (2*list[i].percent); //Calculates the size of the image
lab->setFixedSize(QSize(sizeChange, sizeChange));
lab->setPixmap(pic);
lab->setScaledContents(true);
l->addWidget(lab);
}
l->addStretch();
l->setSpacing(0);
Also this works I think
l->setSizeConstraint(QLayout::SetMaximumSize);

How do you change the minimum size of a QTableWidget?

I am embedding some QTableWidget(s) recursively inside of each other. The final GUI will have 4 to a couple thousand squares. Here is an example screenshot only showing 16 squares.:
Because there are so many squares (thousands) that need to be displayed I need the minimum size of each square to be something like 5x5 pixels.
The problem is that I used my mouse to size the window as small as possible... and then I got to what you see in the screenshot! The screenshot has each square being about 18x18 pixels... which isn't small enough to fit thousands of squares on a screen. Something is preventing me from using my mouse to size the squares smaller!
How can I make the squares in this screenshot have a smaller minimum size?
main.cpp:
#include "TableWidget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TableWidget *x1 = new TableWidget(2,2);
for(int i = 0; i < x1->rowCount(); i++) {
for(int j = 0; j < x1->columnCount(); j++) {
x1->setCellWidget(i,j,new TableWidget(2,2));
}
}
QGridLayout *layout = new QGridLayout;
layout->addWidget(x1, 0, 0);
QWidget *window = new QWidget;
window->setLayout(layout);
window->show();
return a.exec();
}
TableWidget.h:
class TableWidget : public QTableWidget
{
Q_OBJECT
public:
TableWidget(int rows, int columns, QWidget *parent = 0);
private:
signals:
public slots:
};
TableWidget.cpp:
TableWidget::TableWidget(int rows, int columns, QWidget *parent) :
QTableWidget(rows,columns,parent)
{
//------
QTableWidget::horizontalHeader()->hide();
QTableWidget::verticalHeader()->hide();
//------
QTableWidget::horizontalHeader()->setResizeMode(QHeaderView::Stretch);
QTableWidget::verticalHeader()->setResizeMode(QHeaderView::Stretch);
//------
QTableWidget::setEditTriggers(QAbstractItemView::NoEditTriggers);
//------
QTableWidget::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QTableWidget::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
//------
QTableWidget::horizontalHeader()->setMinimumSectionSize(1);
QTableWidget::verticalHeader()->setMinimumSectionSize(1);
QTableWidget::horizontalHeader()->setDefaultSectionSize(1);
QTableWidget::verticalHeader()->setDefaultSectionSize(1);
// FIXME, the minimum size is not 1 pixel... it is like 10 pixels...
//------
QTableWidget::setSelectionMode(QAbstractItemView::NoSelection);
}
UPDATE - As per comments below I tried rendering many more squares and here is 1024:
On my WindowsXP, I can't shrink most programs lower than 148x96. It might have absolutely nothing to do with your Widgets.
Test with 256+ squares, and tell us how big each square is.
Windows (and whatever it is you're using) have this limitation to guarantee the users are able to see the bottons on the top, a few letters of the title, and enough room for scrollbars. And so the user doesn't lose the program they shrunk to 3x2px.