Search code examples
c++user-interfacelayoutqt4

Qt Creating layouts and adding widgets to layouts dynamically


I am trying to create layouts in my MainWindow class dynamically. I have four frames which are laid with a grid layout object. Each frame contains a custom ClockWidget. I want the ClockWidget objects to resize accordingly when I resize the main window, so I need to add them to a layout. However, I need to do this at runtime, since the object itself is created at runtime. I tried to accomplish this programmatically, but the commented-out code below attempting to create a new layout causes the program to crash. What is the procedure for doing this correctly?

Header file:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include "ClockView.h"

namespace Ui{
    class MainWindow;
}

class QLayout;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:

    explicit MainWindow(QWidget *parent = 0);

    ~MainWindow();

    void populateViewGrid();

private:

    Ui::MainWindow *ui;

    ClockView *clockView_1;
    ClockView *clockView_2;
    ClockView *clockView_3;
    ClockView *clockView_4;

    QLayout *layout_1;
    QLayout *layout_2;
    QLayout *layout_3;
    QLayout *layout_4;
};

#endif // MAINWINDOW_H

implementation file:

#include <QVBoxLayout>

#include "MainWindow.h"
#include "ui_MainWindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    populateViewGrid();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::populateViewGrid()
{
    clockView_1 = new ClockView(ui->frame_1);
    clockView_2 = new ClockView(ui->frame_2);
    clockView_3 = new ClockView(ui->frame_3);
    clockView_4 = new ClockView(ui->frame_4);

    /*
    layout_1 = new QVBoxLayout;
    layout_2 = new QVBoxLayout;
    layout_3 = new QVBoxLayout;
    layout_4 = new QVBoxLayout;

    layout1->addWidget(clockView_1);
    layout2->addWidget(clockView_2);
    layout3->addWidget(clockView_3);
    layout4->addWidget(clockView_4);

    ui->frame_1->setLayout(layout_1);
    ui->frame_2->setLayout(layout_2);
    ui->frame_3->setLayout(layout_3);
    ui->frame_3->setLayout(layout_4);
    */
}

Solution

  • Your procedure is correct. There are some typos, for example, you're setting the layout twice for frame3. That may be your problem. Crashes aren't always reproducible. I don't think you have any other problems than that. Below is a self contained example. It also keeps all the instances by value, avoiding the premature pessimization of an extra dereference via a pointer.

    // https://github.com/KubaO/stackoverflown/tree/master/questions/dynamic-widget-10790454
    #include <cmath>
    #include <QtGui>
    #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
    #include <QtWidgets>
    #endif
    #include <array>
    
    // Interface
    
    class ClockView : public QLabel
    {
    public:
        explicit ClockView(QWidget* parent = nullptr) : QLabel(parent)
        {
            static int ctr = 0;
            setText(QString::number(ctr++));
        }
    };
    
    class MainWindow : public QMainWindow
    {
    public:
        explicit MainWindow(QWidget *parent = nullptr);
        void populateViewGrid();
    
    private:
        static constexpr int N = 10;
    
        QWidget central{this};
        QGridLayout centralLayout{&central};
        std::array<QFrame, N> frames;
    
        std::array<ClockView, N> clockViews;
        std::array<QVBoxLayout, N> layouts;
    };
    
    // Implementation
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent)
    {
        setCentralWidget(&central);
    
        const int n = ceil(sqrt(N));
        for (int i = 0; i < N; ++ i) {
            frames[i].setFrameShape(QFrame::StyledPanel);
            centralLayout.addWidget(&frames[i], i/n, i%n, 1, 1);
        }
    
        populateViewGrid();
    }
    
    void MainWindow::populateViewGrid()
    {
        for (int i = 0; i < N; ++ i) {
            layouts[i].addWidget(&clockViews[i]);
            frames[i].setLayout(&layouts[i]);
        }
    }
    
    int main(int argc, char** argv)
    {
        QApplication app{argc, argv};
        MainWindow w;
        w.show();
        return app.exec();
    }
    

    And the qmake project file.

    greaterThan(QT_MAJOR_VERSION, 4) {
        QT = widgets 
        CONFIG += c++11
    } else {
        QT = gui 
        unix:QMAKE_CXXFLAGS += -std=c++11
        macx {
            QMAKE_CXXFLAGS += -stdlib=libc++
            QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.7
        }
    }
    TARGET = dynamic-widget-10790454
    TEMPLATE = app
    SOURCES += main.cpp