topic logo
Qt ⇢ Unit 6

More on Layouts

vertical, horizontal and grid

Summary

Introduce horizontal and grid layouts and demonstrate how they work.

Background

Layouts are such an essential part of any Qt Widgets application that having a good familiarity with them will bring any Qt developer a long way towards creating pleasant compositions that users will like. That’s why this and the three following units will illustrate all the notable layout classes and demonstrate how to tweak them properly.

New Classes

  • QHBoxLayout: The counterpart to QVBoxLayout. It lays out the items horizontally.
  • QGridLayout: This is the most complex type of layout. It structures it’s items in a two dimensional grid with rows and columns. The layout items can be positioned on the zero indexed layout grid. Individual fields can span multiple columns or rows.
  • QList: The most used generic container template class. It’s a sequential list of values of a given type. Values can be added and removed from any position. Lists can be conveniently iterated through.
  • QString: Qt’s string class. A very versatile, simple to use string class.

Second Layout Example

This unit’s sample application demonstrates three of the four main layout classes and how to replace an existing layout of a widget. The application employs a set of timers to transition between the individual states.

State Changing Application
State Changing Application

The application window consists of a class called “Floorplan”. It is derived from QWidget. It’s header declaration contains the constructor that initializes the window, four slots that are triggered after consecutive timeouts and a list of pointers to labels that are used by looping through them.

State 1: Vertical Layout

floorplan.cpp - constructor
Floorplan::Floorplan()
{
    resize(300, 200);

    auto layout1 = new QVBoxLayout(this);
    for (int i = 0; i<4; i++) {
        auto text = "Label " + QString().setNum(i);
        auto label = new QLabel(text);
        labels << label;
        layout1->addWidget(label);
    }

    auto timer1 = new QTimer(this);
    timer1->setSingleShot(true);
    connect(timer1, &QTimer::timeout, this, &Floorplan::onTimeout1);
    timer1->start(2000);
}

When the program starts, it creates a new vertical layout on the root window, then creates four child labels and adds them to the layout.

There are two curiosities here:

auto text = "Label " + QString().setNum(i);

Qt has it’s own string class called “QString” and basically all strings in Qt are represented by it. It uses Unicode to encode text and has a long list of useful methods.

The expression above uses “QString::setNum()“, which converts a number to a string. Here an empty string is created and then the method sets the value converting the integer value of the loop counter. This is then concatenated with the static prefix string using the overloaded [+] operator.

floorplan.h
private:
    QList<QLabel*> labels;
floorplan.cpp
    labels << label;

Here is another elemental Qt class: the QList template class. It represents a dynamic, sequential list of variables of a defined type. The header in the example code declares a list of pointers to QLabel values. In the label creation loop it appends the pointers to the list with the “<<” overloaded operator. Now they can be addressed by list indexes using the “QList::at()” method.

auto timer1 = new QTimer(this);
timer1->setSingleShot(true);
connect(timer1, &QTimer::timeout, this, &Floorplan::onTimeout1);
timer1->start(2000);

The last part of the constructor sets up a timer that calls a slot two seconds after the application starts, which shifts the layout to the next state.

State 2: Horizontal Layout

floorplan.cpp - horizontal reorganization
void Floorplan::onTimeout1()
{
    delete this->layout();

    auto layout2 = new QHBoxLayout(this);
    foreach(QLabel* label, labels) {
        layout2->addWidget(label);
    }

    [..]
}

This slot is called after the first timeout.

delete this->layout();

It deletes the previously applied layout. The layout pointer of each widget can be obtained with the “QWidget::layout()” method.

auto layout2 = new QHBoxLayout(this);

This creates a new horizontal box layout. It’s in every way the counterpart of the vertical box layout.

foreach(QLabel* label, labels) {
    layout2->addWidget(label);
}

Qt provides the “foreach()” macro as a convenience to quickly loop over the elements of Qt container objects. Here it goes over all widget pointers and adds them to the new layout. This is logically equivalent to the following code:

for(int i=0; i<labels.size(); i++) {
    layout2->addWidget(labels.at(i));
}

The last part of the slot (not part of the listing above) sets up the timer that will trigger the next state just as before.

State 3: Grid Layout

floorplan.cpp - grid layout
void Floorplan::onTimeout2()
{
    [..]

    auto layout3 = new QGridLayout(this);
    layout3->addWidget(labels.at(0), 0, 0);
    layout3->addWidget(labels.at(1), 0, 1);
    layout3->addWidget(labels.at(2), 1, 0);
    layout3->addWidget(labels.at(3), 1, 1);

    [..]
}

This slot mimics the previous one with the exception that this time a grid layout is created. The first line puts the first label at the first row and first column of the layout. The second one puts the second label to the first row and second column. Then the next two lines do the same with the next two widgets one row further. This results in a 2x2 grid of labels on the screen.

State 4: Grid Layout with Span

floorplan.cpp - another grid layout
void Floorplan::onTimeout3()
{
    [..]

    auto layout4 = new QGridLayout(this);
    layout4->addWidget(labels.at(0), 0, 0, 1, 3);
    layout4->addWidget(labels.at(1), 1, 0);
    layout4->addWidget(labels.at(2), 1, 1);
    layout4->addWidget(labels.at(3), 1, 2);
}

This last slot creates a grid like before, but the first element receives two additional parameters for row and column span. It now spans one row (vertically) and three columns (horizontally). The next three labels are added to the second row, so the first one will span over all three of them. This can be compared to joining cells in a spreadsheet.

Conclusion

This unit demonstrated two new layout types in addition to the one previously introduced. While the vertical and horizontal layouts are easy to write and read, the grid layout is more complex and employs a fair amount of magic numbers making it hard to maintain. However it is well suited in situations where widgets are laid out employing loops. Otherwise it’s best to use nested layouts which are explained in one of the next units.

This unit also introduced the QString and QList classes, which you’ll encounter in a lot of places throughout the Qt framework and applications.