En el ejemplo anterior realizamos nuestra primera aplicación en Qt, el clásico Hola Mundo, en el cual solamente creamos una etiqueta con el texto “Hola Mundo!” y la mostramos en pantalla. Utilizamos este enfoque debido a que realizamos un programa que sólo serviría de ejemplo, pero en él se pueden apreciar serias deficiencias, tal vez la más notoria es que sólo es posible utilizar un elemento de interfaz gráfica (widget) a la vez, el cual en nuestro caso fue una etiqueta.
En este artículo veremos la forma de solucionar esto: utilizando Layouts.
Los Layouts son objetos que nos permiten organizar los widgets dentro de una ventana, en Qt disponemos de los siguientes tipos básicos de Layouts:
- QHBoxLayout
- QVBoxLayout
- QGridLayout
- QFormLayout
A continuación describiremos brevemente estos layouts y mostraremos un ejemplo básico de su uso.
QHBoxLayout
Nos permite ordenar los widgets en filas, es decir, de manera horizontal.

El código para crear una aplicación con un layout como el de la imagen anterior es el siguiente, explicaremos sólo las líneas de código que no hayan sido vistas a detalle en partes anteriores del tutorial:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| #include <QObject>
#include <QApplication>
#include <QDialog>
#include <QHBoxLayout>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDialog *ventana = new QDialog();
QHBoxLayout *layout = new QHBoxLayout();
QPushButton *boton1 = new QPushButton(QObject::trUtf8("Botón 1"));
QPushButton *boton2 = new QPushButton(QObject::trUtf8("Botón 2"));
QPushButton *boton3 = new QPushButton(QObject::trUtf8("Botón 3"));
layout->addWidget(boton1);
layout->addWidget(boton2);
layout->addWidget(boton3);
ventana->setLayout(layout);
ventana->setWindowTitle("QHBoxLayout");
ventana->show();
return app.exec();
} |
Las líneas 1 a 5 importan las bibliotecas necesarias para esta aplicación, QObject nos proveerá de la función trUtf8() necesaria para escribir cadenas con caracteres Unicode, en nuestro caso, caracteres propios del español como la ñ, o el acento (tilde).
QDialog es una clase que representa un diálogo de aplicación. Utilizamos un diálogo en lugar de una ventana principal buscando mantener la simplicidad del ejemplo. Un diálogo es una ventana mediante la cual el usuario y la aplicación se comunican. Algunos de sus usos son:
- Informar al usuario sobre la ocurrencia de algún evento relevante como una notificación al terminar un proceso o un error al realizarlo.
- Solicitar confirmación para realizar alguna acción como borrar o guardar un archivo.
- Solicitar información, como la palabra a buscar en un diálogo de buscar y reemplazar.
QHBoxLayout es una clase que representa a nuestro layout horizontal. QPushButton es una clase que representa a un botón estándar.
11
12
13
| QDialog *ventana = new QDialog();
QHBoxLayout *layout = new QHBoxLayout();
QPushButton *boton1 = new QPushButton(QObject::trUtf8("Botón 1")); |
En las líneas 11 a 13 creamos un nuevo diálogo, layout y botón mediante la sentencia new para ejecutar el constructor de cada clase, en este caso el constructor del botón es el único que recibe parámetros, este parámetro es una cadena de texto que contenga la etiqueta que debe mostrar el botón. Las líneas 14 y 15 crean los otros botones de la misma forma.
17
18
19
| layout->addWidget(boton1);
layout->addWidget(boton2);
layout->addWidget(boton3); |
En las líneas 17 a 19 colocamos los botones dentro de nuestro layout mediante la función
addWidget(QWidget *widget, int stretch=0, Qt::Alignment aling=0)
Esta función recibe como parámetros un apuntador al widget que deseamos agregar, un entero que indica el indice de “flexibilidad” (stretch) del widget y un elemento de la enumeración Qt::Alignment, los últimos dos parámetros pueden omitirse y en caso de hacerlo se les asiga el valor de cero. El stretch indica la proporción de espacio disponible en el layout que el widget ocupará, es relativo al stretch indicado en los otros widgets que están dentro del layout, entre mayor sea el valor de stretch mayor será el espacio que ocupará el widget. La enumeración Qt:Alignment indica la alineación que tendrá el widget y puede tener los siguiente valores:
- Horizontales: Qt::AlignLeft (izquierda), Qt::AlignRight (derecha), Qt::AlignHCenter (centrado horizontalmente), Qt::AlignJustify (justificado)
- Verticales:Qt::AlignTop (superior/arriba), Qt::AlignBottom (inferior/abajo), Qt::AlignVCenter (centrado verticalmente)
- Bidimensionales:Qt::AlignCenter (centrado horizontal y verticalmente)
Los widgets se colocan en el orden en que se ejecutan las instrucciones, de forma que en este layout el primer botón de izquierda a derecha (la dirección por default de este layout) es boton1 seguido de boton2 y por último boton3. Si queremos invertir el orden de los widgets podemos escribir las instrucciones en orden inverso o podemos indicar que nuestros widgets se añadan de derecha a izquierda con la instrucción
setDirection(QBoxLayout:RightToLeft)
17
18
| ventana->setLayout(layout);
ventana->setWindowTitle("QHBoxLayout"); |
En la línea 17 establecemos nuestro layout como el principal de la ventana, es decir el que indicará como se muestran los widgets en esa ventana. Y por último en la línea 18 indicamos el título que queremos que muestre nuestra ventana, si no se especifica se utilizará el del nombre del archivo.
QVBoxLayout
Nos permite ordenar los widgets en columnas, es decir, de manera vertical.

