Cleaning QLayout (Part Two)

In a previous part (http://developernode.net/2017/05/20/clean-qlayout-properly-part-one/), I have summarized two ways to clear QLayout. These were: “official way” (the one that is described in Qt documentation), and SO way that was proposed by Wes Hardaker. In this post, I intend to do the following:

  1. Define clearing QLayout operation rigorously.
  2. Show why both solutions do not work as expected.

 

Definition of Clearing QLayout

From the first sight, it might look that it is irrelevant to define something simple as removing operation from a specific object. However, this needs to be done properly, since Qt documentation says us that by deleting each instance of QLayoutItem we would remove all items from a QLayout. However, we see nothing like that. And the reason for that is Qt documentation uses different definition for delete operation. So, I must emphasize MY definition of delete from some object.

My definition of delete is the following (in my humble opinion, this definition of delete is for the most of developers the same): When deleted from some container, this container must have no elements in it both visually and both in memory. In QLayout case, this would mean that memory would be freed from the items in it after delete. And also, none of the items would be visible in this layout. So, in order to clear QLayout, we must ensure the following:

  1. Memory must be cleared from items in QLayout.
  2. No items can be visible in QLayout.

Later on, we will see how these insurances are made using both of the two solutions presented in the first blog post

Official Way of Clearing QLayout

Let’s say we have such structure of layout. Layout we would like to clear is vertical layout. This layout consists of two QLabel elements, and one horizontal layout having two QLabel elements, either.

Example structure for layout

Knowing everything we want to achieve, I can start writing some code for clearing our QLayout. Let’s say the header (LayoutManagement.h) for this problem would be this:

#pragma once
class QLayout;
 
namespace LayoutManagement
{
    void ClearLayout(QLayout& layout);
}

Then, implementation of the official way to clear QLayout (LayoutManagement.cpp) would look like this:

#include <QLayout>
#include <QLayoutItem>
 
#include "LayoutManagement.h"
 
namespace LayoutManagement
{
    void ClearLayout(QLayout&amp; layout)
    {
        QLayoutItem* child;
        while ((child = layout.takeAt(0)) != 0)
            delete child;
    }
}

Result of this layout cleanup algorithm would look like this:

Qt documentation way of clearing layout (garbled widgets)

The result we get is widgets garbled on each other. Why is that so? Well, as Qt documentation suggests, this way of layout cleanup just means removing layout items, i.e. instances of QLayoutItem. Documentation says nothing about QWidget instances. So, from what we see, we can assume this solution is not a proper way to clear Qt layout.

Almost Proper Way

Code for this way could be implemented like this:

#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-&gt;layout())
            {
                ClearLayout(*item-&gt;layout());
 
                // After clearing layout, we delete it.
                delete item-&gt;layout();
            }
 
            // We check if item has some widget. If so, we also delete that widget.
            if (item-&gt;widget())
                delete item-&gt;widget();
 
            // Finally, we remove item itself.
            delete item;
        }
    }
}

And this way looks almost well enough. However, in this way it’s not that hard to make app to crash. Lets say we have a vertical layout having custom label which can emit Clicked() signals on mouse release event. Such label can be implemented in the following way:

// CustomLabel.h
#pragma once
 
#include <QLabel>
 
class CustomLabel
    : public QLabel
{
    Q_OBJECT
    public:
        CustomLabel(QWidget* parent = nullptr);
        virtual ~CustomLabel();
 
    signals:
        void Clicked();
 
    protected:
        virtual void mouseReleaseEvent(QMouseEvent* event) override;
};
 
// CustomLabel.cpp
#include <QVariant>
 
#include "CustomLabel.h"
 
CustomLabel::CustomLabel(QWidget* parent)
{
}
 
CustomLabel::~CustomLabel()
{
}
 
void CustomLabel::mouseReleaseEvent(QMouseEvent* event)
{
    // Emit clicked signal.
    emit Clicked();
 
    // After emiting the signal, I set specific property for the label (this line will make app to crash using the following solution)
    this->setProperty("clicked", true);
}

Now we can promote to this label some of our widgets, and, using this label in such a way that, when label is clicked, the layout would be cleared, we would cause app to crash. Example usage of such a label would be the following:

connect(ui->customLabel, &CustomLabel::Clicked, this, [this]
{
    LayoutManagement::ClearLayout(*ui->verticalLayout);
});

When doing like this, we make sure that after sending Clicked() signal, we would go to the event loop, and in that loop call LayoutManagement::ClearLayout(*ui->verticalLayout); would be handled, since this call is part of the slot which handles CustomLabel::Clicked signal. However, clearing layout, using the solution I have found out on SO, completely deletes CustomLabel instance. But we did not return from the event loop which also means that scope of CustomLabel::mouseReleaseEvent(QMouseEvent* event) is still ACTIVE!!! So, we have a scope to which control is being returned from the event loop. In other words, emit Clicked(); is handled, and now after this call, call this->setProperty(“clicked”, true); is being made. However, the instance is already removed, since we cleared layout in this way. And this means we have an app crash here. Instance of the CustomLabel is destroyed, so trying to execute call this->setProperty(“clicked”, true); causes segmentation fault which also means that app crashes. So, we see that the way I have found on stackoverflow is not complete, since this way does not allow the widget instance to clear layout itself when custom signal handling is complete.

Summary

I have taken a look into two ways of clearing QLayout. The first way was using documentation approach, another one was more complete, but proved not good enough, since it is possible to make application to crash when widget being in a layout emits such a custom signal that handler of it clears layout itself.

Leave a Reply

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