Saturday, April 27, 2013

Layout management in Qt4

In this part of the Qt4 programming tutorial, we will talk about the layout management of widgets.
A typical application consists of various widgets. Those widgets are placed inside layouts. A programmer must manage the layout of the application. In Qt4 programming library we have two options.
  • absolute positioning
  • layouts

Absolute Positioning

The programmer specifies the position and the size of each widget in pixels. When you use absolute positioning, you have to understand several things.
  • the size and the position of a widget do not change, if you resize a window
  • applications look different (often crappy) on various platforms
  • changing fonts in your application might spoil the layout
  • if you decide to change your layout, you must completely redo your layout, which is tedious and time consuming
There might be situations, where we can possibly use absolute positioning. For example, this tutorial. We do not want to make the examples too difficult, so we often use absolute positioning to explain a topic. But mostly, in real world programs, programmers use layouts.
absolute.cpp
#include <QApplication>
#include <QDesktopWidget>
#include <QTextEdit>


class Absolute : public QWidget
{
 public:
     Absolute(QWidget *parent = 0);
};


Absolute::Absolute(QWidget *parent)
    : QWidget(parent)
{
  QTextEdit *edit = new QTextEdit(this);
  edit->setGeometry(5, 5, 200, 150);
}

int main(int argc, char *argv[])
{
  QApplication app(argc, argv);

  Absolute window;

  window.setWindowTitle("Absolute");
  window.show();

  return app.exec();
}
The setGeometry() method is used to position the widget on the window in absolute coordinates.
QTextEdit *edit = new QTextEdit(this);
edit->setGeometry(5, 5, 200, 150);
We create a QTextEdit widget and manually position it. The setGeometry() method does two things. It positions the widget to absolute coordinates and resizes the widget.
Before resizement
Figure: before resizement

After resizement
Figure: after resizement

QVBoxLayout

The QVBoxLayout class lines up widgets vertically. The widgets are added to the layout using the addWidget() method.
verticalbox.h
#pragma once

#include <QWidget>

class VerticalBox : public QWidget
{
  public:
    VerticalBox(QWidget *parent = 0);

};
The header file.
verticalbox.cpp
#include <QVBoxLayout>
#include <QPushButton>
#include "verticalbox.h"

VerticalBox::VerticalBox(QWidget *parent)
    : QWidget(parent)
{
  QVBoxLayout *vbox = new QVBoxLayout(this);
  vbox->setSpacing(1);

  QPushButton *settings = new QPushButton("Settings", this);
  settings->setSizePolicy(QSizePolicy::Expanding, 
      QSizePolicy::Expanding);
  QPushButton *accounts = new QPushButton("Accounts", this);
  accounts->setSizePolicy(QSizePolicy::Expanding, 
      QSizePolicy::Expanding);
  QPushButton *loans = new QPushButton("Loans", this);
  loans->setSizePolicy(QSizePolicy::Expanding, 
      QSizePolicy::Expanding);
  QPushButton *cash = new QPushButton("Cash", this);
  cash->setSizePolicy(QSizePolicy::Expanding, 
      QSizePolicy::Expanding);
  QPushButton *debts = new QPushButton("Debts", this);
  debts->setSizePolicy(QSizePolicy::Expanding, 
      QSizePolicy::Expanding);

  vbox->addWidget(settings);
  vbox->addWidget(accounts);
  vbox->addWidget(loans);
  vbox->addWidget(cash);
  vbox->addWidget(debts);

  setLayout(vbox);
}
In our example, we have one vertical layout manager. We put five buttons into it. We make all buttons expandable in both directions.
QVBoxLayout *vbox = new QVBoxLayout(this);
vbox->setSpacing(1);
We create the QVBoxLayout and set 1px spacing among child widgets.
QPushButton *settings = new QPushButton("Settings", this);
settings->setSizePolicy(QSizePolicy::Expanding, 
   QSizePolicy::Expanding);
We create a button and set a size policy for it. The child widgets are managed by the layout manager. By default, the button is expanded horizontally and has a fixed size vertically. If we want to change it, we set a new size policy. In our case, the button is expandable into both directions.
vbox->addWidget(settings);
vbox->addWidget(accounts);
vbox->addWidget(loans);
vbox->addWidget(cash);
vbox->addWidget(debts);
We add the child widgets to the layout manager.
setLayout(vbox);
We set the QVBoxLayout manager for the window.
main.cpp
#include <QApplication>
#include "verticalbox.h"

int main(int argc, char *argv[])
{
  QApplication app(argc, argv); 

  VerticalBox window;

  window.resize(240, 230);
  window.move(300, 300);
  window.setWindowTitle("VerticalBox");
  window.show();
  
  return app.exec();
}
Main file.
QVBoxLayout
Figure: QVBoxLayout

Buttons

In the following example, we display two buttons on the client area of the window. They will be positioned in the right bottom corner of the window.
buttons.h
#pragma once

#include <QWidget>
#include <QPushButton>

class Buttons : public QWidget
{
  public:
    Buttons(QWidget *parent = 0);