El código para crear una aplicación con un layout como el de la imagen anterior es el siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| #include <QObject>
#include <QApplication>
#include <QDialog>
#include <QVBoxLayout>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDialog *ventana = new QDialog();
QVBoxLayout *layout = new QVBoxLayout();
QPushButton *boton1 = new QPushButton(QObject::trUtf8("Botón 1"));
QPushButton *boton2 = new QPushButton(QObject::trUtf8("Botón 2"));
QPushButton *boton3 = new QPushButton(QObject::trUtf8("Botón 3"));
layout->addWidget(boton1);
layout->addWidget(boton2);
layout->addWidget(boton3);
ventana->setLayout(layout);
ventana->setWindowTitle("QVBoxLayout");
ventana->show();
return app.exec();
} |
Si lo comparamos con el ejemplo anterior del QHBoxLayout, lo único que cambia es que nuestro objeto layout ahora es de tipo QVBoxLayout, el resultado de esto es que los widgets ahora se organizan de acuerdo a las reglas de este layout, es decir, de arriba hacia abajo. Si queremos invertir el orden en que se agregan los widgets al layout utilizamos la instrucción
setDirection(QBoxLayout::BottomToTop);
QGridLayout
Nos permite ordenar los widgets a manera de tabla o rejilla.

El código para crear una aplicación con un layout similar al de la imagen es:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| #include <QObject>
#include <QApplication>
#include <QDialog>
#include <QGridLayout>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDialog *ventana = new QDialog();
QGridLayout *layout = new QGridLayout();
QPushButton *boton1 = new QPushButton(QObject::trUtf8("Botón 1"));
QPushButton *boton2 = new QPushButton(QObject::trUtf8("Botón 2"));
QPushButton *boton3 = new QPushButton(QObject::trUtf8("Botón 3"));
layout->addWidget(boton1, 0, 0);
layout->addWidget(boton2, 0, 1);
layout->addWidget(boton3, 1, 0);
ventana->setLayout(layout);
ventana->setWindowTitle("QGridLayout");
ventana->show();
return app.exec();
} |
Nuevamente cambiamos el tipo de nuestro objeto layout, ahora será de la clase QGridLayout.
La forma de agregar los widgets a este tipo de layout es ligeramente distinta, ahora lo haremos mediante la función
addWidget(QWidget *widget, int fila, int columna, Qt::Alignment align=0);
Esta función recibe como parámetros un apuntador al widget que deseamos agregar, dos enteros que indican la fila y columna, respectivamente, de la celda sobre la cual queremos agregar el widget en cuestión y un valor de la enumeración Qt::Alignment. Las filas y columnas comienzan a contarse desde cero de tal manera que la primera celda en la esquina superior izquierda tiene la posición fila=0, columna=0.
17
18
19
| layout->addWidget(boton1, 0, 0);
layout->addWidget(boton2, 0, 1);
layout->addWidget(boton3, 1, 0); |
En la línea 17 colocamos al primer botón en la celda definida por la intersección de la fila cero con la columna cero, es decir, posición 0,0. Luego colocamos al boton2 y boton3 en las celdas 0,1 y 1,0 respectivamente.
El espacio vacío en la celda 1,1 se genera debido a que el QGridLayout crea tantas divisiones de filas y columnas como el número máximo de filas o columnas que indiquemos, en este caso dos, debido a la existencia de las filas y columnas uno y cero. Por ejemplo, si añadieramos los botones al layout mediante las líneas
layout->addWidget(boton1, 0, 0);
layout->addWidget(boton2, 1, 1);
layout->addWidget(boton3, 2, 2);
Obtendríamos el siguiente resultado:

