I'm trying to implement a cell-based widget similar to the one in Jupyter Notebook in Qt where multiple cells are stacked vertically. Each cell is made of a QTextEdit and multiple buttons and the cell's vertical size would grow by entering more text into the text edit (i.e. no vertical scrollbars in the text edit).
However I'm not sure what the best way would be to implement such a widget in Qt. I've tried a QListWidget with a custom widget (made of a QTextEdit and a couple of buttons) for each item, however the custom widget wouldn't grow vertically by entering more text and instead the QTextEdit scrollbars would show up. I also implemented a second version using QListView and QStyledItemDelegate however I encountered multiple issues including not being able to interact with the painted buttons (through the delegate).
Any suggestions/examples about how to implement such a cell-based widget?
You can subclass QTextEdit and listen for changes in the document size using document()->size() (not the size of the QTextEdit, mind). You can then resize the QTextEdit's parent to reflect the new size. To fill the parent with the widget, use a layout. I've used QVBoxLayout several times below for this purpose. (One, to hold multiple QTextEdit widgets in a vertical column; Two, to hold a single TextEdit).
#include <QtCore>
#include <QtWidgets>
class DyTextEdit : public QTextEdit
{
Q_OBJECT
public:
DyTextEdit(QWidget *parent = nullptr) :
QTextEdit(parent)
{
QTextDocument *doc = document();
currentSize = doc->size();
qDebug() << "[Init] Size:" << currentSize;
// listen to text/content changes
connect(this, &QTextEdit::textChanged, this, &DyTextEdit::on_textChanged);
// connect <> 💡
connect(this, &DyTextEdit::sizeChanged, this, &DyTextEdit::resizeParent);
doc->setTextWidth(-1);
emit sizeChanged(currentSize); // init
}
signals:
// emitted when document size changes
void sizeChanged(QSizeF size);
public slots:
void on_textChanged()
{
QSizeF newSize = document()->size();
qDebug() << "[TextChanged] Size:" << newSize;
// detect changes in the document size
if (newSize != currentSize)
emit sizeChanged(newSize); // emit signal💡
// update size
currentSize = newSize;
}
void resizeParent(QSizeF size)
{
// resize the parent's height, don't bother with the width
parentWidget()->setFixedHeight(size.height());
}
private:
QSizeF currentSize; // keeps track of current document size
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// holds all dytextedits
QWidget aggregateWidget;
QVBoxLayout aggregateLayout(&aggregateWidget);
// note: for scalability, use a QList (or QLists)
// first textbox
QWidget widget;
QVBoxLayout vbLayout(&widget);
vbLayout.setMargin(0);
DyTextEdit dytext(&widget);
vbLayout.addWidget(&dytext);
// first button row
QHBoxLayout hbLayout(&widget);
hbLayout.setMargin(0);
QPushButton pb1a("Push this.", &widget);
hbLayout.addWidget(&pb1a);
QPushButton pb1b("Push this.", &widget);
hbLayout.addWidget(&pb1b);
// second textbox
QWidget widget2;
QVBoxLayout vbLayout2(&widget2);
vbLayout2.setMargin(0);
DyTextEdit dytext2(&widget2);
vbLayout2.addWidget(&dytext2);
// second button row
QHBoxLayout hbLayout2(&widget2);
hbLayout2.setMargin(0);
QPushButton pb2a("Push this.", &widget2);
hbLayout2.addWidget(&pb2a);
QPushButton pb2b("Push this.", &widget2);
hbLayout2.addWidget(&pb2b);
// add widgets to layout
aggregateLayout.addWidget(&widget); // cell 1
aggregateLayout.addLayout(&hbLayout); // |
aggregateLayout.addWidget(&widget2); // cell 2
aggregateLayout.addLayout(&hbLayout2); // |
aggregateLayout.setSizeConstraint(QLayout::SetMinAndMaxSize);
aggregateWidget.show();
return a.exec();
}
#include "main.moc" // include if there are QObject classes in main.cpp
Note that above, I've used rows of buttons using QHBoxLayout and added them to the overall, aggregate layout detached from the textbox widgets. You can still use a say, QGridLayout if you wish to set your buttons in a different format. You can also combine the textedits and button layouts as children under another widget (to serve as an umbrella) covering both. That would more clearly form a cell of widget and may be easier to move around. (I've uploaded a workable example of this on Github.)
Demo (macOS):
This is a screenshot of after typing content and copy-pasting content.
Note that the aggregate widget will contract on removal of lines of text.
Related
I have the following codes in my Qt project with the following main:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
The class Widget is a QWidget object with the following constructor:
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_Scene = new QGraphicsScene(this);
QGraphicsLinearLayout* layout = new
QGraphicsLinearLayout(Qt::Orientation::Vertical);
for(int i = 0; i < 10; i++)
{
std::string name = "m_" + std::to_string(i);
GraphicsTextItem* item = new GraphicsTextItem(nullptr, QString(name.c_str()));
layout->addItem(item);
}
QGraphicsWidget* list = new QGraphicsWidget;
list->setPos(0,0);
list->setLayout(layout);
m_Scene->addItem(list);
QGraphicsView* view = new QGraphicsView(this);
view->setScene(m_Scene);
// Why one of these lines must be uncommented?
//m_Scene->setSceneRect(0, 0, 1920, 768);
//QVBoxLayout *ttopLayout = new QVBoxLayout;
//ttopLayout->addWidget(view);
//setLayout(ttopLayout);
}
GraphicsTextItem is just a QGraphicsWidget for displaying text:
class GraphicsTextItem : public QGraphicsWidget
{
public:
QString m_Name;
QColor m_Color;
public:
GraphicsTextItem(QGraphicsItem * parent = nullptr, const QString& name = QString());
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
{
Q_UNUSED(option)
Q_UNUSED(widget)
QFont font("Times", 10);
painter->setFont(font);
painter->setPen(m_Color);
painter->drawText(0, 0, m_Name);
}
};
My question is that why my scene is not shown. I must either define a SceneRect or define a layout on my widget?
I made an even shorter MCVE for demonstration:
#include <QtWidgets>
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QWidget qWinMain;
qWinMain.resize(320, 240);
QFrame qFrm(&qWinMain);
qFrm.setFrameStyle(QFrame::Box | QFrame::Raised);
qFrm.setLineWidth(0);
qFrm.setMidLineWidth(1);
qWinMain.show();
return app.exec();
}
compiled and started in cygwin64. This is how it looks:
There is a main window (with window manager decoration).
There is a child QFrame.
The child QFrame is "pressed" into the upper left corner.
How comes?
What QWidget does ensure: Child widgets are rendered (in front) when QWidget is rendered.
What QWidget is not (directly) responsible for: Layouting child widgets.
For this, a layout manager has to be plugged in:
#include <QtWidgets>
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QWidget qWinMain;
qWinMain.resize(320, 240);
QVBoxLayout qVBox(&qWinMain);
QFrame qFrm(&qWinMain);
qFrm.setFrameStyle(QFrame::Box | QFrame::Raised);
qFrm.setLineWidth(0);
qFrm.setMidLineWidth(1);
qVBox.addWidget(&qFrm);
qWinMain.show();
return app.exec();
}
compiled and started again in cygwin64. This is how it looks:
Now, the QFrame qFrm is filling the QWidget qWinMain nicely. Resize events received in qWinMain will be forwarded to the layout manager qVBox which will re-layout the children of qWinMain (i.e. qFrm) again.
I strongly believe OP's GraphicsView is just not visible because it has no minimal size requirement. (It's just to small to be visible.)
Hence, adding a layout manager ensures that the GraphicsView fills the parent widget client area. Resizing the contents of GraphicsView (by m_Scene->setSceneRect(0, 0, 1920, 768);) is yet another option to fix this, albeit the worse one.
Finally, the link to Qt Doc.: Layout Management.
Layout Management
The Qt layout system provides a simple and powerful way of automatically arranging child widgets within a widget to ensure that they make good use of the available space.
Introduction
Qt includes a set of layout management classes that are used to describe how widgets are laid out in an application's user interface. These layouts automatically position and resize widgets when the amount of space available for them changes, ensuring that they are consistently arranged and that the user interface as a whole remains usable.
All QWidget subclasses can use layouts to manage their children. The QWidget::setLayout() function applies a layout to a widget. When a layout is set on a widget in this way, it takes charge of the following tasks:
Positioning of child widgets
Sensible default sizes for windows
Sensible minimum sizes for windows
Resize handling
Automatic updates when contents change:
Font size, text or other contents of child widgets
Hiding or showing a child widget
Removal of child widgets
I'm under Ubuntu 18.04 and using last QtCreator with last Qt5 framework
I'm trying to setup a vertical layout with at top an horizontal nested layout and under a custom made widget (based on clock exemple for the moment)
I setup a fresh widget project but as I dont understand yet how to get acess to nested layout from c++ code I removed automaticaly created mainform and created layout at execution.
If I use my custom widget as window it works if I use a window and add my custom widget it works but if I add a layout, add my custom widget to this layout and call setLayout on window it disappear...
I tried almost all orders : set the layout first or after adding my widget.
I tried to call show() on my widget or not befor or after adding it to layout
I tried to add the nested layer first or last nothing change
I 've read several time exemples and manual about layout and nested one
I can see my nested layout but not my widget
here's my main :
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
w.setWindowTitle("WOUND : Windings Organiser with Usefull and Neat Drawings");
QVBoxLayout* hl=new QVBoxLayout;// top vertical layout
QHBoxLayout* slotqueryLayout=new QHBoxLayout; // nested first horizontal layout
QLabel *slotqueryLabel = new QLabel("Slot number:");
QLineEdit *slotqueryEdit = new QLineEdit("48");
slotqueryLayout->addWidget(slotqueryLabel);
slotqueryLayout->addWidget(slotqueryEdit);
hl->addLayout(slotqueryLayout);
WindingViewer* wv=new WindingViewer(&w); // my widget is just a simple canvas to draw things
hl->addWidget(wv);
wv->show(); // dont know if it's needed but if I remove it it dont change anything / tried to do it before or after adding to layout
w.setLayout(hl); // if called before adding content it dont change
w.show();
return a.exec();
}
and here you can find my custom widget:
class WindingViewer : public QWidget
{
Q_OBJECT
public:
explicit WindingViewer(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
signals:
public :
int SlotNumber;
public slots:
};
and
WindingViewer::WindingViewer(QWidget *parent) : QWidget(parent)
{
SlotNumber=3;
resize(200, 200);
}
void WindingViewer::paintEvent(QPaintEvent *)
{
int side = qMin(width(), height());
QColor SlotColor(127, 127, 127);
QPainter painter(this);
static const QPoint slotpolygonext[4] = {
QPoint(-2,85),
QPoint(-3,95),
QPoint(3, 95),
QPoint(2, 85)
};
static const QPoint slotpolygonint[5] = {
QPoint(-1,75),
QPoint(-2,85),
QPoint(2, 85),
QPoint(1, 75),
QPoint(-1,75),
};
painter.setRenderHint(QPainter::Antialiasing);
painter.translate(width() / 2, height() / 2);
painter.scale(side / 200.0, side / 200.0);
painter.setPen(SlotColor);
for (int i = 0; i < SlotNumber; ++i) {
painter.drawPolyline(slotpolygonext,4);
painter.drawPolyline(slotpolygonint,5);
painter.rotate(360.0/SlotNumber);
}
}
I hope the question is clear enough. I've search for an answer here and over internet before posting. I found few things but nothing totaly related.
The custom widget is part of the window, you could see if manually with the mouse you make the height increase.
And then why is it hidden?
The layouts handle size policies and use the sizeHint() function of the widgets to obtain the default size, and in your case the sizeHint() is not implemented because it is not observed when the window is displayed, the solution is to implement that method:
*.h
public:
explicit WindingViewer(QWidget *parent = nullptr);
QSize sizeHint() const override;
*.cpp
QSize WindingViewer::sizeHint() const
{
return QSize(200, 200);
}
To be more clear explaining my problem, I've done a screenshot with some notes on it, hope it helps:
QGroupBox Layout format problem
As you can see from it, I have one big QVBoxLayout for the main layout of the app, inside it I've put a Qwidget, then a QGridLayout and then a QGridLayout again.
Inside this last one QGridLayout, I've put two QGroupBoxes, one in position 0,0 and one in position 0,1. Each QGroupBox has its own inner Layout, both of QGridLayout type again.
The screenshot shows that the firs QGroupBox works good, while the second one, that's quite smaller than the first, has two problems:
1) The label shoul be "Specific Operations" but it is trunked, and the only way to show it completely seems to be to put the buttons one next to the other horizontally... but I don't want it!
2) I managed to align the QGroupbox on the left of its "grid" but I need it to be on the upper-left corner of it, while it is centered for the moment... How can I achieve this?
Here is part of the code that should help you understand. Here is the kalk.h file:
class Kalk : public QWidget
{
Q_OBJECT
public:
Kalk(QWidget *parent = 0);
private slots:
void kalkChange(QString);
//....
private:
QComboBox *chooser;
QVBoxLayout *mainLayout;
QGridLayout *subLayout;
QGridLayout *operationsLayout;
QGroupBox *baseOperators;
QGridLayout *baseOperatorsLayout;
QGroupBox *specificOperators;
QGridLayout *specificOperatorsLayout;
};
Then the corresponding kalk.cpp file:
Kalk::Kalk(QWidget *parent) : QWidget(parent){
chooser = new QComboBox();
//...
connect(chooser,SIGNAL(currentIndexChanged(QString)),this,SLOT(kalkChange(QString)));
mainLayout = new QVBoxLayout;
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
subLayout = new QGridLayout;
subLayout->setEnabled(false);
subLayout->setSizeConstraint(QLayout::SetFixedSize);
mainLayout->addWidget(chooser);
mainLayout->addLayout(subLayout);
//operationsLayout = new QHBoxLayout;
operationsLayout = new QGridLayout;
operationsLayout->setSizeConstraint(QLayout::SetFixedSize);
baseOperators = new QGroupBox(tr("Base Operations"));
baseOperatorsLayout = new QGridLayout(baseOperators);
baseOperatorsLayout->setSizeConstraint(QLayout::SetFixedSize);
specificOperators = new QGroupBox(tr("Specific Operations"));
specificOperatorsLayout = new QGridLayout(specificOperators);
specificOperatorsLayout->setSizeConstraint(QLayout::SetFixedSize);
operationsLayout->addWidget(baseOperators,0,0);
operationsLayout->setAlignment(baseOperators,Qt::AlignLeft);
operationsLayout->addWidget(specificOperators,0,1);
operationsLayout->setAlignment(specificOperators,Qt::AlignLeft);
mainLayout->addLayout(operationsLayout);
setLayout(mainLayout);
//...
}
In another function I load the buttons inside the Layout of the QGroupBox, but I don't think the problem is here...
I have less than 1 day of experience in QT (that's why I do not know much of it) I have a window full of information (labels, text, buttons, etc) organized by layouts.
I need that after I press one button, all of the components in a window be hidden (which I already did) except for one label which should increase to barely the size of the whole window
Despite I tried modifying the "geometry" attribute (with code) the hidden layouts do not let the label to be increased. I thought also of using the option of layout breaking, but the label losses its dynamism. Could anyone please recommend me anything to do? Thanks.
Has anyone done something like this before. Thanks.
I once provided an answer to SO: Qt - How to create Image that scale with window, and keeps aspect ratio?. The actual intention was to scale an image in a QLabel with original aspect ratio to consume maximum available size.
However, I got the feedback that the suggested solution would not work properly when my Label would be used in a QGridLayout. (This sounds very similar to the issue of the OP.) Hence, I modified the sample to reproduce the issue and fiddled a little bit around with. For me, it seems that resize events of the main window are processed in the QGridLayout but affect layouted image label only partially. (Shrinking is applied but growing not.) Fortunately, I found a very simple work-around: Setting a non-empty frame to the QLabel solved the problem. I had a look into the source code on woboq.org. I hoped to get a hint what the changed frame style would activate (to apply this as fix for my resize issue). Finally, I was not patient enough and put it aside.
Beside of this QLabel in a QGridLayout resize issue, changing the visibility of widgets should cause a proper re-layout. I would prefer show/hide (instead of delete and re-new) as this is surely easier to implement, more efficient, and less error-prone.
I took the old sample code and added a tool button which can be used to toggle the visibilty of some of the layouted widgets:
// Qt header:
#include <QtWidgets>
class LabelImage: public QLabel {
private:
QPixmap _qPixmap, _qPixmapScaled;
public:
void setPixmap(const QPixmap &qPixmap) { setPixmap(qPixmap, size()); }
protected:
virtual void resizeEvent(QResizeEvent *pQEvent);
private:
void setPixmap(const QPixmap &qPixmap, const QSize &size);
};
void LabelImage::resizeEvent(QResizeEvent *pQEvent)
{
QLabel::resizeEvent(pQEvent);
setPixmap(_qPixmap, pQEvent->size());
}
void LabelImage::setPixmap(const QPixmap &qPixmap, const QSize &size)
{
_qPixmap = qPixmap;
_qPixmapScaled = _qPixmap.scaled(size, Qt::KeepAspectRatio);
QLabel::setPixmap(_qPixmapScaled);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QMainWindow qWin;
QToolBar qToolbar;
QAction qCmdTgl(QString::fromUtf8("Decoration"));
qCmdTgl.setCheckable(true);
qCmdTgl.setChecked(true);
qToolbar.addAction(&qCmdTgl);
qWin.addToolBar(&qToolbar);
QGroupBox qBox;
QGridLayout qGrid;
// a macro for the keyboard lazy:
#define Q_LBL_WITH_POS(ROW, COL) \
QLabel qLbl##ROW##COL(QString::fromLatin1(#ROW", "#COL)); \
/*qLbl##ROW##COL.setFrameStyle(QLabel::Raised | QLabel::Box);*/ \
qGrid.addWidget(&qLbl##ROW##COL, ROW, COL, Qt::AlignCenter)
Q_LBL_WITH_POS(0, 0);
Q_LBL_WITH_POS(0, 1);
Q_LBL_WITH_POS(0, 2);
Q_LBL_WITH_POS(1, 0);
LabelImage qLblImg;
qLblImg.setFrameStyle(QLabel::Raised | QLabel::Box);
qLblImg.setAlignment(Qt::AlignCenter);
//qLblImg.setMinimumSize(QSize(1, 1)); // seems to be not necessary
qLblImg.setSizePolicy(
QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
QPixmap qPM;
if (qPM.load("cats.jpg")) qLblImg.setPixmap(qPM);
else {
qLblImg.setText(
QString::fromLatin1("Sorry. Cannot find file 'cats.jpg'."));
}
qGrid.addWidget(&qLblImg, 1, 1, Qt::AlignCenter);
qGrid.setRowStretch(1, 1); // tell QGridLayout to stretch this cell...
qGrid.setColumnStretch(1, 1); // ...prior to other cells (w/ stretch 0)
Q_LBL_WITH_POS(1, 2);
Q_LBL_WITH_POS(2, 0);
Q_LBL_WITH_POS(2, 1);
Q_LBL_WITH_POS(2, 2);
qBox.setLayout(&qGrid);
qWin.setCentralWidget(&qBox);
qWin.show();
// install signal handlers
QObject::connect(&qCmdTgl, &QAction::triggered,
[&](bool on) {
qLbl00.setVisible(on); qLbl01.setVisible(on); qLbl02.setVisible(on);
qLbl10.setVisible(on); qLbl12.setVisible(on);
qLbl20.setVisible(on); qLbl21.setVisible(on); qLbl22.setVisible(on);
});
// run application
return app.exec();
}
I compiled and tested in VS2013 on Windows 10:
After toggling the Decoration tool button:
Note:
Out of curiosity, I commented the line which changes the frame style
qLblImg.setFrameStyle(QLabel::Raised | QLabel::Box);
and again, resizing of image didn't work properly anymore.
You can remove and hide widgets inside a layout using QLayout::removeWidget(*widget); but you do not need to actually remove it. You should use QWidget::hide() for the content to disappear and for the video label's cell to be able to take that space. I think you need to pay attention to the video label's size policy if it does not increase in size. Assuming you have a QGridLayout like so:
label1 label2 label3
label4 videoLabel label5
button1 button2 button3
And let's say, when you click button3, label1, label2 and label4 should all disappear and videoLabel takes the newly created space. I would group the widgets label1, label2, label4 and videoLabel into a single widget having its own sub-layout. I use QSizePolicy::Expaning to make sure my videoLabel takes the maximum space possible. Here is the implementation:
Widget::Widget(QWidget *parent) :
QWidget(parent)
{
setStyleSheet("QLabel{font-size:20px;}");
fullScreen = false; //current fullscreen state
//main grid layout
baseLayout = new QGridLayout(this);
baseLayout->setMargin(0);
baseLayout->setAlignment(Qt::AlignCenter);
setLayout(baseLayout);
//widget container for label1, label2, label4, videolabel
groupWidget = new QWidget();
//sub-layout inside the group layout
subLayout = new QGridLayout();
subLayout->setAlignment(Qt::AlignCenter);
subLayout->setMargin(0);
groupWidget->setLayout(subLayout);
//label and button instantializing. I set background colors to show their sizes
label1 = new QLabel("Label1");
label1->setStyleSheet("background-color:white;");
label2 = new QLabel("Label2");
label2->setStyleSheet("background-color:orange;");
label3 = new QLabel("Label3");
label4 = new QLabel("Label4");
label4->setStyleSheet("background-color:blue;color:white;");
label5 = new QLabel("Label5");
videoLabel = new QLabel("videoLabel");
videoLabel->setStyleSheet("background-color:red;");
videoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
button1 = new QPushButton("button1");
button2 = new QPushButton("button2");
button3 = new QPushButton("button3");
//the grouped widget spans for 2 rows and columns, hence "2,2"
baseLayout->addWidget(groupWidget, 0,0,2,2);
subLayout->addWidget(label1, 0, 0);
subLayout->addWidget(label2, 0, 1);
subLayout->addWidget(label4, 1, 0);
subLayout->addWidget(videoLabel, 1, 1);
//adding rest of the labels and buttons to the base grid
baseLayout->addWidget(label3, 0, 2);
baseLayout->addWidget(label5, 1, 2);
baseLayout->addWidget(button1, 2, 0);
baseLayout->addWidget(button2, 2, 1);
baseLayout->addWidget(button3, 2, 2);
//button3 toggles fullscreen
connect(button3, SIGNAL(clicked(bool)), this, SLOT(onButton3Clicked(bool)));
}
//slot for button3 click
void Widget::onButton3Clicked(bool)
{
if (!fullScreen){
//removing widget from layouts is not really necessary. Make sure to hide
/*subLayout->removeWidget(label1);
subLayout->removeWidget(label2);
subLayout->removeWidget(label4);*/
label1->hide();
label2->hide();
label4->hide();
fullScreen = true;
}
else{
label1->show();
label2->show();
label4->show();
/*subLayout->addWidget(label1, 0, 0);
subLayout->addWidget(label2, 0, 1);
subLayout->addWidget(label4, 1, 0);*/
fullScreen = false;
}
}
I got the following results for this:
Keep in mind there are other approaches to this question. This one need not be necessarily the best when it comes to memory, but it is quite easy to follow.
I'm trying to find the settings or size policy so that each page in my QToolBox instance only takes up the space needed by its content. I've tried everything I could see in the properties for both the instance and for each of the individual pages.
Am I misconstruing the functionality of QToolBox widget or just missing the right setting?
What I am going for is something similar to the accordion fold type widget in Qt Creator:
I can't seem to get this "Sort" page to take only the size needed to display the button and field.
Unfortunately you can't do that directly because it will span all the available space that the title widgets don't occupy. You can emulate what you want by setting a fixed height on the QToolBox if you know the exact height your page(s). But you do not want to do that in practise.
If you want the behavior you ask for then you need to write your own custom control. It doesn't have to be hard. Use a QVBoxLayout and fill into it items of a custom class, let's call it ToolItem, which is a QWidget with a title (perhaps a button to show/hide) and another QWidget for showing the contents that is either visible or not.
The following very simple example will toggle the visibility of the ToolItem when it is clicked. And only when visible will it occupy any space.
class ToolItem : public QWidget {
public:
ToolItem(const QString &title, QWidget *item) : item(item) {
QVBoxLayout *layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(new QLabel(title));
layout->addWidget(item);
setLayout(layout);
item->setVisible(false);
}
protected:
void mousePressEvent(QMouseEvent *event) {
item->setVisible(!item->isVisible());
}
private:
QWidget *item;
};
class ToolBox : public QWidget {
public:
ToolBox() : layout(new QVBoxLayout) {
setLayout(layout);
}
void addItem(ToolItem *item) {
// Remove last spacer item if present.
int count = layout->count();
if (count > 1) {
layout->removeItem(layout->itemAt(count - 1));
}
// Add item and make sure it stretches the remaining space.
layout->addWidget(item);
layout->addStretch();
}
private:
QVBoxLayout *layout;
};
And simple usage of it:
QWidget *window = new QWidget;
window->setWindowTitle("QToolBox Example");
QListWidget *list = new QListWidget;
list->addItem("One");
list->addItem("Two");
list->addItem("Three");
ToolBox *toolBox = new ToolBox;
toolBox->addItem(new ToolItem("Title 1", new QLabel("Some text here")));
toolBox->addItem(new ToolItem("Title 2", list));
toolBox->addItem(new ToolItem("Title 3", new QLabel("Lorem Ipsum..")));
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(toolBox);
window->setLayout(layout);
window->resize(500, 500);
window->show();
You can now tweak it to look like the QToolBox if needed.
Please don't hesitate to ask follow-up questions.
The example shown from Qt Designer may not be using a QToolBox, which behaves more like a stacked tab widget only displaying a single page at a time. The example in Qt Designer appears to be a QTreeWidget with custom drawing or styling.
This is not the complete answer.
I traced down the actual component, it can be included outside designer (kind of). Here is a minimal example showing how to do that (modified from https://github.com/zdenekzc/qtdesigner-integration).
form.h
#ifndef FORM_H
#define FORM_H
#include <QMainWindow>
class FormWindow : public QMainWindow {
Q_OBJECT
public:
explicit FormWindow (QWidget * parent = 0);
};
#endif // FORM_H
form.cc
#include "form.h"
#include <QApplication>
#include <QtDesigner/QtDesigner>
#include <QtDesigner/QDesignerComponents>
FormWindow::FormWindow (QWidget* parent) : QMainWindow (parent) {
QDesignerFormEditorInterface* core = QDesignerComponents::createFormEditor (this);
core->setWidgetBox (QDesignerComponents::createWidgetBox (core, 0));
this->setCentralWidget (core->widgetBox());
}
extern "C" int main (int argc, char * * argv) {
QApplication app (argc, argv);
FormWindow * win = new FormWindow ();
win->show ();
return app.exec();
}
qt-designer.pro
QT += designer
HEADERS = form.h
SOURCES = form.cc
LIBS += -lQt5DesignerComponents
Build it:
mkdir -p build
cd build
qmake-qt5 ../qt5-design.pro
make
./qt5-design
This is obviously not useful by itself unless you want to build a designer but another step towards isolating the actual component.