Search code examples
c++qtlayout

How do I break layout in Qt by using code instead of Design mode?


So, when I am using Design mode in Qt Creator I can break central widget's layout simply by right-clicking on it and choosing "Break Layout" option:

enter image description here

But how can I do it from the code?

Suppose my central widget was emptied and is set to have QVBoxLayout. How can I break this layout using code?

I looked for the solution in Qt docs but haven't found anything that could help me.

When I tried simply deleting the layout, my app crashed.


Solution

  • Short answer: deleting a layout is indeed the correct way to do what you asked.

    Long answer requires a visit into Qt's source code.

    • QWidget::setLayout won't let you set nullptr as a layout nor will it let you change the layout if another layout is already set on the widget:

      void QWidget::setLayout(QLayout *l)
      {
          if (Q_UNLIKELY(!l)) {
              qWarning("QWidget::setLayout: Cannot set layout to 0");
              return;
          }
          if (layout()) {
              if (Q_UNLIKELY(layout() != l))
                  qWarning("QWidget::setLayout: Attempting to set QLayout \"%s\" on %s \"%s\", which already has a"
                           " layout", l->objectName().toLocal8Bit().data(), metaObject()->className(),
                           objectName().toLocal8Bit().data());
              return;
          }
      [...]
          l->setParent(this);
      [...]
      }
      

      Note that I skipped many lines, unnecessary for my explanation, except for the call to setParent on the layout. It will be important later.

    • QWidget::takeLayout does set a layout to nullptr but is private.

    • The destructor of QLayout is thereore the only way for the widget's layout to be reset to nullptr;

      QLayout::~QLayout()
      {
          Q_D(QLayout);
          if (d->topLevel && parent() && parent()->isWidgetType() && parentWidget()->layout() == this)
              parentWidget()->d_func()->layout = nullptr;
          else if (QLayout *parentLayout = qobject_cast<QLayout *>(parent()))
              parentLayout->removeItem(this);
      }
      

      Note how the destructor uses parent()/parentWidget().

    All of this explains why the help page says you have to delete a a widget's layout before changing it.


    Example

    With the above explanation in mind, it is easy to build a minimum code to demonstrate the layout being broken. You can copy the below code and run it, then uncomment //delete layout; and run it again to see that it indeed breaks the layout.

    #include <QtWidgets/QApplication>
    #include <QtWidgets/QPushButton>
    #include <QtWidgets/QMainWindow>
    #include <QtWidgets/QBoxLayout>
    
    int main(int argc, char** argv)
    {
        QApplication a(argc, argv);
        a.setQuitOnLastWindowClosed(true);
    
        QMainWindow w;
        w.setMinimumSize(100, 80);
        w.setCentralWidget(new QWidget(&w));
    
        QPushButton* button1 = new QPushButton("1"),
                   * button2 = new QPushButton("2");
        //Move a button to avoid overlap when the layout is deleted
        button2->move(20, 50); 
    
        QVBoxLayout* layout = new QVBoxLayout();
        layout->addWidget(button1);
        layout->addWidget(button2);
    
        w.centralWidget()->setLayout(layout);
        //layout->setParent(&w); //Uncommenting this line will make the application crash
        //delete layout;
    
        w.show();
        return a.exec();
    }
    

    Alternatively to w.centralWidget()->setLayout(layout);, you could have written QVBoxLayout* layout = new QVBoxLayout(w.centralWidget()); (as is documented by Qt).


    Regarding the crash

    The only way I found to make the application crash, with no guarantee this is what is happenning for you (see my comment under your question), is to reparent the layout between the call to setLayout and the delete.
    This is because of the notes I mentioned above: if you change the parent like this, it gives no chance to the layout to unset itself from the widget holding it in its destructor (the important part is because it is after callng setLayout).
    You may test the layout's parent is changed, even if you cannot locate where, by calling layout->setParent(w.centralWidget()); right before the `delete. If your application stops crashing, i.e. if the destructor does what it is supposed to, this will be a BIG clue.

    There may be other ways to make the application crash but all of them would be caused by you, not by Qt.
    That does not change the bottom line: deleting the layout is indeed the correct approach and the cause for your application crashing must be sought elsewhere.