How to create a custom grip to resize widgets inside a layout?
-
I created a label to behave as a grip, to be able to 'customize' the height of widgets that are inside of a
QGridLayout
.To get the resize working correctly with any kind of widget size, I needed to set both min and maximum height according to the mouse position relative to the grip:
// Adapt the widget size based on mouse movement. QPoint delta = e->pos() - mousePos; mousePos = e->pos(); if (delta.y()) parentHeight += delta.y(); else parentHeight -= delta.y(); parent->setMinimumHeight(parentHeight); parent->setMaximumHeight(parentHeight);
But this prevents the widget from growing together with her
QGridLayout
parent, as theMaximumHeight
is limited.An example:
After reducing the height of the GUI and then increasing it again, how I could make the
red
frame grow together with theblue
frame?I thought in "resetting" the
setMaximumHeight
at themouseRelease
event, then it could 'grow' again:void mouseReleaseEvent(QMouseEvent* e) { parent->setMaximumHeight(QWIDGETSIZE_MAX); }
However, the widget is automatically adjusted by her
QGridLayout
parent:Reproducible example as seen in the gif:
// gripLabel.h class GripLabel : public QLabel { Q_OBJECT public: QGridLayout* layout; QWidget* parent; bool resizing = false; QSize gripSize = QSize(20, 8); QPoint mousePos; int parentHeight = 0; GripLabel(QWidget* p = 0) : QLabel() { parent = p; layout = qobject_cast<QGridLayout*>(parent->layout()); setMinimumWidth(20); // background-color: green // debug only, just to be able to see the grip area. this->setStyleSheet(R"( background-image: url(:/icons/sizegrip); background-repeat: no-repeat; background-position: bottom right; background-color: green; )"); } void mousePressEvent(QMouseEvent* e) { mousePos = e->pos(); QPoint gripPos = QPoint(width() - gripSize.width(), height() - gripSize.height()); // Check if we hit the grip handle. if ((mousePos.x() >= gripPos.x()) && (mousePos.y() >= gripPos.y())) { parentHeight = parent->height(); resizing = true; } else resizing = false; } void mouseReleaseEvent(QMouseEvent* e) { //parent->setMaximumHeight(QWIDGETSIZE_MAX); } void mouseMoveEvent(QMouseEvent* e) { if (resizing) { // Adapt the widget size based on mouse movement. QPoint delta = e->pos() - mousePos; mousePos = e->pos(); if (delta.y()) parentHeight += delta.y(); else parentHeight -= delta.y(); parent->setMinimumHeight(parentHeight); parent->setMaximumHeight(parentHeight); } } };
#include "gripLabel.h" QtWidgetsApplication8::QtWidgetsApplication8(QWidget* parent) : QMainWindow(parent) { ui.setupUi(this); QGridLayout* layout = new QGridLayout(); ui.centralWidget->setLayout(layout); QFrame* frame = new QFrame(this); QGridLayout* frameLayout = new QGridLayout(); frame->setLayout(frameLayout); frame->setObjectName("frame"); frame->setStyleSheet("#frame { border: 4px solid red; }"); frame->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); frameLayout->setContentsMargins(0, 0, 0, 0); GripLabel* grip = new GripLabel(frame); frameLayout->addWidget(grip, 0, 1, 0, 1, Qt::AlignRight); layout->addWidget(frame, 0, 0); QFrame* frame2 = new QFrame(this); QGridLayout* frameLayout2 = new QGridLayout(); frame2->setLayout(frameLayout2); frame2->setObjectName("frame2"); frame2->setStyleSheet("#frame2 { border: 4px solid blue; }"); frame2->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); frameLayout2->setContentsMargins(0, 0, 0, 0); GripLabel* grip2 = new GripLabel(frame2); frameLayout2->addWidget(grip2, 0, 1, 0, 1, Qt::AlignRight); layout->addWidget(frame2, 1, 0); QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); layout->addItem(spacer, 2, 0); layout->setRowStretch(0, 1); layout->setRowStretch(1, 1); }
What I'm missing is find a way to get the widget resized by the
GripLabel
class be able to grow together her parent layout. -
The problem you have here is that you're trying to move part of a layout functionality into its contents. That leads to all sorts of problems, including the one you describe.
A widget is not supposed to manage its own layout, because to do so it would have to have knowledge about its surroundings - the layout it is in, its siblings and parent. That's a lot of information not related to a function of a widget. Also what if you put your widget in a dock widget or fixed size container? It wouldn't behave properly.
What you want to achieve here is basically a custom layout, not custom widget. A layout governs the geometry of its contents. It has access to all the widget's properties and sets their size/position. No trickery with min/max size is needed. Instead of subclassing a widget create your own subclass of QLayout. It could then resize all the widgets appropriately when parent is resized and you would avoid a lot of other problems. Widgets inside would not have to use min/max to define their geometry. They would just provide the usual size hints that the layout would respect You can achieve the drag resize behavior by installing an event filter in the layout when you add a widget to it. But it's the function of a layout, so it should be handled there, not in the widget.
-
Hi,
Beside the excellent points pointed by @Chris-Kawa, aren't you trying to recreate QSplitter ?