Ownership of QAction in a QMenu - c++

I was using Qt to build a basic GUI for an application and I have a few questions..
So I created the GUI, it works fine and I thought I would check something..
for(int i=0; i < 100000; i++)
{
menu = new QMenu(this);
act = new QAction("About", menu);
menu->addAction(act);
connect(act, SIGNAL(triggered()), this, SLOT(slotHelpAbout()));
menuBar()->addMenu(menu)->setText("Help");
}
menuBar()->clear();
I use the QMenuBar of the QMainWindow class and fill it up with QMenu which are also filled up with QAction for wich I connect the triggered signal to a few slots.. This works fine but when I call clear it should delete the menu/action items contained by QMenuBar.. I am checking in task manager and the memory usage is still huge..
Even after:
QList<QAction*> lst = menuBar()->actions();
for(int i=0;i < lst.length(); i++)
{
delete lst.at(i);
}
Shouldn't all memory used by the QMenus and QActions be freed ?

No, they still exist in memory because they will only be deleted when menu is deleted, and menu is only deleted when this (assuming a QMainWindow) is deleted. Calling clear does not delete them.
The reason clear doesn't do this is because (among other things) it supports a scenario like the following: you have named variables referring to the QAction instances, and you want to rearrange them on your menu. You call clear to remove them all, then call addAction with those same actions in the order you desire.
If you want to delete them directly, you can just delete the menu bar. This will delete all the menus and actions parented to the menu bar recursively. Calling menuBar() automatically creates a new one if it does not exist, so you don't even have to worry about that.
#include <QtGui>
int main(int argc, char **argv) {
QApplication app(argc, argv);
QMainWindow m;
QMenu *menu = m.menuBar()->addMenu("test");
for (int i = 0; i < 30000; ++i) {
menu->addAction(QString::number(i)); // memory going up, up, up...
}
delete m.menuBar(); // frees memory
menu = m.menuBar()->addMenu("test2"); // Automatically creates new menu bar
menu->addAction("test 2 action");
m.show();
return app.exec();
}

QMenuBar::clear will do nothing for you since, as pointed out by #Dave Mateer, it only removes actions from the QMenuBar and doesn't delete them.
Also, deleting the list of actions from the QMenuBar will not cause each QMenu itself to be deleted.
Each QMenu that you have is parented to this which is presumably your QMainWindow. They will only be deleted when you delete the QMainWindow and not it's menu bar. You can change your code so that you parent each QMenu to the QMenuBar so that deleting the menu bar deletes the menus (and their actions). Alternatively you can keep hold of pointers to each individual menu and delete them manually.

Related

Do you need to delete widget after removeItemWidget from QTreeWidget?