Sin embargo habrá que tener en cuenta que las filas o columnas extras no serán visibles a menos que establezcamos el alto/ancho mínimo de cada fila/columna o que coloquemos un widget en la fila/columna correspondiente, en este último caso (que es lo que ha ocurrido en los dos ejemplos anteriores) el QGridLayout asigna automáticamente el alto/ancho mínimo de esta fila/columna con un valor igual al mínimo requerido por el widget en cuestión. Podemos establecer el ancho mínimo de una columna y el alto mínimo de una fila con las funciones:
setColumnMinimumWidth(int columna, int anchoMinimo )
setRowMinimumHeight(int fila, int altoMinimo)
Por ejemplo si añadieramos los botones al layout mediante el siguiente código:
layout->addWidget(boton1, 0, 0);
layout->addWidget(boton2, 1, 1);
layout->addWidget(boton3, 3, 3);
Se crearían cuatro filas y cuatro columnas, es decir, una fila y columna más que en el ejemplo anterior, pero al ejecutar el programa obtendríamos el mismo resultado que se muestra en la imagen pasada. Esto ocurre debido a que aunque la fila y columna con el número dos existen, estas tienen un alto y ancho de cero ya que no hemos colocado ningún widget en ellas ni hemos establecido el tamaño mínimo para ellas. Pero si después de la línea dónde agregamos el último botón al layout añadimos las líneas:
layout->setColumnMinimumWidth(2, 60);
layout->setRowMinimumHeight(2, 20);
Obtendríamos el siguiente resultado:

QFormLayout
Nos permite ordenar los widgets de manera similar a la de formulario web, es decir, filas compuestas por un par de widgets, los cuales normalmente son una etiqueta y un campo de texto.

El código para crear una aplicación con un layout como el de la imagen anterior es el siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| #include <QObject>
#include <QApplication>
#include <QDialog>
#include <QFormLayout>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDialog *ventana = new QDialog();
QVBoxLayout *layout = new QVBoxLayout();
QPushButton *boton1 = new QPushButton(QObject::trUtf8("Botón 1"));
QPushButton *boton2 = new QPushButton(QObject::trUtf8("Botón 2"));
QPushButton *boton3 = new QPushButton(QObject::trUtf8("Botón 3"));
layout->addRow(QObject::trUtf8("Botón 1:"), boton1);
layout->addRow(QObject::trUtf8("Botón 2:"), boton2);
layout->addRow(QObject::trUtf8("Botón 3:"), boton3);
ventana->setLayout(layout);
ventana->setWindowTitle("QVBoxLayout");
ventana->show();
return app.exec();
} |
En este caso nuestro layout será del tipo QFormLayout y para añadir widgets utilizarermos la función
addRow(QLabel *etiqueta, QWidget *widget);
aunque también es posible utilizar la siguiente función sobre cargada, con el fin de evitar crear una etiqueta para cada uno de los widgets que deseeemos agregar:
addRow(const QString &texto, QWidget *widget)
17
18
19
| layout->addRow(QObject::trUtf8("Botón 1:"), boton1);
layout->addRow(QObject::trUtf8("Botón 2:"), boton2);
layout->addRow(QObject::trUtf8("Botón 3:"), boton3); |
En las líneas 17 a 19 agregamos los widgets al layout, utilizamos la versión sobrecargada de addRow() en la que el primer parámetro es una cadena de texto con el fin de ahorrar código, debido a que en este ejemplo no necesitamos que el texto de alguna fila cambie y por lo tanto no es necesario conservar un apuntador hacia las etiquetas. Este tipo de layout es muy útil, por ejemplo, en la creación de formularios para la solicitud de información (como un registro de usuarios o un formulario de comentarios) en dónde debemos de indicar que información debe de capturar el usuario en cada campo.
Layouts Anidados
Normalmente las aplicaciones que desarrollemos no tendrán una interfaz tan sencilla como para ser diseñadas utilizando sólo un layout, para organizar los widgets de maneras más complejas utilizamos layouts anidados, es decir, un layout dentro de otro. La función que nos permite anidar layouts depende del tipo de layout que estemos utilizando, para QHBoxLayout y QVBoxLayout la función es:
addLayout(QLayout *layout, int Qt::Alignment align=0)
Para el QGridLayout además debemos especificar la fila y columna de la celda dónde deseamos agregar el layout, de la siguiente forma
addLayout(QLayout *layout, int fila, int columna, Qt::Alignment align=0)
Para el QFormLayout se utiliza una función sobrecargada de addRow(), en una de las siguientes formas, dependiendo de la apariencia que deseemos lograr:
addRow(QLabel *label, QLayout *layout)
addRow(QString &texto, QLayout *layout)
Está función recibe como parámetros un apuntador a la etiqueta que contiene el texto que describe al layout o simplemente una QString con ese texto y un apuntador al layout que deseamos agregar.
Esta función recibe cómo parámetro un apuntador al layout que deseamos agregar.
A continuación mostraremos un sencillo ejemplo de anidación de layouts para crear una interfaz de usuario muy conocida: un formulario de inicio de sesión.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| #include <QApplication>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QDialog>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDialog *ventana = new QDialog();
QHBoxLayout *layoutUsuario = new QHBoxLayout();
QHBoxLayout *layoutContrasenia = new QHBoxLayout();
QHBoxLayout *layoutBotones = new QHBoxLayout();
QVBoxLayout *layoutPrincipal = new QVBoxLayout();
QLabel *etiquetaUsuario = new QLabel("Usuario");
QLabel *etiquetaContrasenia = new QLabel("Password");
QLineEdit *campoUsuario = new QLineEdit();
QLineEdit *campoContrasenia = new QLineEdit();
QPushButton *botonAceptar = new QPushButton("Aceptar");
QPushButton *botonCancelar = new QPushButton("Cancelar");
layoutUsuario->addWidget(etiquetaUsuario);
layoutUsuario->addWidget(campoUsuario);
layoutContrasenia->addWidget(etiquetaContrasenia);
layoutContrasenia->addWidget(campoContrasenia);
layoutBotones->addStretch();
layoutBotones->addWidget(botonAceptar);
layoutBotones->addWidget(botonCancelar);
layoutPrincipal->addLayout(layoutUsuario);
layoutPrincipal->addLayout(layoutContrasenia);
layoutPrincipal->addLayout(layoutBotones);
ventana->setLayout(layoutPrincipal);
ventana->setWindowTitle(QObject::trUtf8("Iniciar Sesión"));
ventana->show();
return app.exec();
} |
Al ejecutar este código obtendremos una salida parecida a la siguiente imagen

