Deleting widget that is in layout - c++

What will happen if we will run delete widget for widget that is in layout? If this case was written in documentation, please give me the link (I didn't find).
Code example:
QLabel *l1 = new QLabel("1st");
QLabel *l2 = new QLabel("2nd");
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(l1);
layout->addWidget(l2);
QWidget *mainWidget = new QWidget;
mainWidget->setLayout(layout);
mainWidget->show();
delete l1;
l2->deleteLater();
Can things that will happen be different for l1 and l2?

I believe what you are doing is almost same, though neither would properly remove from layout the way you should be doing it. They are still being left as bad references in the layout (if I remember correctly)
The first one simply deletes the item now. The second will delete it once the control returns back to the event loop. But really, the way people usually remove items from a layout is to take them from the layout (giving it a chance to adjust itself), then delete the item and its widget (if you want).
QLayoutItem *child;
while ((child = layout->takeAt(0)) != 0) {
delete child->widget();
delete child;
}
Again, the deleting of the widget (child->widget()) is only needed if you want to destroy the widget that was added, in addition to the layout item that was holding it.

QLayout's listen for events of type ChildRemoved and remove the items
accordingly. Simply deleting the widget is safe.
by #FrankOsterfeld here.

dont use delete l1 on Qobjects that has active slots connected to them, you will run into a crash.
Use:
l1->hide();
l1->deleteLater();
It works fine for me

Generally, I don't like to delete Qt widgets, rather remove them from the appropriate layout. (Qt will delete its own widgets if you set the Delete on close window attribute. ) The difference between calling delete and delete later is that delete is the normal C++ delete operation that will call the destructor and free the memory associated with the object.
The deleteLater() method, as discussed in the Qt documentation deletes the object when the event loop is entered.

Related

Do I need to setParent to nullptr before deleting QWidget that's owned by QStackedWidget

I have a QStackedWidget which has a bunch of static "pages" but in a couple of cases one page needs to be recreated when it's switched to. Currently I have something like this:
void InsetNavigator::Navigate(InsetPage *page)
{
auto current_page = qobject_cast<InsetPage*>(stacked_widget_->currentWidget());
auto current_idx = stacked_widget_->currentIndex();
current_page->MadeHidden();
stacked_widget_->removeWidget(current_page);
current_page->setParent(nullptr);
delete current_page;
stacked_widget_->insertWidget(current_idx -1, page);
stacked_widget_->setCurrentWidget(page);
page->MadeVisible();
}
My question is, do I need to bother with reparenting the current page to a nullptr before deleting it, or can I just delete the current_page and the QStackedWidget will handle the fact that it's been deleted for me? I don't know if leaving the stacked widget as the parent but deleting the pointer will cause issues.
It depends on how the container-widget is implemented.
But in most-cases if not all, the container-widget (QStackedWidget in OP's case) updates own internal-state automatically once any child-widget is deleted.

Qt remove nested layout

I've got several QHBoxLayout objects nested inside a single QVBoxLayout. I've looked through a number of stackoverflow questions and answers, but I've not been able to find a way to completely remove the layout for the contents of the QScrollArea widget. All the answers I've seen have only made it possible to set the layout again, but when the layout does get set a second time, the objects are still present.
This is the code that I'm working with:
QSignalMapper* sMap = new QSignalMapper(this);
QVBoxLayout* vBox = new QVBoxLayout();
outerVector = 0;
for (vector<vector<QPushButton*>>::iterator o_iter = buttonGrid.begin(); o_iter < buttonGrid.end(); o_iter++) {
int innerVector = 0;
QHBoxLayout* hBox = new QHBoxLayout();
for (vector<QPushButton*>::iterator i_iter = (*o_iter).begin(); i_iter < (*o_iter).end(); i_iter++) {
hBox->addWidget(buttonGrid.at(outerVector).at(innerVector));
sMap->setMapping(buttonGrid.at(outerVector).at(innerVector), ((outerVector * 100) + innerVector));
connect(buttonGrid.at(outerVector).at(innerVector), SIGNAL(clicked()), sMap, SLOT(map()));
innerVector++;
}
vBox->addLayout(hBox);
outerVector++;
}
ui->GameAreaWidgetContents->setLayout(vBox);
connect(sMap, SIGNAL(mapped(int)), this, SLOT(on_buttonGrid_clicked(int)));
Right now, I have this for clearing the layout:
delete hBox;
delete vBox;
ui->GameAreaWidgetContents->layout();
What is the best, and most effective way to clear the contents of the widget?
I believe I've fixed this, This is less of a Qt issue, but more of a lack of clearing the vector<vector<QPushButton*>> buttonGrid object. It looked like the layout wasn't being cleared, because the additional QPushButton objects were being added onto the vector<vector<QPushButton*>> object.
It's a fairly rookie mistake on my behalf.
Updated:
I infer that GameAreaWidgetContents is a QScrollArea. To clear its layout manager, you can do:
delete ui->GameAreaWidgetContents->layout();
The vbox will no longer be the widget's layout manager and any nested children will be deleted automatically by the Qt parenting system.
From the docs on QWidget::setLayout():
If there already is a layout manager installed on this widget, QWidget won't let you install another. You must first delete the existing layout manager (returned by layout()) before you can call setLayout() with the new layout.

Deleting Pointer to widget Qt C++

I am new with Qt and i am very confused about how widgets are deleted. I was reading a video and i wanted to show up a QProgressbar while the video frames are being read and then remove this QProgressbar when the video is loaded.
I have done it with 2 different ways:
Using Pointers
QWidget* wd = new QWidget();
QProgressBar* pB = new QProgressBar(wd);
QLabel* label = new QLabel(wd);
//setting geometry and updating the label and progressbar
wd->deleteLater();
wd->hide();
this code is written inside a class and i was assuming when the destructor of this class is called, the widget will be deleted with all of it's children but that didn't happen and everytime i run this function again a new widget is created without hiding or deleting the previous one (NOTE: i have tried to delete the label and progressbar from the widget assuming that they will disappear from inside the widget but this didn't happen "delete(pB);")
Using Objects
QWidget wd;
QProgressBar pB(&wd);
QLabel label(wd);
//setting geometry and updating the label and progressbar
wd.deleteLater();
wd.hide();
When i have run the same code but using objects instead of pointers , it has run exactly as i have wanted and everytime i run the function, the old widget is destroyed and a new one is created.
NOTE: -Also when i close the main window, in case of pointers, the widget wd still exists and the program doesn't terminate until i close them manually
- In case of Objects, when i close the main window everything is closed and the program is terminated correctly.
I need someone to explain me why is this happening and how if i am having a vector of pointers to widgets to delete all pointers inside that vector without any memory leakage
In typical C++ the rule would be "write one delete for every new". An even more advanced rule would be "probably don't write new or delete and bury that in the RIAA pattern instead". Qt changes the rule in this regard because it introduces its own memory management paradigm. It's based on parent/child relationships. QWidgets that are newed can be given a parentWidget(). When the parentWidget() is destroyed, all of its children will be destroyed. Hence, in Qt it is common practice to allocate objects on the stack with new, give them a parent, and never delete the memory yourself. The rules get more complicated with QLayout and such becomes sometimes Qt objects take ownership of widgets and sometimes they don't.
In your case, you probably don't need the deleteLater call. That posts a message to Qt's internal event loop. The message says, "Delete me when you get a chance!" If you want the class to manage wd just give it a parent of this. Then the whole parent/child tree will get deleted when your class is deleted.
It's all really simple. QObject-derived classes are just like any other C++ class, with one exception: if a QObject has children, it will delete the children in its destructor. Keep in mind that QWidget is-a QObject. If you have an instance allocated usingnew`, you must delete it, or ensure that something (a smart pointer!) does.
Of course, attempting to delete something you didn't dynamically allocate is an error, thus:
If you don't dynamically allocate a QObject, don't deleteLater or delete it.
If you don't dynamically allocate a QObject's children, make sure they are gone before the object gets destructed.
Also, don't hide widgets you're about to destruct. It's pointless.
To manage widget lifetime yourself, you should use smart pointers:
class MyClass {
QScopedPointer<QWidget> m_widget;
public:
MyClass() :
widget{new QWidget};
{
auto wd = m_widget->data();
auto pb = new QProgressBar{wd};
auto label = new QLabel{wd};
}
};
When you destroy MyClass, the scoped pointer's destructor will delete the widget instance, and its QObject::~QObject destructor will delete its children.
Of course, none of this is necessary: you should simply create the objects as direct members of the class:
class MyClass {
// The order of declaration has meaning! Parents must precede children.
QWidget m_widget;
QProgressBar m_bar{&m_widget};
QLabel m_label{&m_widget};
public:
MyClass() {}
};
Normally you'd be using a layout for the child widgets:
class MyClass {
QWidget m_widget;
QVBoxLayout m_layout{&m_widget};
QProgressBar m_bar;
QLabel m_label;
public:
MyClass() {
m_layout.addWidget(&m_bar);
m_layout.addWidget(&m_label);
}
};
When you add widgets to the layout, it reparents them to the widget the layout has been set on.
The compiler-generated destructor looks as below. You can't write such code, since the compiler-generated code will double-destroy the already destroyed objects, but let's pretend you could.
MyClass::~MyClass() {
m_label.~QLabel();
m_bar.~QProgressBar();
m_layout.~QVBoxLayout();
// At this point m_widget has no children and its `~QObject()` destructor
// won't perform any child deletions.
m_widget.~QWidget();
}

When does qt classes work as smart pointers

For example:
QFile* file = new QFile...
If there is no delete file is it memory leak? I ask because I'm new in Qt and reviewing some code I've found this so I wonder if that is sane for Qt classes or not?
Using QFile, there are usually no reason to make it dynamic, but yes -> delete should be here, or else it will leak.
in Qt there are only one exception from mandatory rule "for each new there should be delete".
If you are creating widget with parent like this:
QWidget* w = new QWidget();
QWidget* w2 = new QWidget(w);
Once you you delete w - all it's children (w2 in our case) also will be deleted. This shortens code, but this is only an exception. Rest of stuff - should be deleted.
Edited: Of course, you can use QScopedPointer, or usual std::unique_ptr.
The rule is simple. If QObject has a parent then it will be deleted by parent. If not, you should delete it yourself.

How to remove widgets from layout in Qt

I have a piece of code which does this: a method named prepareUI makes the UI ready to be able to load search results that are fed into it. A method named onClear that is called when the results that are already showing needs to be cleared. And a method named populateSearchResults that takes the search data and loads the UI with it. The container that holds the data is a publicly available pointer, since it is needed to clear the results from onClear:
void MyClass::prepareSearchUI() {
//there may be many search results, hence need a scroll view to hold them
fResultsViewBox = new QScrollArea(this);
fResultsViewBox->setGeometry(28,169,224,232);
fSearchResultsLayout = new QGridLayout();
}
void MyClass::onClear() {
//I have tried this, this causes the problem, even though it clears the data correctly
delete fSearchResultContainer;
//tried this, does nothing
QLayoutItem *child;
while ((child = fSearchResultsLayout->takeAt(0)) != 0) {
...
delete child;
}
}
void MyClass::populateWithSearchesults(std::vector<std::string> &aSearchItems) {
fSearchResultContainer = new QWidget();
fSearchResultContainer->setLayout(fSearchResultsLayout);
for (int rowNum = 0; rowNum < aSearchItems.size(); rowNum++) {
QHBoxLayout *row = new QHBoxLayout();
//populate the row with some widgets, all allocated through 'new', without specifying any parent, like
QPushButton *loc = new QPushButton("Foo");
row->addWidget(loc);
fSearchResultsLayout->addLayout(row, rowNum, 0,1,2);
}
fResultsViewBox->setWidget(fSearchResultContainer);
}
Problem is, when I call onClear which internally calls delete, it does remove all the results that were showing. But after that, if I call populateWithSearchesults again, my app crashes, and the stack trace shows this method as where it crashed.
How do I fix this problem?
It seems that you have some misconceptions about ownership. A QLayout takes ownership of any item that is added to it: http://doc.qt.io/qt-5/qlayout.html#addItem
That means the QLayout is responsible for deleting these items. If you delete them then the QLayout will also try to delete them and then you get the crash you're seeing now.
QLayout doesn't have good functionality for deleting contents and re-adding them (for example removeWidget probably doesn't work as you would hope.) But there's a reason for this.
QLayout is not intended to be used as a list view.
What you do want is a, wait for it, QListView. Which will even handle the scroll functionality for you, and make adding and removing elements a possibility.
Actually you can solve this issue easily, even if you are using QGridLayout or any other Layouts :
subLayout->removeWidget(m_visibleCheckBox);//removes and then it causes some zigzag drawing at (0,0)
m_visibleCheckBox->setVisible(false);//asks to redraw the component internally
setLayout(subLayout);//for safety as we may use that layout again and again
If you just use the first line it will cause this :