I have a QTreeWidget with two columns: one for property name and one for property value. The value can be edited via a widget. For example one property is Animal. When you double click the property value column I make a (custom) combobox with different animal types via this code:
QTreeWidgetItemComboBox* comboBox = new QTreeWidgetItemComboBox(treeItem, 1);
// treeitem is a pointer to the row that is double clicked
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
ui.treeWidget->setItemWidget(treeItem, 1, comboBox);
When the row loses focus I remove the widget again (and the value is put as text of the QTreeWidgetItem). For removing I use
ui.treeWidget->removeItemWidget(treeItem, 1);
Now I'm wondering, since I've used new, do I neww to also delete the widget. I know this is the case if you use takeChild(i) for example. But I didn't see something similar for an itemWidget.
Do I need to delete it what would be the right order?
QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1);
// Do I need a cast here since the return type is QWidget*
ui.treeWidget->removeItemWidget(treeItem, 1);
delete comboBox;
or
QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1);
// Do I need a cast here since the return type is QWidget*
delete comboBox;
ui.treeWidget->removeItemWidget(treeItem, 1);
When the widget is added ot the QTreeWidget, it indeed takes ownership of the widget. But it only implies that the widget will be deleted when the parent is destroyed.
So if you just want to remove the widget while keeping the parent QTreeWidget alive, you indeed have to delete it manually.
The correct solution is the first one, remove the widget from the QTreeWidget first, and then delete it with one of the following ways:
delete comboBox;
comboBox = nullptr;
or:
comboBox.deleteLater();
The second one is preferred.
EDIT:
I don't change the answer since it could be a dishonest to change what was already accepted, ...
But as #Scopchanov mentioned, by reading the source code, the QTreeWidget::removeItemWidget() already calls the deleteLater() method on the old widget. We don't have to do it manually.
Anyway, the documentation says it is safe to call deleteLater() more than once:
Note: It is safe to call this function more than once; when the first deferred deletion event is delivered, any pending events for the object are removed from the event queue.
Therefore, manually deleting the widget after calling QTreeWidget::removeItemWidget() becomes useless.
You are not allowed to delete the item widget as the tree is the owner of the widget once it has been passed to the tree with setItemWidget().
From the documentation of setItemWidget():
Note: The tree takes ownership of the widget.
EDIT: In case you want a new widget, simply call setItemWidget() once more or call removeItemWidget() in case you do not need the widget anymore. The tree will ensure that no memory gets lost.
Explaination
You should not manually delete a widget, added to a QTreeWidget, since it is automatically deleted either by
destructing its parent tree widget
This is a direct consequence of the Qt's parent-child mechanism.
calling QTreeWidget::removeItemWidget anytime the tree widget still lives.
This one is not so obvious, since the documentation simply sais:
Removes the widget set in the given item in the given column.
However, looking at the source code it becomes pretty clear what is indeed happening, i.e.
QTreeWidget::removeItemWidget calls QTreeWidget::setItemWidget with a null pointer (no widget)
inline void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column)
{ setItemWidget(item, column, nullptr); }
QTreeWidget::setItemWidget in turn calls QAbstractItemView::setIndexWidget
void QTreeWidget::setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget)
{
Q_D(QTreeWidget);
QAbstractItemView::setIndexWidget(d->index(item, column), widget);
}
Finally QAbstractItemView::setIndexWidget checks if there is already a widget at this index, and if there is one, calls its deleteLater method
if (QWidget *oldWidget = indexWidget(index)) {
d->persistent.remove(oldWidget);
d->removeEditor(oldWidget);
oldWidget->removeEventFilter(this);
oldWidget->deleteLater();
}
Simply put (and this should be made clear in the documentation of both methods of QTreeWidget), any call to QTreeWidget::setItemWidget or QTreeWidget::removeItemWidget deletes the widget (if any) already set for the item.
Example
Here is a simple example I have prepared for you in order to demonstrate the described behaviour:
#include <QApplication>
#include <QBoxLayout>
#include <QTreeWidget>
#include <QComboBox>
#include <QPushButton>
struct MainWindow : public QWidget
{
MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
auto *l = new QVBoxLayout(this);
auto *treeWidget = new QTreeWidget(this);
auto *item = new QTreeWidgetItem(treeWidget);
auto *button = new QPushButton(tr("Remove combo box"), this);
auto *comboBox = new QComboBox();
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
treeWidget->setItemWidget(item, 0, comboBox);
l->addWidget(button);
l->addWidget(treeWidget);
connect(comboBox, &QComboBox::destroyed, [](){
qDebug("The combo box is gone.");
});
connect(button, &QPushButton::clicked, [treeWidget, item](){
treeWidget->removeItemWidget(item, 0);
});
resize(400, 300);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Result
The described ways of destroyng the widget could be tested with the application
Simply closing the window destroys the tree widget together with its child combo box, hence the combo box's destroyed signal is emitted and the lambda prints
The combo box is gone.
After pressing the button the lambda function connected to its clicked signal is called, which removes the combo box from the tree widget. Because the combo box is deleted (automatically) as well, the lambda from the second connect statement is called, which also prints
The combo box is gone.

Qt & Prepending one QMenu to another QMenu

Using Qt5, say I have a control implementing its own context menu. And suppose that under some conditions I want to prepend some items to the standard context menu. So, to do this I create a temporary QMenu, add some stuff to it and the append the standard menu. Something like:
// MyControl is derived from QPlainTextEdit
void MyControl::showContextMenu(const QPoint& pos)
{
// This is QPlainTextEdit::createStandardContextMenu()
QMenu* contextMenu = createStandardContextMenu();
if (someCondition)
{
QMenu* tempMenu = new QMenu(this);
/* add several actions to tempMenu */
tempMenu->addSeperator();
for (auto a : contextMenu->actions)
{
tempMenu->addAction(a);
}
// Feel like I should delete the original QMenu here but doing this
// will delete the QActions it created
// delete contextMenu;
contextMenu = tempMenu;
}
contextMenu->exec(mapToGlobal(pos));
delete contextMenu;
}
My question is, isn't this introducing a memory leak? And if so, what is the correct way to go about this? I can't delete contextMenu before I do contextMenu = newMenu; because that apparently deletes the actions I want.
EDIT:
Ultimately what I want to do is use createStandardContextMenu() which returns a allocated QMenu, and then add some QActions to the top of the menu, and make sure nothing leaks.
QWidget::addAction(QAction*) does not take ownership of the action. Whether deleting contextMenu will result in deletion of actions depends on how createStandardContextMenu is implemented (e. g. QMenu::addAction(QString) does take ownership of the action it creates).
So the actions that are owned by the original menu should be re-parented:
for (auto a : contextMenu->actions)
{
tempMenu->addAction(a);
if (a->parent() == contextMenu){
a->setParent(tempMenu);
}
}
delete contextMenu;
contextMenu = tempMenu;
Use insertAction on the QMenu to insert custom actions before the first standard action, like this:
QMenu* contextMenu = createStandardContextMenu();
QAction* first = contextMenu->actions().at(0);
QAction* customAction = /* Create some custom action */
contextMenu->insertAction(first, customAction);
EDIT: You can then use insertSeparator to separate your custom actions from the first standard action.
Context menu should be modal dialog, so dont use dynamic allocation, and pass "this" to contructor, and build menu depending on passed context
(condition)
{
context.add(...);
}
CustomMenu menu(this,context);
menu.exec(mapToGlobal(point));
no rep to add comment, so
QMenu* tempMenu = new QMenu(this);
no, this wont leak, since you pass pointer of parent object.

Are destructors necessary in QDialogs?

I am following Qt examples (like TabDialog) and I notice that all the UI items are created as pointers - yet I see no delete and no destructor.
Is that right ? Will that not lead to memory leak ?
I am trying to add destructors
~TabDialog()
{
delete tabWidget;
delete buttonBox;
}
and on caller
TabDialog *tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->exec();
But the program crashes when I close the dialog.
Are the destructors, and delete all pointer items, unnecessary or am I doing it wrong ?
I think that you are confused because of these lines:
tabWidget = new QTabWidget;//and so on
You don't see explicit parent (like new QTabWidget(this);), but it is not necessary here. Take a look here:
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(tabWidget);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setLayout will reparent your QVBoxLayout and QVBoxLayout will reparent all widgets inside it, so now your widgets has a parent and they will be destroyed after your dialog.
As doc said:
When you use a layout, you do not need to pass a parent when
constructing the child widgets. The layout will automatically reparent
the widgets (using QWidget::setParent()) so that they are children of
the widget on which the layout is installed.
Note: Widgets in a layout are children of the widget on which the
layout is installed, not of the layout itself. Widgets can only have
other widgets as parent, not layouts.
I'm sorry, but this just doesn't reproduce. The test case is below. You'd probably need to add some extra code to make it reproduce.
Qt's memory management will take care of everything, since all of the widgets end up having parents. Namely:
Tabs are parented as soon as they are passed to addTab.
tabWidget and buttonBox are parented as soon as they are added to the layout.
Since you delete the tabWidget and buttonBox before Qt attempts to delete them, everything is fine. As soon as you delete them, QObject's memory management is informed and they are removed from the TabDialog's child list. I've made this point explicit in the destructor code.
The meaning of Q_ASSERT is: "At this point during runtime, the following must be true". If we're wrong, the debug build will abort. Since it doesn't, the assertions are correct. Thus, before delete tabWidget, the dialog has both QTabWidget and QDialogButtonBox children. After delete tabWidget, the dialog should not have any QTabWidget children anymore. And so on.
#include <QApplication>
#include <QDialog>
#include <QTabWidget>
#include <QDialogButtonBox>
#include <QVBoxLayout>
class TabDialog : public QDialog
{
QTabWidget *tabWidget;
QDialogButtonBox *buttonBox;
public:
TabDialog() :
tabWidget(new QTabWidget),
buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel))
{
tabWidget->addTab(new QWidget, tr("General"));
tabWidget->addTab(new QWidget, tr("Permissions"));
tabWidget->addTab(new QWidget, tr("Applications"));
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(tabWidget);
layout->addWidget(buttonBox);
setLayout(layout);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
}
~TabDialog() {
Q_ASSERT(findChild<QTabWidget*>());
Q_ASSERT(findChild<QDialogButtonBox*>());
delete tabWidget;
Q_ASSERT(! findChild<QTabWidget*>());
Q_ASSERT(findChild<QDialogButtonBox*>());
delete buttonBox;
Q_ASSERT(! findChild<QTabWidget*>());
Q_ASSERT(! findChild<QDialogButtonBox*>());
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TabDialog *tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->exec();
return 0;
}
The only way it'd crash is if you tried the following:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TabDialog *tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->exec();
// At this point `tabDialog` is a dangling pointer.
delete tabDialog; // crash
return 0;
}
Unfortunately, Qt examples are a case of pointless premature pessimization. Qt's classes utilize the PIMPL idiom extensively. Thus the size of, say a QTabWidget is not much larger than that of QObject (48 vs. 16 bytes on my 64 bit platform). By allocating the fixed members of your class on the heap, you're performing two heap allocations: a small one for the QObject-derived class, and then another one for its PIMPL. You're doubling the number of allocations for no good reason.
Here's how to avoid this pessimization:
#include <QApplication>
#include <QDialog>
#include <QTabWidget>
#include <QDialogButtonBox>
#include <QVBoxLayout>
class TabDialog : public QDialog
{
QVBoxLayout m_layout;
QTabWidget m_tabWidget;
QDialogButtonBox m_buttonBox;
QWidget m_generalTab, m_permissionsTab, m_applicationsTab;
public:
TabDialog() :
m_layout(this),
m_buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
{
m_tabWidget.addTab(&m_generalTab, tr("General"));
m_tabWidget.addTab(&m_permissionsTab, tr("Permissions"));
m_tabWidget.addTab(&m_applicationsTab, tr("Applications"));
m_layout.addWidget(&m_tabWidget);
m_layout.addWidget(&m_buttonBox);
connect(&m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(&m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
auto tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->show(); // NOT tabDialog->exec()!!
return app.exec();
}
The less explicit heap allocations, the better. This way you can't even be tempted to delete anything in the destructor, since there are no pointers involved. The compiler generates necessary destructor calls for you automatically.
Furthermore, if you're showing only one window in main, there's no point to explicit heap allocation either:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
TabDialog tabDialog;
tabDialog.show();
return app.exec();
}
Memory handling on Qt
Qt handles the widgets as a tree, every widget has a parent and every parent has
the obligation to free the children memory, if a widget has no parent you should delete it manually with the operator delete.

Qt memory management for containers

I know there is many questions about memory management in Qt. Also I read these SO questions:
Memory Management in Qt
Memory management in Qt?
Qt memory management. What's wrong?
But in my case, I confused again!
I have a QTableWidget with name myTable. I add run-time widgets to it by setCellWidget:
void MyClass::build()
{
for (int i=LOW; i<HIGH; i++)
{
QWidget *widget = new QWidget(myTable);
//
// ...
//
myTable->setCellWidget(i, 0, widget);
}
}
Then, I delete all items like below:
void MyClass::destroy()
{
for (int i = myTable->rowCount(); i >= 0; --i)
myTable->removeRow(i);
}
These methods call many times during long time. And myTable as the parent of those widgets will live along program's life-time.
Dose method destroy() release memory quite and automatically? Or I have to delete allocated widgets myself like below?
void MyClass::destroy2() // This maybe causes to crash !!
{
for (int i = myTable->rowCount(); i >= 0; --i)
{
QWidget *w = myTable->cellWidget(i, 0);
delete w;
myTable->removeRow(i);
}
}
Generally speaking, when in doubt or confused about how to use a class, consult the documentation that should've came with it. Fortunately, QTableWidget::setCellWidget() does in fact come with documentation:
void QTableWidget::setCellWidget ( int row, int column, QWidget * widget )
Sets the given widget to be displayed in the cell in the
given row and column, passing the ownership of the widget to the
table [emphasis mine]. If cell widget A is replaced with cell widget B, cell widget
A will be deleted. For example, in the code snippet below, the
QLineEdit object will be deleted.
setCellWidget(index, new QLineEdit);
...
setCellWidget(index, new QTextEdit);
After the call to myTable->setCellWidget(), the table now owns the widget you passed into it. That means that myTable is responsible for deleting the widgets you pass to setCellWidget(). You don't need to do delete w; when you're removing rows. Your first destroy() function should be sufficient.
From the documentation:
void QTableWidget::setCellWidget ( int row, int column, QWidget * widget )
Sets the given widget to be displayed in the cell in the given row and column, passing the ownership of the widget to the table.
If cell widget A is replaced with cell widget B, cell widget A will be deleted. For example, in the code snippet below, the QLineEdit object will be deleted.
That is, you don't clean up manually, as freeing of resources happens automatically down the Qt object tree, of which your widget has become a part.

Qt - remove all widgets from layout?

This doesn't seem easy. Basically, I add QPushButtons through a function to a layout, and when the function executes, I want to clear the layout first (removing all QPushButtons and whatever else is in there), because more buttons just get appended to the scrollview.
header
QVBoxLayout* _layout;
cpp
void MainWindow::removeButtonsThenAddMore(const QString &item) {
//remove buttons/widgets
QVBoxLayout* _layout = new QVBoxLayout(this);
QPushButton button = new QPushButton(item);
_layout->addWidget(button);
QPushButton button = new QPushButton("button");
_layout->addWidget(button);
QWidget* widget = new QWidget();
widget->setLayout(_layout);
QScrollArea* scroll = new QScrollArea();
scroll->setWidget(widget);
scroll->show();
}
I had the same problem: I have a game app whose main window class inherits QMainWindow. Its constructor looks partly like this:
m_scene = new QGraphicsScene;
m_scene->setBackgroundBrush( Qt::black );
...
m_view = new QGraphicsView( m_scene );
...
setCentralWidget( m_view );
When I want to display a level of the game, I instantiate a QGridLayout, into which I add QLabels, and then set their pixmaps to certain pictures (pixmaps with transparent parts). The first level displays fine, but when switching to the second level, the pixmaps from the first level could still be seen behind the new ones (where the pixmap was transparent).
I tried several things to delete the old widgets. (a) I tried deleting the QGridLayout and instantiating a new one, but then learned that deleting a layout does not delete the widgets added to it. (b) I tried calling QLabel::clear() on the new pixmaps, but that of course had only an effect on the new ones, not the zombie ones. (c) I even tried deleting my m_view and m_scene, and reconstructing them every time I displayed a new level, but still no luck.
Then (d) I tried one of the solutions given above, namely
QLayoutItem *wItem;
while (wItem = widget->layout()->takeAt(0) != 0)
delete wItem;
but that didn't work, either.
However, googling further, I found an answer that worked. What was missing from (d) was a call to delete item->widget(). The following now works for me:
// THIS IS THE SOLUTION!
// Delete all existing widgets, if any.
if ( m_view->layout() != NULL )
{
QLayoutItem* item;
while ( ( item = m_view->layout()->takeAt( 0 ) ) != NULL )
{
delete item->widget();
delete item;
}
delete m_view->layout();
}
and then I instantiate a new QGridLayout as with the first level, add the new level's widgets to it, etc.
Qt is great in many ways, but I do think this problems shows that things could be a bit easier here.
Layout management page in Qt's help states:
The layout will automatically reparent the widgets (using
QWidget::setParent()) so that they are children of the widget on which
the layout is installed.
My conclusion: Widgets need to be destroyed manually or by destroying the parent WIDGET, not layout
Widgets in a layout are children of the widget on which the
layout is installed, not of the layout itself. Widgets can only have
other widgets as parent, not layouts.
My conclusion: Same as above
To #Muelner for "contradiction" "The ownership of item is transferred to the layout, and it's the layout's responsibility to delete it." - this doesn't mean WIDGET, but ITEM that is reparented to the layout and will be deleted later by the layout. Widgets are still children of the widget the layout is installed on, and they need to be removed either manually or by deleting the whole parent widget.
If one really needs to remove all widgets and items from a layout, leaving it completely empty, he needs to make a recursive function like this:
// shallowly tested, seems to work, but apply the logic
void clearLayout(QLayout* layout, bool deleteWidgets = true)
{
while (QLayoutItem* item = layout->takeAt(0))
{
if (deleteWidgets)
{
if (QWidget* widget = item->widget())
widget->deleteLater();
}
if (QLayout* childLayout = item->layout())
clearLayout(childLayout, deleteWidgets);
delete item;
}
}
This code deletes all its children. So everything inside the layout 'disappears'.
qDeleteAll(yourWidget->findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly));
This deletes all direct widgets of the widget yourWidget. Using Qt::FindDirectChildrenOnly is essential as it prevents the deletion of widgets that are children of widgets that are also in the list and probably already deleted by the loop inside qDeleteAll.
Here is the description of qDeleteAll:
void qDeleteAll(ForwardIterator begin, ForwardIterator end)
Deletes all the items in the range [begin, end] using the C++ delete > operator. The item type must be a pointer type (for example, QWidget *).
Note that qDeleteAll needs to be called with a container from that widget (not the layout). And note that qDeleteAll does NOT delete yourWidget - just its children.
Untried: Why not create a new layout, swap it with the old layout and delete the old layout? This should delete all items that were owned by the layout and leave the others.
Edit: After studying the comments to my answer, the documentation and the Qt sources I found a better solution:
If you still have Qt3 support enabled, you can use QLayout::deleteAllItems() which is basically the same as hint in the documentation for QLayout::takeAt:
The following code fragment shows a safe way to remove all items from a layout:
QLayoutItem *child;
while ((child = layout->takeAt(0)) != 0) {
...
delete child;
}
Edit: After further research it looks like both version above are equivalent: only sublayouts and widget without parents are deleted. Widgets with parent are treated in a special way. It looks like TeL's solution should work, you only should be careful not to delete any top-level widgets. Another way would be to use the widget hierarchy to delete widgets: Create a special widget without parent and create all your removeable widgets as child of this special widget. After cleaning the layout delete this special widget.
You also want to make sure that you remove spacers and things that are not QWidgets. If you are sure that the only things in your layout are QWidgets, the previous answer is fine. Otherwise you should do this:
QLayoutItem *wItem;
while (wItem = widget->layout()->takeAt(0) != 0)
delete wItem;
It is important to know how to do this because if the layout you want to clear is part of a bigger layout, you don't want to destroy the layout. You want to ensure that your layout maintains it's place and relation to the rest of your window.
You should also be careful, you're are creating a load of objects each time you call this method, and they are not being cleaned up. Firstly, you probably should create the QWidget and QScrollArea somewhere else, and keep a member variable pointing to them for reference. Then your code could look something like this:
QLayout *_layout = WidgetMemberVariable->layout();
// If it is the first time and the layout has not been created
if (_layout == 0)
{
_layout = new QVBoxLayout(this);
WidgetMemberVariable->setLayout(_layout);
}
// Delete all the existing buttons in the layout
QLayoutItem *wItem;
while (wItem = widget->layout()->takeAt(0) != 0)
delete wItem;
// Add your new buttons here.
QPushButton button = new QPushButton(item);
_layout->addWidget(button);
QPushButton button = new QPushButton("button");
_layout->addWidget(button);
You do not write about going the other way, but you could also just use a QStackedWidget and add two views to this, one for each arrangement of buttons that you need. Flipping between the two of them is a non issue then and a lot less risk than juggling various instances of dynamically created buttons
None of the existing answers worked in my application. A modification to Darko Maksimovic's appears to work, so far. Here it is:
void clearLayout(QLayout* layout, bool deleteWidgets = true)
{
while (QLayoutItem* item = layout->takeAt(0))
{
QWidget* widget;
if ( (deleteWidgets)
&& (widget = item->widget()) ) {
delete widget;
}
if (QLayout* childLayout = item->layout()) {
clearLayout(childLayout, deleteWidgets);
}
delete item;
}
}
It was necessary, at least with my hierarchy of widgets and layouts, to recurse and to delete widgets explicity.
only works for my buttonlist, if the widgets themeselves are deleted, too. otherwise the old buttons are still visible:
QLayoutItem* child;
while ((child = pclLayout->takeAt(0)) != 0)
{
if (child->widget() != NULL)
{
delete (child->widget());
}
delete child;
}
I had a similar case where I have a QVBoxLayout containing dynamically created QHBoxLayout objects containing a number of QWidget instances. For some reason I couldn't get rid of the widgets either by deleting neither the top level QVBoxLayout or the individual QHBoxLayouts. The only solution I got to work was by going through the hierarchy and removing and deleting everything specifically:
while(!vbox->isEmpty()) {
QLayout *hb = vbox->takeAt(0)->layout();
while(!hb->isEmpty()) {
QWidget *w = hb->takeAt(0)->widget();
delete w;
}
delete hb;
}
If you want to remove all widgets, you could do something like this:
foreach (QObject *object, _layout->children()) {
QWidget *widget = qobject_cast<QWidget*>(object);
if (widget) {
delete widget;
}
}
I have a possible solution for this problem (see Qt - Clear all widgets from inside a QWidget's layout). Delete all widgets and layouts in two seperate steps.
Step 1: Delete all widgets
QList< QWidget* > children;
do
{
children = MYTOPWIDGET->findChildren< QWidget* >();
if ( children.count() == 0 )
break;
delete children.at( 0 );
}
while ( true );
Step 2: Delete all layouts
if ( MYTOPWIDGET->layout() )
{
QLayoutItem* p_item;
while ( ( p_item = MYTOPWIDGET->layout()->takeAt( 0 ) ) != nullptr )
delete p_item;
delete MYTOPWIDGET->layout();
}
After step 2 your MYTOPWIDGET should be clean.
If you don't do anything funny when adding widgets to layouts and layouts to other layouts they should all be reparented upon addition to their parent widget. All QObjects have a deleteLater() slot which will cause the QObject to be deleted as soon as control is returned to the event loop. Widgets deleted in this Manor also delete their children. Therefore you simply need to call deleteLater() on the highest item in the tree.
in hpp
QScrollArea * Scroll;
in cpp
void ClearAndLoad(){
Scroll->widget()->deleteLater();
auto newLayout = new QVBoxLayout;
//add buttons to new layout
auto widget = new QWidget;
widget->setLayout(newLayout);
Scroll->setWidget(widget);
}
also note that in your example the _layout is a local variable and not the same thing as the _layout in the header file (remove the QVBoxLayout* part). Also note that names beginning with _ are reserved for standard library implementers. I use trailing _ as in var_ to show a local variable, there are many tastes but preceding _ and __ are technically reserved.
There's some sort of bug in PyQt5 where if you employ the above methods, there remain undeleted items shown in the layout. So I just delete the layout and start over:
E.g.
def run_selected_procedures(self):
self.commandListLayout.deleteLater()
self.commandListLayout = QVBoxLayout()
widget = QWidget()
widget.setLayout(self.commandListLayout)
self.scrollArea.setWidget(widget)
self.run_procedures.emit(self._procSelection)