  private:
    QPushButton *ok;
    QPushButton *apply;

};
Header file.
buttons.cpp
#include <QVBoxLayout>
#include <QHBoxLayout>
#include "buttons.h"

Buttons::Buttons(QWidget *parent)
    : QWidget(parent)
{
  QVBoxLayout *vbox = new QVBoxLayout(this);
  QHBoxLayout *hbox = new QHBoxLayout();

  ok = new QPushButton("OK", this);
  apply = new QPushButton("Apply", this);

  hbox->addWidget(ok, 1, Qt::AlignRight);
  hbox->addWidget(apply, 0, Qt::AlignRight);

  vbox->addStretch(1);
  vbox->addLayout(hbox);
}
Say we wanted to have two buttons in the right bottom corner of the window.
QVBoxLayout *vbox = new QVBoxLayout(this);
QHBoxLayout *hbox = new QHBoxLayout();
We create two box layout managers. One vertical and one horizontal box layout manager.
ok = new QPushButton("OK", this);
apply = new QPushButton("Apply", this);
We create two buttons.
hbox->addWidget(ok, 1, Qt::AlignRight);
hbox->addWidget(apply, 0, Qt::AlignRight);
The buttons are placed inside the horizontal layout manager. These buttons are right aligned. We use the addWidget() method. The first parameter is the child widget. The second parameter is the stretch factor and the last parameter is alignment. By setting the stretch factor to 1 for the ok button, we give it space from the left side to the right side of the window. The widget does not expand to all space alloted to it. Finally, the Qt::AlignRight constant aligns the widget to the right of the allotted space. We also right aligned the apply button, but it was not necessary. The ok button already pushes the apply button to the right.
vbox->addStretch(1);
vbox->addLayout(hbox);
We put an empty, expandable space into the vertical box by calling the addStretch() method. Then we add the horizontal box layout to the vertical box layout.
main.cpp
#include <QApplication>
#include "buttons.h"

int main(int argc, char *argv[])
{
  QApplication app(argc, argv);  

  Buttons window;

  window.resize(290, 170);
  window.move(300, 300);  
  window.setWindowTitle("Buttons");
  window.show();
  
  return app.exec();
}
Main file.
Buttons
Figure: Buttons

Nesting layouts

The idea of the following example is to show that layout managers can be combined. By combination of even simple layouts we can create sophisticated dialogs or windows. To nest layouts, we utilize the addLayout() method.
layouts.h
#pragma once

#include <QWidget>

class Layouts : public QWidget
{
  public:
    Layouts(QWidget *parent = 0);

};
Header file.
layouts.cpp
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include "layouts.h"

Layouts::Layouts(QWidget *parent)
    : QWidget(parent)
{

  QVBoxLayout *vbox = new QVBoxLayout();
  QHBoxLayout *hbox = new QHBoxLayout(this);

  QListWidget *lw = new QListWidget(this);
  lw->addItem("The Omen"); 
  lw->addItem("The Exorcist");
  lw->addItem("Notes on a scandal");
  lw->addItem("Fargo");
  lw->addItem("Capote");

  QPushButton *add = new QPushButton("Add", this);
  QPushButton *rename = new QPushButton("Rename", this);
  QPushButton *remove = new QPushButton("Remove", this);
  QPushButton *removeall = new QPushButton("Remove All", this);

  vbox->setSpacing(3);
  vbox->addStretch(1);
  vbox->addWidget(add);
  vbox->addWidget(rename);
  vbox->addWidget(remove);
  vbox->addWidget(removeall);
  vbox->addStretch(1);

  hbox->addWidget(lw);
  hbox->addSpacing(15);
  hbox->addLayout(vbox);

  setLayout(hbox);
}
In the example, we create a window that consists of four buttons and one list widget. The buttons are grouped in a vertical column and placed to the right of the list widget. If we resize the window, the list widget is being resized as well.
QVBoxLayout *vbox = new QVBoxLayout();
The QVBoxLayout will be the column for the buttons.
QHBoxLayout *hbox = new QHBoxLayout(this);
The QHBoxLayout will be the base layout for our widgets.
QListWidget *lw = new QListWidget(this);
lw->addItem("The Omen"); 
lw->addItem("The Exorcist");
lw->addItem("Notes on a scandal");
lw->addItem("Fargo");
lw->addItem("Capote");
Here we create the list widget.
QPushButton *add = new QPushButton("Add", this);
QPushButton *rename = new QPushButton("Rename", this);
QPushButton *remove = new QPushButton("Remove", this);
QPushButton *removeall = new QPushButton("Remove All", this);
Here we create our four buttons.
vbox->setSpacing(3);
vbox->addStretch(1);
vbox->addWidget(add);
vbox->addWidget(rename);
vbox->addWidget(remove);
vbox->addWidget(removeall);
vbox->addStretch(1);
The vertical box with four buttons is created. We put some little space among our buttons. Notice that we add a stretch factor to the top and to the bottom of the vertical box. This way, the buttons are vertically centered.
hbox->addWidget(lw);
hbox->addSpacing(15);
hbox->addLayout(vbox);
The list widget and the vertical box of buttons are placed into the horizontal box layout. The addLayout() method is used to add a layout to another layout.
setLayout(hbox);
We set the base layout for the parent window.
main.cpp
#include <QApplication>
#include "layouts.h"

