Reputation: 21883
Say I'm creating an image gallery view that has 3 columns (i.e. it displays 3 images on each row). I can add new images to the end by using division and remainder:
int row = images.size() / 3;
int col = images.size() % 3;
gridLayout->addWidget(myImage, row, col);
The problem arises when I want to remove an image from the middle; it now leaves that middle row with just two images instead of "shifting" everything back and filling each row with 3 images (except the last row).
It seems like QGridLayout doesn't have too many features, am I missing something here or is the only option to implement everything myself? Or am I using the wrong tool (QGridLayout) in the first place?
Upvotes: 0
Views: 1248
Reputation: 20141
AFAIK, there is no auto-relayout in QGridLayout. I'm even not sure whether QGridLayout
is intended for this purpose.
IMHO, QTableView or QTableWidget might be the better choice.
(Concerning this, QAbstractItemModel::moveRows() comes into my mind.)
However, this doesn't mean that it cannot be achieved.
I made an MCVE to demonstrate this – testQDeleteFromLayoutShift.cc
:
#include <cassert>
#include <vector>
#include <QtWidgets>
// comment out to get rid of console diagnostic output
#define DIAGNOSTICS
class PushButton: public QPushButton {
public:
PushButton(const QString &text, QWidget *pQParent = nullptr):
QPushButton(text)
{ }
#ifdef DIAGNOSTICS
virtual ~PushButton() { qDebug() << "Destroyed:" << this << text(); }
#else // (not) DIAGNOSTICS
virtual ~PushButton() = default;
#endif // DIAGNOSTICS
};
// number of columns in grid
const int wGrid = 3;
// fill grid with a certain amount of buttons
std::vector<QPushButton*> fillGrid(QGridLayout &qGrid)
{
const int hGrid = 5;
std::vector<QPushButton*> pQBtns; pQBtns.reserve(wGrid * hGrid);
unsigned id = 0;
for (int row = 0; row < hGrid; ++row) {
for (int col = 0; col < wGrid; ++col) {
QPushButton *pQBtn = new PushButton(QString("Widget %1").arg(++id));
qGrid.addWidget(pQBtn, row, col);
pQBtns.push_back(pQBtn);
}
}
#ifdef DIAGNOSTICS
qDebug() << "qGrid.parent().children().count():"
<< dynamic_cast<QWidget*>(qGrid.parent())->children().count();
qDebug() << "qGrid.count():"
<< qGrid.count();
#endif // DIAGNOSTICS
return pQBtns;
}
// delete a button from grid (shifting the following)
void deleteFromGrid(QGridLayout &qGrid, QPushButton *pQBtn)
{
qDebug() << "Delete button" << pQBtn->text();
// find index of widget in grid
int i = 0;
const int n = qGrid.count();
while (i < n && qGrid.itemAt(i)->widget() != pQBtn) ++i;
assert(i < n);
// find item position in grid
int row = -1, col = -1, rowSpan = 0, colSpan = 0;
qGrid.getItemPosition(i, &row, &col, &rowSpan, &colSpan);
// remove button from layout
QLayoutItem *pQItemBtn = qGrid.itemAt(i);
qGrid.removeItem(pQItemBtn);
// reposition all following button layouts
for (int j = i + 1; j < n; ++j) {
QLayoutItem *pQItem = qGrid.takeAt(i);
const int row = (j - 1) / wGrid, col = (j - 1) % wGrid;
qGrid.addItem(pQItem, row, col);
}
delete pQBtn;
#if 1 // diagnostics
qDebug() << "qGrid.parent().children().count():"
<< dynamic_cast<QWidget*>(qGrid.parent())->children().count();
qDebug() << "qGrid.count():"
<< qGrid.count();
#endif // 0
}
// application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
QWidget qWin;
qWin.setWindowTitle(QString::fromUtf8("Demo Delete from QGridLayout (with shift)"));
QGridLayout qGrid;
qWin.setLayout(&qGrid);
std::vector<QPushButton*> pQBtns = fillGrid(qGrid);
qWin.show();
// install signal handlers
for (QPushButton *const pQBtn : pQBtns) {
QObject::connect(pQBtn, &QPushButton::clicked,
[&qGrid, pQBtn](bool) {
QTimer::singleShot(0, [&qGrid, pQBtn]() {
deleteFromGrid(qGrid, pQBtn);
});
});
}
// runtime loop
return app.exec();
}
A Qt project file to build this – testQDeleteFromLayoutShift.pro
:
SOURCES = testQDeleteFromLayoutShift.cc
QT += widgets
Output in Windows 10 (built with VS2017):
Qt Version: 5.13.0
qGrid.parent().children().count(): 16
qGrid.count(): 15
After clicking on button “Widget 8”:
Delete button "Widget 8"
Destroyed: QPushButton(0x25521e3ef10) "Widget 8"
qGrid.parent().children().count(): 15
qGrid.count(): 14
After clicking on button “Widget 6”
Delete button "Widget 6"
Destroyed: QPushButton(0x25521e3f7d0) "Widget 6"
qGrid.parent().children().count(): 14
qGrid.count(): 13
After clicking on button “Widget 12”
Delete button "Widget 12"
Destroyed: QPushButton(0x25521e46ad0) "Widget 12"
qGrid.parent().children().count(): 13
qGrid.count(): 12
Notes:
I used lambdas for the signal handlers of QPushButton::clicked()
to pass the related QGridLayout
and QPushButton*
to the handler deleteFromGrid()
– IMHO the most convenient way.
deleteFromGrid()
is called via a QTimer::singleShot()
with delay 0. If I would call deleteFromGrid()
in the signal handler of QPushButton::clicked()
directly, this would result in some kind of Harakiri due to the last line in deleteFromGrid()
: delete pQBtn;
.
The nested lambdas might look a bit scaring, sorry.
While writing this answer I recalled an older of mine:
SO: qgridlayout add and remove sub layouts
which might be of interest.
Upvotes: 1