En las líneas 1 a 7 incluimos los archivos de cabecera necesarios para desarrollar la aplicación, mientras que en las líneas 13 a 26 creamos los widgets que utilizaremos. En las líneas 28 y 29 colocamos los widgets del campo usuario en un QHBoxLayout, lo mismo hacemos para los campos de contraseña y para los botones. Con el fin de lograr una interfaz más agradable alineamos los botones a la derecha insertando un espacio en blanco al principio del QHBoxLayout mediante la instrucción
addStretch(int stretch=0);
38
39
40
41
42
| layoutPrincipal->addLayout(layoutUsuario);
layoutPrincipal->addLayout(layoutContrasenia);
layoutPrincipal->addLayout(layoutBotones);
ventana->setLayout(layoutPrincipal); |
A partir de la línea 38 agregamos en el layout principal (el cual es un QVBoxLayout) el layout que contiene los controles de usuario, seguido por el que contiene los controles de contraseña y por último el que contiene los botones y finalmente en la línea 40 establecemos el layout principal de nuestra apliciación.
Es posible diseñar una interfaz de usuario utilizando distintas combinaciones de layouts, es cuestión de tiempo y experiencia poder encontrar aquella que sea más fácil y rápida de crear o la que se adapte mejor a las necesidades de nuestra aplicación. Para el ejemplo anterior un mejor diseño (considerando que requiere menos líneas) puede lograrse utilizando un QFormLayout en lugar de los primeros dos QHBoxLayout. El código es el siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| #include <QApplication>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QDialog>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDialog *ventana = new QDialog();
QHBoxLayout *layoutBotones = new QHBoxLayout();
QFormLayout *layoutPrincipal = new QFormLayout();
QLineEdit *campoUsuario = new QLineEdit();
QLineEdit *campoContrasenia = new QLineEdit();
QPushButton *botonAceptar = new QPushButton("Aceptar");
QPushButton *botonCancelar = new QPushButton("Cancelar");
layoutBotones->addStretch();
layoutBotones->addWidget(botonAceptar);
layoutBotones->addWidget(botonCancelar);
layoutPrincipal->addRow(QString("Usuario"), campoUsuario);
layoutPrincipal->addRow(QString("Password"), campoContrasenia);
layoutPrincipal->addRow(layoutBotones);
ventana->setLayout(layoutPrincipal);
ventana->setWindowTitle(QObject::trUtf8("Iniciar Sesión"));
ventana->setWindowFlags(Qt::Window);
ventana->show();
return app.exec();
} |
Al ejecutar este código obtenemos una salida similar a la siguiente

La cual tiene una apariencia muy similar a la del ejemplo anterior y que en mi opinión incluso se ve mejor, pero lo importante es que el número de líneas utilizadas se redujo un poco, pero es de suponer que en una aplicación de mayor tamaño el ahorro de líneas de código también será mayor.
Puedes continuar este tutorial en el artículo de Singals y Slots en el cual explicamos como agregar funcionalidad a una aplicación de Qt.