int main(int argc, char *argv[])
{
  QApplication app(argc, argv);  
    
  Layouts window;
  
  window.move(300, 300);
  window.setWindowTitle("Layouts");
  window.show();

  return app.exec();
}
Main file.
Layouts
Figure: Layouts

QGridLayout

The QGridLayout places its widgets in a grid. It is a very sophisticated and powerful layout. Every programmer should be familiar with this layout.
calculator.h
#pragma once

#include <QWidget>

class Calculator : public QWidget
{
  public:
    Calculator(QWidget *parent = 0);

};
Header file.
calculator.cpp
#include <QPushButton>
#include <QGridLayout>
#include "calculator.h"

Calculator::Calculator(QWidget *parent)
    : QWidget(parent)
{
  QGridLayout *grid = new QGridLayout(this);
  grid->setSpacing(2);

  QString values[16] = { "7", "8", "9", "/", 
    "4", "5", "6", "*",
    "1", "2", "3", "-",
    "0", ".", "=", "+"
  };

  int pos = 0;

  for (int i=0; i<4; i++) {
    for (int j=0; j<4; j++) {
      QPushButton *btn = new QPushButton(values[pos], this);
      btn->setFixedSize(40, 40);
      grid->addWidget(btn, i, j);
      pos++;
    }
  }

  setLayout(grid);
}
We create the skeleton of a calculator.
QGridLayout *grid = new QGridLayout(this);
grid->setSpacing(2);
We create the grid layout and set 2px space among child widgets.
QString values[16] = { "7", "8", "9", "/", 
  "4", "5", "6", "*",
  "1", "2", "3", "-",
  "0", ".", "=", "+"
};
These are the characters that will be displayed on the buttons.
for (int i=0; i<4; i++) {
 for (int j=0; j<4; j++) {
   QPushButton *btn = new QPushButton(values[pos], this);
   btn->setFixedSize(40, 40);
   grid->addWidget(btn, i, j);
   pos++;
 }
}
We place 16 widgets into the grid layout. Each of the buttons will have a fixed size.
main.cpp
#include <QApplication>
#include "calculator.h"

int main(int argc, char *argv[])
{
  QApplication app(argc, argv); 

  Calculator window;

  window.move(300, 300);
  window.setWindowTitle("Calculator");
  window.show();

  return app.exec();
}
Main file.
QGridLayout
Figure: QGridLayout

More complicated layout

In the last example of this chapter, we create a more complicated window using the QGridLayout manager.
karenina.h
#pragma once

#include <QWidget>

class Karenina : public QWidget
{
  public:
    Karenina(QWidget *parent = 0);

};
Header file.
karenina.cpp
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QTextEdit>
#include "karenina.h"

Karenina::Karenina(QWidget *parent)
    : QWidget(parent)
{
  QGridLayout *grid = new QGridLayout(this);
  grid->setSpacing(20);

  QLabel *title = new QLabel("Title", this);
  grid->addWidget(title, 0, 0, 1, 1);

  QLineEdit *edt1 = new QLineEdit(this);
  grid->addWidget(edt1, 0, 1, 1, 1);

  QLabel *author = new QLabel("Author", this);
  grid->addWidget(author, 1, 0, 1, 1);

  QLineEdit *edt2 = new QLineEdit(this);
  grid->addWidget(edt2, 1, 1, 1, 1);

  QLabel *review = new QLabel("Review", this);
  grid->addWidget(review, 2, 0, 1, 1);

  QTextEdit *te = new QTextEdit(this);
  grid->addWidget(te, 2, 1, 3, 1);

  setLayout(grid);
}
The code creates a window, which could be used to enter a author, title and a review for a book.
QGridLayout *grid = new QGridLayout(this);
grid->setSpacing(20);
We create a grid layout and set some spacing.
QLabel *title = new QLabel("Title", this);
grid->addWidget(title, 0, 0, 1, 1);
These code lines create a label widget and place it into the grid layout. The addWidget() method has five parameters. The first parameter is the child widget. A label in our case. The next two parameters are the row and column in the grid, where we place the label. Finally, the last parameters are the rowspan and the colspan. These parameter specify, how many rows the current widget will span. In our case, the label will span only one column and one row.
QTextEdit *te = new QTextEdit(this);
grid->addWidget(te, 2, 1, 3, 1);
The QTextEdit widget is placed into the third row, second column. It spans three rows and one column.
main.cpp
#include <QApplication>
#include "karenina.h"

int main(int argc, char *argv[])
{
  QApplication app(argc, argv);  

  Karenina window;

  window.move(300, 300);
  window.setWindowTitle("Anna Karenina");
  window.show();

  return app.exec();
}
Main file.
Karenina
Figure: Karenina
This part of the Qt4 tutorial was dedicated to layout management.

No comments:

Post a Comment