Do Layouts become your children when you use setLayout in Qt - c++

Here's the code:
class MyWidget: public QWidget
{
public:
MyWidget();
~MyWidget();
private:
QHBoxLayout* theLayout;
QVBoxLayout* subLayout1;
QVBoxLayout* subLayout2;
//More subLayouts
}
MyWidget::MyWidget()
{
theLayout = new QHBoxLayout();
subLayout1 = new QVBoxLayout();
subLayout2 = new QVBoxLayout();
//More subLayouts
//-------- Fill subLayouts with widgets using addWidget --------
theLayout->addLayout(subLayout1);
theLayout->addLayout(subLayout2);
//add all subLayouts
setLayout(theLayout);
}
MyWidget::~MyWidget()
{
//Destructor with nothing in it
}
**Note: the layouts are members of the class.
So I know that the widgets filled in the subLayouts are now MyWidget's children (or at least that's what I thought if not please tell me) so I don't need to delete them in the destructor (Qt cleans them up right?), but are the layouts also children of MyWidget or do I need to delete these in the destructor?

As the documentation says the setLayout call will reparent the given layout. So this widget will be it's parent, so you don't need to manually delete it.
I think you should use constructors with the argument if possible. And also you don't need to store the layouts as a members if you just initialize them.

Related

Adding widget to layout only works in constructor of custom class

I have a class MainWindow subclassing QMainWindow. As the central widget I have a QScrollArea with a QWidget inside, as a container for my own custom widgets. I set a QVBoxLayout as the layout for the QWidget container and pass this layout to the constructor of my custom class, which is supposed to dynamically create instances of my custom widget class and add them to the layout.
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = Q_NULLPTR);
virtual ~MainWindow ();
private:
QVBoxLayout mainLayout;
QScrollArea scrollArea;
QWidget container;
NotificationControl control;
};
MainWindow::MainWindow (QWidget *parent) :
QMainWindow(parent),
container(&scrollArea),
control(&mainLayout) {
container.setLayout(&mainLayout);
scrollArea.setWidget(&container);
setCentralWidget(&scrollArea);
}
Now in my NotificationControl class I have a method addNotification:
void NotificationControl::addNotification (Notification notif) {
qDebug() << "NotificationControl addNotification" << notif.get_app_name();
NotificationWidget* widget = new NotificationWidget(notif);
container->addWidget(widget);
}
When this method is called, I get the debug output, but nothing is added to the layout. But if I add this to the end of the constructor of NotificationControl...
NotificationWidget* notif = new NotificationWidget(Notification());
container->addWidget(notif);
...for some reason it works. I'm pretty sure the problem isn't the NotificationWidget class. For testing purposes I made some changes, it doesn't actually use the Notification object that is passed at all.
What could be the problem?
Edit: I just noticed that adding widgets later (not in constructor) makes the widgets added in the constructor disappear.
Any widget added as a child widget must be explicitly shown if the parent widget is already visible. Usually, the child widgets are added before the parent widget is shown, and they then become visible themselves, too. But if you add them later, you must make them explicitly visible.

Do I have to delete these pointers?

This is the MainWindow class which I call and use the function show() to make it visible to the user.
class MainWindow : public QMainWindow
{
Q_OBJECT
QWidget *centralWidget;
QGridLayout* gridLayout;
QGridLayout* infoBoxLayout;
QHBoxLayout* buttonGroup;
QHBoxLayout* subCategoryLayout;
//... more widgets
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
void setupUi();
void setupConnections();
private slots:
void add();
void edit();
void remove();
void find();
void clearAll();
void screenshotDesktop();
void screenshotApp();
void currentSubCategoryChanged( const QString& );
void curretCategoryChanged( const int );
void keyPressEvent( QKeyEvent * );
};
I created for each widget (those pointers after the macro Q_OBJECT) a new object on the heap with new. However, I did not delete them anywhere in the program. Does this cause a memory leak in Qt? Or does something from Qt delete them automatically when destroying the class?
If a widget has a parent set, then Qt will handle deleting the widget.
In the case of a MainWindow, when you close it, the MainWindow and its children will be cleaned up, so pass the parent to the widget's constructor: -
QHBoxLayout* buttonGroup = new QHBoxLayout(this); // where this is the parent (MainWindow)
If you create a Widget such as this: -
QHBoxLayout* buttonGroup = new QHBoxLayout;
And haven't passed in the parent, then it will not be cleaned up and you'll have to handle that yourself.
if you add them to the gui hierarchy then they will be cleaned up when the MainWindow is deleted
this is because parents assume ownership over their children (which is set with the various adds of the gui)
so a this->add(centralWidget); will call centralWidget->setParent(this); which will let centralWidget be deleted when MainWindow is deleted
you are free to delete QObjects yourself but beware dangling pointers (QPointer will help here). though I suggest using deleteLater() to ensure no strange behavior when a pointer still lives on the stack.
for more info about the object tree see here
The automatic memory management through parent-child relationships is done by the QObject. QWidget happens to be a QObject, and it so happens that widgets that have parent widgets have the same underlying QObjects as parents.
A QObject with children automatically deletes its children in its destructor.
A QObject or a QWidget may be adopted by another object. For example, adding widgets to a layout will automatically reparent them to the widget the layout is set on. Even if a layout doesn't have a widget set on it yet, the reparenting will be done at the time you add the layout to a widget (or to a layout that has a widget set). It's pretty clever and saves a lot of typing and reduces possibilities for mistakes.
The idiomatic, minimum-typing way of adding widgets to another widget is:
MyWidget() {
QLayout * layout = new QHBoxLayout(this); // set a layout on this widget
layout->addWidget(new QLabel("foo")); // the label is reparented to this
layout->addWidget(new QPushButton("bar")); // the button is reparented to this
}

