Proper way to clean QLayout

This is the final part of post series about cleaning QLayout in Qt. In the first part (http://developernode.net/2017/05/20/clean-qlayout-properly-part-one/), I have introduced two ways to clear QLayout. In the second part (http://developernode.net/2017/05/21/clean-qlayout-properly-part-two/), I have shown why these ways are wrong. In summary, the first way just removes QLayoutItem instances, and the second way fails on custom signals handling. So, in this post I will show the way how to delete QLayout properly, and will discuss why this way is the correct way to do so.

Widget must be removed when control returns to the event loop

In the second part, we found out that app crash is caused that delete is called without taking into account such a fact that control must return to the event loop. When control is not in the event loop, it means some event is being handled, and we might face that event handling might be some slot handling the signal. If this is the case, we get app to crash. In other words, we must take care of event loop. So, instead of line delete item->widget();, we should use item->widget()->deleteLater(). Then, according to http://doc.qt.io/qt-5/qobject.html#deleteLater, the object will be deleted when control returns to the event loop.

So, after changing one line, we get the desired result, and no segmentation faults. The code to clear QLayout is the following now:

#include <QLayout>
#include <QLayoutItem>
#include <QWidget>
 
#include "LayoutManagement.h"
 
namespace LayoutManagement
{
    void ClearLayout(QLayout&amp; layout)
    {
        QLayoutItem *item;
        while((item = layout.takeAt(0)))
        {
            // If item is layout itself, we should clear that layout as well.
            if (item->layout())
            {
                ClearLayout(*item->layout());
 
                // After clearing layout, we delete it.
                delete item->layout();
            }
 
            // We check if item has some widget. If so, we also delete that widget.
            if (item->widget())
                item->widget()->deleteLater();
            // Finally, we remove item itself.
            delete item;
        }
    }
}

It turns out to be one small fix to make sure layout is cleared properly. So, reader might ask why do we need three articles to cover such change? That is because no one properly in discussed a way to clear QLayout completely. And also the strange thing here is that Qt documentation itself does not cover such use case which might be very frequent for any Qt developer. And finally, this solution is not complete yet. Yes, after this change app will never crash, but we still have one problem.

Official Illusion of Clearing QLayout

Do you remember the official way of clearing QLayout discussed in the second part? And now we use deleteLater() to clear layout. And from Qt documentation we saw that layout will be cleared when control will return to the event loop. What does it mean? This means that we can create garbled text effect while control did not return to the event loop yet. Mostly, it won’t be a problem, because returning to the event loop will happen fast enough that garbled text effect will not be noticed. However, sometimes heavy loads/reloads might happen (especially, when hundreds of custom widget instances with deep hierarchies are involved). And his might be the case when UI looks very bad for some period of time. And this needs to be solved. But how?

Well, we know exactly that deleteLater() will definitely remove the widget properly. So, the code is correct and complete from this point of view. So, what could we do? Actually, one of ways to solve such problem could be just hiding all the widgets before their removal. In this way, widgets would not be visible, and layout would not look garbled or anything like that event if the widgets are not removed yet.

The code below shows complete solution for clearing QLayout:

// LayoutManagement.h
#pragma once
 
class QLayout;
 
namespace LayoutManagement
{
    void HideAllWidgets(QLayout&amp; layout);
 
    void ClearLayout(QLayout&amp; layout);
}
 
// LayoutManagement.cpp
#include <QLayout>
#include <QLayoutItem>
#include <QWidget>
 
#include "LayoutManagement.h"
 
namespace LayoutManagement
{
    void HideAllWidgets(QLayout&amp; layout)
    {
        for (int i = 0; i < layout.count(); i++) {
            auto item = layout.itemAt(i);
            if (item->layout())
                HideAllWidgets(*item->layout());
 
            if (item->widget())
                item->widget()->hide();
        }
    }
 
    void ClearLayout(QLayout&amp; layout)
    {
        HideAllWidgets(layout);
 
        QLayoutItem* item;
        while((item = layout.takeAt(0)))
        {
            // If item is layout itself, we should clear that layout as well.
            if (item->layout())
            {
                ClearLayout(*item->layout());
 
                // After clearing layout, we delete it.
                delete item->layout();
            }
 
            // We check if item has some widget. If so, we also delete that widget.
            if (item->widget())
                item->widget()->deleteLater();
 
            // Finally, we remove item itself.
            delete item;
        }
    }
}

Final Thoughts

In the series, I have shown a way to clear QLayout in a correct and complete manner without any risk of segmentation fault. Also, this way makes sure all the widgets will be removed with all QLayoutItem instances. Finally, I have shown that, before removal, we should make sure that widgets would not be visible for the actual user, since for a short period of time user might see Qt implementation details, i.e. behavior when not being returned to the event loop.

Leave a Reply

Your email address will not be published. Required fields are marked *