QDialog deletes its QLayout?

I have a Qt4 application, that has numerous dialogs. I am curious to know whether or not a QDialog deletes its Layout. Take for example:
class MyDialog : public QDialog {
public:
MyDialog(QWidget* _parent = 0) : QDialog(_parent) {
//instantiate some widgets
m_layout = new QGridLayout(this);
setLayout(m_layout)
//add some widgets to the layout
}
~MyDialog() {
//Do I need this code? or will the parent delete the layout?
//delete m_layout;
}
private:
QGridLayout* m_layout;
}
So do I need to write my own destructor? or will the QDialog take care of the memory management of m_layout?
The QDialog will delete the QLayout upon destruction. You do not need to delete the layout in the destructor.
See also: http://doc-snapshot.qt-project.org/qt5-stable/qtwidgets/qwidget.html#setLayout

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)

Getting Parent Layout in Qt

quick question. Is there any way to (easily) retrieve the parent layout of a widget in Qt?
PS: QObject::parent() won't work, for logical reasons.
EDIT:
I'm positive the widget has a parent layout, because I added it to a layout earlier in the code. Now, I have many other layouts in the window and while it is possible for me to keep track of them, I just want to know if there is an easy and clean way to get the parent layout.
EDIT2:
Sorry, "easy and clean" was probably not the best way of putting. I meant using the Qt API.
EDIT3:
I'm adding the widget to the layout like this:
QHBoxLayout* layout = new QHBoxLayout;
layout->addWidget(button);
SOLVED!
Usage: QLayout* parentLayout = findParentLayout(addedWidget)
QLayout* findParentLayout(QWidget* w, QLayout* topLevelLayout)
{
for (QObject* qo: topLevelLayout->children())
{
QLayout* layout = qobject_cast<QLayout*>(qo);
if (layout != nullptr)
{
if (layout->indexOf(w) > -1)
return layout;
else if (!layout->children().isEmpty())
{
layout = findParentLayout(w, layout);
if (layout != nullptr)
return layout;
}
}
}
return nullptr;
}
QLayout* findParentLayout(QWidget* w)
{
if (w->parentWidget() != nullptr)
if (w->parentWidget()->layout() != nullptr)
return findParentLayout(w, w->parentWidget()->layout());
return nullptr;
}
(Updated answer)
I guess it is not easily possible then. Since a Widget can be technically contained in multiple layouts (a horizontal layout which is aligned inside a vertical layout, for instance).
Just remember that a QWidget's parent does not change if it is aligned in a layout.
You possibly have to keep track of that yourself, then.
Simply use:
QHBoxLayout* parentLayout = button->parentWidget()->layout();
I assume button is a child of the widget which contains the layout which contains button. button->parentWidget() returns a pointer to the widget of the button's parent and ->layout() returns the pointer to the layout of the parent.
After some exploration, I found a "partial" solution to the problem.
If you are creating the layout and managing a widget with it, it is possible to retrieve this layout later in the code by using Qt's dynamic properties. Now, to use QWidget::setProperty(), the object you are going to store needs to be a registered meta type. A pointer to QHBoxLayout is not a registered meta type, but there are two workarounds. The simplest workaround is to register the object by adding this anywhere in your code:
Q_DECLARE_METATYPE(QHBoxLayout*)
The second workaround is to wrap the object:
struct Layout {
QHBoxLayout* layout;
};
Q_DECLARE_METATYPE(Layout)
Once the object is a registered meta type, you can save it this way:
QHBoxLayout* layout = new QHBoxLayout;
QWidget* widget = new QWidget;
widget->setProperty("managingLayout", QVariant::fromValue(layout));
layout->addWidget(widget);
Or this way if you used the second workaround:
QHBoxLayout* layout = new QHBoxLayout;
QWidget* widget = new QWidget;
Layout l;
l.layout = layout;
widget->setProperty("managingLayout", QVariant::fromValue(l));
layout->addWidget(widget);
Later when you need to retrieve the layout, you can retrieve it this way:
QHBoxLayout* layout = widget->property("managingLayout").value<QHBoxLayout*>();
Or like this:
Layout l = widget->property("managingLayout").value<Layout>();
QHBoxLayout* layout = l.layout;
This approach is applicable only when you created the layout. If you did not create the layout and set it, then there is not a simple way of retrieving it later. Also you will have to keep track of the layout and update the managingLayout property when necessary.
use widget.parent().layout() and search brute force (recursion included) is my only advice. Maybe you can search be "name".
Have you tried this? Don't forget to check for NULL.
QLayout *parent_layout = qobject_cast< QLayout* >( parent() );
If parent_layout equals NULL, then the parent widget is not a layout.
Have you tried QWidget::layout() ?