Inicio Rápido con Qt 4

Iniciado por Expermicid, Enero 28, 2013, 03:51:30 PM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Enero 28, 2013, 03:51:30 PM Ultima modificación: Enero 25, 2015, 08:59:41 PM por Expermicid
En este tutorial cubrimos de manera rápida y sin entrar en mucho detalle, conceptos básicos de Qt como Layouts o Signals/Slots.

Revisamos la creación de los ejemplos mediante código fuente y mediante Qt Creator.

Introducción a Qt

Qt es un framework para el desarrollo de aplicaciones multiplataforma creado por la compañia Trolltech y que actualmente es propiedad de Nokia, la función más conocida de Qt es la de la creación de interfaces de usuario, sin embargo no se limita a esto, ya que también provee varias clases para facilitar ciertas tareas de programación como el manejo de sockets, soporte para programación multihilo, comunicación con bases de datos, manejo de cadenas de caracteres, entre otras.

Qt utiliza C++ de manera nativa, pero ofrece soporte para otros lenguajes como Python mediante PyQt, Java mediante QtJambi, o C# mediante Qyoto.
Qt es un framework muy poderoso, comparable con Swing de Java o .NET de Microsoft, además ofrece una suite de aplicaciones para facilitar y agilizar las tareas de desarrollo, las aplicaciones que componen esta suite son:


  • Qt Assistant: Herramienta para visualizar la documentación oficial de Qt.
  • Qt Designer: Herramienta WYSIWYG para crear interfaces de usuario.
  • Qt Linguist: Herramienta para la traducción de aplicaciones.
  • Qt Creator: IDE para el lenguaje C++, pero especialmente diseñado para Qt, integra las primeras dos herramientas mencionadas.

Qt es utilizado por empresas como Intel, Google o Dreamworks. Algunos ejemplos de aplicaciones desarrolladas utilizando Qt son:


  • El entorno de escritorio KDE
  • Google Earth
  • Skype
  • Virtual Box

Qt está disponible bajo las siguientes licencias:


  • Qt GNU GPL v. 3.0: Utilizada para el desarrollo de aplicaciones de código abierto. Si se realizan cambios al código fuente de Qt, estos tienen la obligación de liberarse.
  • Qt GNU LGPL v.2.1: Permite el desarrollo de aplicaciones privativas bajo ciertas restricciones. Si se realizan cambios al código fuente de Qt, estos tienen la obligación de liberarse.
  • Commercial: Es la única licencia con costo, es utilizada para el desarrollo de aplicaciones propietarias, incluye soporte y la posibilidad de liberar las aplicaciones desarrolladas bajo cualquier licencia. Si se ralizan cambios al código fuente de Qt, estos no tienen la obligación de ser compartidos.

Instalación de Qt 4

En este artículo revisamos algunas de las formas que existen de instalar y configurar el entorno de desarrollo de Qt, hablamos un poco de sus ventajas y desventajas e indicamos la forma de instalarlo.


  • Instalar Qt utilizando un asistente de instalación
  • Instalar Qt mediante la compilación del código fuente

A continuación, describimos cada una de ellas.

Instalar Qt utilizando un asistente de instalación

Ventajas: Este método también es sencillo de realizar. Permite facilmente mantenerse con la versión más reciente de Qt. Permite instalar versiones anteriores de Qt si es que lo requerimos.

Desventajas: Puede generar un poco de confusión debido a que, dependiendo de los permisos con los que realicemos la instalación, las herramientas de Qt pueden o no ser agregadas al path por el instalador.

Instrucciones: Descargar la versión que deseemos instalar, típicamente la más reciente, desde la página oficial de descargas de Qt: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
En dónde el nombre del instalador suele tener la forma: qt-sdk-[plataforma]-[arquitectura]-[versionlicencia]-[version]-[revision].[extension] Un ejemplo de ello es la versión 4.6.3 opensource de 32 bits para linux que lleva por nombre qt-sdk-linux-x86-opensource-2010.03.bin
Después ejecutamos el instalador y seguimos las instrucciones que nos muestra e indicamos la información que nos pide (path de instalación, aceptar el acuerdo de licencia).
Una vez terminada la instalación podemos ejecutar el entorno de desarrollo integrado Qt Creator y comenzar a crear aplicaciones o revisar los impresionantes ejemplos :D.
Si queremos utilizar las herramientas desde la terminal es necesario ejecutarlas indicando el path en el que están instaladas, por ejemplo en linux, /home/usuario/qt-sdk-qt-sdk-linux-x86-opensource-2010.03/qt/bin/qmake o si lo preferimos agregar al path dicha ruta para sólo indicar el nombre del ejecutable. Si existe más de una versión en el path del sistema suele tomar en cuenta la que aparece primero.
Notas: Este método puede generar algo de confusión con la versión que se esté utilizando para compilar, sobre todo si se utiliza Qt Creator, ya que dentro de él se puede especificar con que versión de las instaladas (y que se le haya indicado previamente) se desea compilar algún proyecto, sea o no la del path.

Instalar Qt mediante la compilación del código fuente

Ventajas: A pesar de ser el método más complicado de los tres, se simplifica bastante gracias al script de configuración y compilación que se incluye, al cual sólo debemos indicarle con que parámetros deseamos incluir o excluir en la construcción de nuestro entorno de desarrollo de Qt.
Suele ser la forma ideal si requerimos alguna característica adicional a las predeterminadas, como soporte para MySQL en Windows, o si deseamos utilizar una versión aún en desarrollo.

Desventajas: Aparte de ser la forma más complicada de las tres, la compilación de Qt toma bastante tiempo, dependiendo del harware de la máquina puede llegar a unas 10 horas o más.
Algunas veces los scripts de instalación presentan algún problema que puede complicar todo ya que no se puede construir mediante los parámetros definidos en el asistente y habrá que buscar que es lo que falla y cómo solucionarlo.

Instrucciones: Descarga el código fuente desde la página oficial de descargas de Qt: http:/qt.nokia.com/downloads
Una vez descargado el archivo, lo descomprimimos, buscamos el script de configuración, de nombre configure, lo ejecutamos y seguimos las instrucciones. Luego esperamos (un largo tiempo) a que termine la instalación.
Podemos agregar una versión compilada al Qt Creator y al path del sistema con el fin de utilizarla de manera más cómoda.

Nuestro Primer Programa en Qt: "Hola Mundo"

En este artículo vamos a crear y explicar a detalle la clásica primera aplicación en programación, esta vez con Qt.
El código de un Hola Mundo en Qt es el siguiente

Código: cpp
#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
  QApplication app(argc, argv);
  QLabel *label = new QLabel("Hola Mundo!");

  label->show();

  return app.exec();
}


Revisaremos el código linea por línea:

Código: cpp
#include <QApplication>
#include <QLabel>


En las primeras dos líneas incluimos los archivos de cabecera que utilizaremos para el ejemplo, QApplication es una clase que representa una aplicación gráfica de Qt, es la encargada de administrar el flujo de ejecución y la configuración principal de la aplicación. QLabel es una clase que representa a un control de interfaz de usuario etiqueta, el cual es generalmente utilizado para mostrar texto no editable en pantalla.

Código: cpp
int main(int argc, char *argv[])


Declaramos nuestra función main en la cual comenzará la ejecución de nuestro programa, es necesario especificar los argumentos de línea de comandos argc (un entero que contiene el número de argumentos) y argv (un arreglo/matriz que contiene el valor de cada uno de los argumentos) ya que al crear un objeto de la clase QApplication es necesario especificarlos.

Código: cpp
QApplication app(argc, argv);


Crea el objeto QApplication llamado app y pasa como parámetros los argumentos de línea de comandos.

Código: cpp
QLabel label("Hola Mundo!");


Crea un objeto QLabel llamado label, y la cual contendrá el texto que queremos mostrar en pantalla, el cual especificamos como argumento del constructor.

Código: cpp
label.show();


Muestra la etiqueta en pantalla. Qt se encargará de crear una ventana para poder mostrar la etiqueta en pantalla ya que nosotros no hemos asignado alguna.

Código: cpp
return app.exec();


Inicia el hilo principal de la aplicación a partir de este momento el framework toma el control de la aplicación y responde a la interacción del usuario con los controles de interfaz gráfica de acuerdo a todo lo especificado anteriormente. La sentencia return regresa al sistema operativo la respuesta de la ejecución del programa una vez que ha finalizado, esta respuesta tipicamente será cero en caso de que la aplicación se haya ejecutado exitosamente.

Construcción de un Proyecto Utilizando la Terminal

Compilación y ejecución utilizando una consola o terminal

Escribimos el texto del ejemplo en un archivo de texto sin formato utilizando una herramienta como el bloc de notas de windows o editores de texto plano de GNU/Linux como GEdit o Kate, guardamos el archivo como holamundo.cpp o cualquier otro nombre con extensión ".cpp". Abrimos una terminal en la ubicación de nuestro archivo holamundo.cpp y ejecutamos los siguientes comandos:

Para crear un archivo de proyecto para nuestra aplicación ejecutamos el comando:

Código: text
qmake -project


Creamos un archivo de proyecto específico para la plataforma o SO en el que estamos trabajando con el comando:

Código: text
qmake


Para especificar el archivo de proyecto a utilizar, muy útil cuando existe más de un archivo de proyecto en nuestro directorio de trabajo actual, ejecutamos el comando:

Código: text
qmake [nombre-de-archivo-de-proyecto].pro


Para generar el archivo ejecutable de nuestra aplicación, ejecutamos el comando:

Código: text
make


o si utilizamos windows el comando será:

Código: text
mingw32-make


Al igual que sucedía con los archivos de proyecto, si existe más de un archivo de construcción de proyecto habrá que especificar el nombre del archivo que deseamos que se procese.
Para ejecutar el archivo ejecutable generado por el compilador al procesar nuestro código, utilizamos el comando:

Código: text
./holamundo


o si utilizamos windows, el comando será:

Código: text
holamundo.exe


Si todo ha ido bien deberías ver una ventana parecida a la siguiente



Construcción de un Proyecto Utilizando Qt Creator

Compilación y Ejecución utilizando Qt Creator

Ejecutamos la aplicación Qt Creator seleccionandola del menú de aplicaciones de nuestro sistema.



Al hacerlo veremos la pantalla de bienvenida de Qt Creator



En ella seleccionamos la opción "New File or Project..." del Menú "File"



Veremos un asistente de creación de nuevo proyecto, para este ejemplo seleccionaremos un proyecto vacío.



En el siguiente paso del asistente seleccionamos las plataformas (targets) para las cuales deseamos construir nuestra aplicación Qt, para este ejemplo nos sirve cualquiera, nosotros elegimos Desktop/Escritorio
En los pasos siguientes del asistente se nos solicitará el nombre del archivo e información sobre control de versiones, para el nombre nos sirve cualquiera, nosotros utilizamos HolaQt.cpp y para el control de versiones por ahora basta con presionar "Finish"



Agregamos un nuevo archivo al proyecto, haciendo clic derecho en la carpeta principal del proyecto y seleccionando la opción "Add New..." del menú contextual que aparece



Escribimos el código del ejemplo y presionamos el botón de ejecutar y construir aplicación. El botón de ejecutar y construir es el botón con una flecha color verde colocado en la parte inferior izquierda de la interfaz de Qt Creator, en la siguiente imagen está debajo del icono de una computadora y tiene un tooltip con el texto "Run Ctrl+R". Ctrl+R es el atajo de teclado para ejecutar la acción que realiza este botón.



Esperamos a que la compilación termine y veremos nuestro programa en ejecución



Layouts de Qt. Organización de los widgets

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 ese ejemplo tiene el gran inconveniente de 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 hablaremos acerca de los Layouts, los cuales nos ayudarán a solucionar situaciones como las del ejemplo anterior.

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

En las secciones de esta parte del tutorial describiremos brevemente estos layouts y mostraremos un ejemplo básico de su uso.

También veremos que es posible anidar los layouts con el fin de crear interfaces de usuario más complejas que las que se pueden lograr utilizando un sólo layout.

QHBoxLayout Organizar Widgets de Manera Horizontal

El 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:

Código: cpp
#include <QObject>
#include <QApplication>
#include <QDialog>
#include <QHBoxLayout>
#include <QPushButton>

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

    QDialog ventana;
    QHBoxLayout *layout = new QHBoxLayout(&ventana);
    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.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 y los revisamos en un artículo siguiente del tutorial. 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 típico.

Código: cpp
QDialog ventana;
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 el parámetro del constructor del layout horizontal es un apuntador a su widget padre, sobre el cual el layout tendrá efecto, el parámetro del constructor del botón es la cadena de texto que mostrará el botón. Las líneas 14 y 15 crean los otros botones de la misma forma.

Código: cpp
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

Código: cpp
addWidget(QWidget *widget, int stretch=0, Qt::Alignment aling=0)


Esta versión de esta función recibe como parámetros un apuntador al widget que deseamos agregar, un entero que indica el índice 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 asigna 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)

Código: cpp
ventana.setLayout(layout);
ventana.setWindowTitle("QHBoxLayout");


Y por último en la línea 21 indicamos el título que queremos que muestre nuestra ventana, si no se especifica se utilizará el del nombre del archivo.

QVBoxLayout Organizar los Widgets de Manera Vertical

El 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:

Código: cpp
#include <QObject>
#include <QApplication>
#include <QDialog>
#include <QVBoxLayout>
#include <QPushButton>

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

    QDialog ventana;
    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

Código: cpp
setDirection(QBoxLayout::BottomToTop)


QGrid Layout. Organizar los Widgets en Forma Tabular

El 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:

Código: cpp
#include <QObject>
#include <QApplication>
#include <QDialog>
#include <QGridLayout>
#include <QPushButton>

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

    QDialog ventana;
    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, 1);

    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

Código: cpp
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.

Código: cpp
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,1 respectivamente.
El espacio vacío en la celda 1,0 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

Código: cpp
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:

Código: cpp
setColumnMinimumWidth(int columna, int anchoMinimo )
setRowMinimumHeight(int fila, int altoMinimo)


Por ejemplo si añadieramos los botones al layout mediante el siguiente código:

Código: cpp
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:

Código: cpp
layout->setColumnMinimumWidth(2, 60);
layout->setRowMinimumHeight(2, 20);


Obtendríamos el siguiente resultado:



QFormLayout. Organizar los Widgets a Manera de Formulario

El 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:

Código: cpp
#include <QObject>
#include <QApplication>
#include <QDialog>
#include <QFormLayout>
#include <QPushButton>

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

    QDialog ventana;
    QFormLayout *layout = new QFormLayout(&ventana);
    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.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

Código: cpp
addRow(QLabel *etiqueta, QWidget *widget)


aunque también es posible utilizar la siguiente función sobre cargada, con el fin de evitar que nosotros tengamos que crear una etiqueta para cada uno de los widgets que deseeemos agregar:

Código: cpp
addRow(const QString &amp;texto, QWidget *widget)


Código: cpp
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. Interfaces de Usuario Complejas en Qt

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:

Código: cpp
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

Código: cpp
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:

Código: cpp
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.

Código: cpp
addRow(QLayout *layout)


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.

Código: cpp
#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;
    QHBoxLayout *layoutUsuario = new QHBoxLayout;
    QHBoxLayout *layoutContrasenia = new QHBoxLayout;
    QHBoxLayout *layoutBotones = new QHBoxLayout;
    QVBoxLayout *layoutPrincipal = new QVBoxLayout(&ventana);

    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.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

Código: cpp
addStretch(int stretch=0);


Código: cpp
layoutPrincipal->addLayout(layoutUsuario);
layoutPrincipal->addLayout(layoutContrasenia);
layoutPrincipal->addLayout(layoutBotones);


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.
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:

Código: cpp
#include <QApplication>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QDialog>

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

    QDialog ventana;
    QHBoxLayout *layoutBotones = new QHBoxLayout;
    QFormLayout *layoutPrincipal = new QFormLayout(&ventana);

    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.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 pero que luce un poco mejor en algunos aspectos, lo relevante para este ejemplo es que el número de líneas utilizadas se redujo un poco, y es de suponer que en una aplicación de mayor tamaño el ahorro de líneas de código también será mayor.

Signals y Slots. Implementando la Funcionalidad de una Aplicación Qt

En la parte anterior de este tutorial vimos como utilizar layouts para diseñar la interfaz gráfica de nuestras aplicaciones y para ejemplificarlo diseñamos un diálogo de inicio de sesión.





En esta parte del tutorial aprenderemos a implementar la funcionalidad de nuestras aplicaciones utilizando Señales y Slots, los cuales forman el mecanismo a través del cual los objetos de una aplicación de Qt (incluyendo widgets) se comunican. Este mecanismo es una de la características distintivas de Qt, ya que otros frameworks suelen utilizar un enfoque basado en callbacks.

Una señal es una notificación que un objeto emite cuándo cambia su estado de manera que podría interesarle a otros objetos. Un slot es una función que se ejecuta cuándo una señal se emite.

Cualquier objeto en el que deseemos implementar señales y slots debe de heredar de la clase QObject, los widgets que utilizaremos normalmente cumplen con esto ya que heredan de QWidget que a su vez hereda de QObject. Los widgets que nos proporciona Qt poseen señales y slots predefinidos, pero también es posible crear un widget personalizado con el fin de añadir nuestras propias señales y slots.

A continuación mostramos el código de nuestra aplicación de inicio de sesión, en el cuál utilizamos señales y slots para implementar su funcionalidad, esto es que al capturar un nombre de usuario registrado y su contraseña correspondiente se nos permita el acceso a una sección restringida de una aplicación y que al capturar un nombre de usuario no registrado o una contraseña no coincidente se niegue el acceso.

En este ejemplo utilizaremos dos señales y dos slots, recordemos que las señales se emiten cuándo un objeto cambia de estado de manera interesante para otros objetos, en este caso serán emitidas cuando se presione alguno de los botones; Cada una de estas señales está conectada a un slot diferente, el cual se ejecuta cuando el botón respectivo es presionado. Más adelante veremos el código de estos slots.

En esta ocasión nuestra aplicación se compone de tres archivos, un archivo de definición de la clase, uno de implementación y un archivo main, definimos una nueva clase debido a que necesitamos un diálogo personalizado para implementar el slot que se ejecutará cuándo se presione el botón Aceptar en nuestra ventana de login, el slot que se ejecuta cuando se presiona el botón cancelar (close() el cual cierra la ventana) ya está predefinido.

NOTA: Sólo revisaremos a detalle las secciones que no hayan sido explicadas en partes anteriores del tutorial o que sean relevantes para esta explicación

El contenido de login.h, el archivo de definición de clase, es el siguiente:

Código: cpp
#ifndef LOGIN_H
#define LOGIN_H

#include <QDialog>

class QPushButton;
class QLineEdit;

class Login : public QDialog
{
    Q_OBJECT

public:
    Login(QWidget *parent=0);

private slots:
    void verificarDatos();

private:
    QPushButton *botonAceptar, *botonCancelar;
    QLineEdit *campoUsuario, *campoPassword;
};
#endif // LOGIN_H


En la línea 4 incluimos el archivo de cabecera de QDialog el cuál necesitamos debido a que nos basaremos en él para construir nuestro diálogo.

Código: cpp
#include <QDialog>


En las líneas 6 y 7 indicamos al compilador que utilizaremos las clases QPushButton y QLineEdit, pero no especificamos sus detalles, los cuales se encuentran en sus archivos .h propios, es posible hacer esto debido a que declaramos las variables como tipo apuntador y a que en este archivo sólo las definimos pero no las utilizamos. También hubiera sido posible incluir los archivos de cabecera pero el sólo indicar el nombre de las clases permite reducir el tiempo de compilación.

Código: cpp
class QPushButton;
class QLineEdit;


En la línea 9 definimos nuestra nueva clase, la cual tendrá el nombre de Login y heredará de QDialog.

Código: cpp
class Login : public QDialog


En la línea 11 utilizamos una macro llamada Q_OBJECT la cual es necesaria en cada clase que utilice señales y slots. Esta macro define algunas funciones de instrospección utilizadas por otras funciones como connect() y es necesaria para que el código de Qt sea traducido por el compilador meta objeto (moc, por sus siglas en inglés) a código C++ estándar.

Código: cpp
Q_OBJECT


A partir de la línea 13 declalramos las funciones y los miembros de nuestra clase. En la línea 14 declaramos el constructor de nuestra clase.

Código: cpp
public:
    Login();


En la línea 17 definimos nuestro slot personalizado, el cual lleva por nombre verificarDatos(), se ejecuta cuándo se presione el botón Aceptar y como su nombre lo indica se encarga de verificar que los datos ingresados sean correctos.

Código: cpp
void verificar();


En las líneas 20 y 21 declaramos los widgets que utilizaremos en nuestra aplicación, dos campos de texto, uno para el nombre de usuario y otro para la contraseña y dos botones, uno para solicitar la verificación de los datos capturados y otro para salir, que sólo se provee por comodidad.

Código: cpp
QPushButton *botonAceptar, *botonCancelar;
    QLineEdit *campoUsuario, *campoPassword;


El contenido del archivo login.cpp es el siguiente:

Código: cpp
#include "login.h"

#include <QApplication>
#include <QPushButton>
#include <QLineEdit>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QMessageBox>

Login::Login(QWidget *parent)
    : QDialog(parent)
{
    QFormLayout *layoutPrincipal = new QFormLayout;
    QHBoxLayout *layoutBotones = new QHBoxLayout;

    botonAceptar = new QPushButton(trUtf8("Aceptar"));
    botonCancelar = new QPushButton(trUtf8("Cancelar"));

    campoUsuario = new QLineEdit();
    campoPassword = new QLineEdit();

    layoutBotones->addStretch();
    layoutBotones->addWidget(botonAceptar);
    layoutBotones->addWidget(botonCancelar);

    layoutPrincipal->addRow(trUtf8("Usuario"), campoUsuario);
    layoutPrincipal->addRow(trUtf8("Contraseña"), campoPassword);
    layoutPrincipal->addRow(layoutBotones);

    setLayout(layoutPrincipal);

    connect(botonAceptar, SIGNAL(clicked()),
            this, SLOT(verificarDatos()));

    connect(botonCancelar, SIGNAL(clicked()),
            this, SLOT(close()));
}

void Login::verificarDatos()
{
    if(campoUsuario->text() == "zonaqt" && campoPassword->text() == "zonaqt"){
        QMessageBox::information(this, "", trUtf8("Bienvenido a la aplicación"));
        QApplication::quit();
    }
    else
        QMessageBox::warning(this, "", trUtf8("La combinación usuario/contraseña no es válida"));
}


En la línea 1 incluimos el archivo de definición de nuestra clase.

Código: cpp
#include "login.h"


En la línea 10 comienza la implementación del constructor de nuestra clase, en él inicializamos los widgets y layouts necesario para la aplicación y definimos la apariencia de nuestro widget de la misma forma que hicimos en la sección anterior del tutorial.

Código: cpp
Login::Login()
{
    QFormLayout *layoutPrincipal = new QFormLayout;
    QHBoxLayout *layoutBotones = new QHBoxLayout;
    ...


Las líneas 30 y 31 son las que presentan novedades.

Código: cpp
connect(botonAceptar, SIGNAL(clicked()),this, SLOT(verificar()));
connect(botonCancelar, SIGNAL(clicked()),this, SLOT(close()));


En ellas utilizamos la función:

Código: cpp
connect(QObject *emisor, const char *signal,
        QObject *receptor, const char *slot,
        Qt::ConnectionType type=Qt::AutoConnection);


La función connect es la encargada de conectar o enlazar dos QObjects, recibe como parámetros un apuntador al QObject emisor de la señal de interés, un apuntador a un arreglo de caracteres que indica la firma de función de la señal que nos interesa monitorear, un apuntador al QObject receptor de la señal y otro apuntador a un arreglo de caracteres, esta vez con la firma de función del slot que se ejecutará cuándo se emita la señal indicada.
Las macros SIGNAL() y SLOT() son requeridas por el moc al indicar el segundo y cuarto parámetro, respectivamente. Es necesario que las firmas de las funciones indicadas en estas macros coincidan en sus parámetros. Con la excepción de que la señal indicada puede tener más parámetros que los requeridos por el slot, en este caso los parámetros adicionales son ignorados.
Al utilizar la función connect() se pueden presentar los siguientes casos:


  • Cada señal está conectada a un slot distinto. Este es el caso más simple y es el que mostramos en este ejemplo, en él el slot que se ejecuta depende de la señal que se emite.
  • Más de una señal puede estar conectada a un slot. En este caso el slot se ejecutará cuándo se emita cualquiera de las señales conectadas a él.
  • Una señal puede estar conectada a más de un slot. En este caso todos los slots conectados a la señal en cuestión se ejecutarán cuándo dicha señal se emita, uno tras otro en un orden indeterminado.
  • Una señal puede estar conectada a otra señal. En este caso la señal indicada en el cuarto parámetro se emitirá al emitirse la indicada en el segundo parámetro.

En la línea 34 comienza la implementación de nuestro slot personalizado, indicamos el tipo de dato de retorno, después la clase a la que pertenece seguido del nombre del slot y su lista de parámetros, en este caso no ningún parámetro es requerido.

Código: cpp
void Login::verificar()


En la línea 36 verificamos que la información capturada en los campos de texto coincida con la información de algún usuario registrado, en este caso y para mantener la simpleza indicamos en el código un único usuario: programacion, y su contraseña correspondiente: linux. La función QLineEdit::text() devuelve una QString con el contenido del QLineEdit en cuestión, en este caso el nombre de usuario para campoUsuario y la contraseña para campo Password.
QString es una clase de Qt que representa una cadena de caracteres unicode y que nos proporciona funciones bastante útiles como concatenación, traducción o búsqueda y reemplazo de caracteres, entre otras.
En caso de que la información capturada sea correcta mostramos un mensaje de bienvenida o confirmación y cerramos esta ventana para mostrar la ventana principal, debido a que en este momento sólo tenemos una ventana entonces salimos de la aplicación. En caso de que la información capturada sea incorrecta mostramos un mensaje de aviso informando que hubo un error.
Los mensajes de bienvenida o error, son mostrados al usuario utilizando las funciones estáticas de la clase QMessageBox

Código: cpp
if(campoUsuario->text() == "zonaqt.com" && campoPassword->text() == "zonaqt.com"){
    QMessageBox::information(this, "", trUtf8("Bienvenido a la aplicación"));
    emit exit(0);
}
else
    QMessageBox::warning(this, "", trUtf8("La combinación usuario/contraseña no es válida"));


Esta función la utilizamos para mostrar un diálogo como el siguiente:

Código: cpp
StandarButton QMessageBox::information(QWidget *padre, const QString &titulo,
        const QString &mensaje, StandarButtons boton=Ok,
        StandarButton botonPredeterminado=NoButton)


Esta función la utilizamos para mostrar un diálogo como el siguiente:

Código: cpp
StandarButton QMessageBox::warning(QWidget *padre, const QString &titulo,
        const QString &mensaje, StandarButtons botones=Ok,
        StandarButton botonPredeterminado=NoButton)


Estas funciones permiten mostrar un diálogo para mostrar información relevante o hacer una pregunta al usuario y recibir una respuesta.
Estas funciones reciben como parámetros obligatorios: Un apuntador al QWidget padre, que en este caso es nuestra ventana de login lo cual indicamos con la instrucción this; Una cadena de texto que será el título del mensaje; Y una cadena de texto que aparecerá en el contenido del QMessageBox.
Y como parámetros opcionales: Un conjunto de valores de la enumeración StandarButtons, que representan los botones que la QMessageBox incluirá y un elemento de la enumeración StandarButtons el cual representa al botón por default de la QMessageBox, es decir, el cual cambiará su estado a presionado (emitirá la señal clicked()) al presionar la tecla enter, en caso de no especificar estos parámetros se seleccionará por defecto el botón Ok.
El contenido del archivo main.cpp es el siguiente

Código: cpp
#include <QApplication>
#include "login.h"

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

    Login *login = new Login();

    login->show();

    return app.exec();
}


En la línea 2 incluimos el archivo de definición de nuestra clase Login.

Código: cpp
#include "login.h"


Y finalmente en la línea 8 creamos e inicializamos un objeto de nuestra clase login, el cual mostramos en la línea 10.

Código: cpp
Login *login = new Login();
login->show();


QMainWindow. La ventana principal de una aplicación

En el artículo anterior de este tutorial vimos como implementar la funcionalidad de una aplicación gráfica de Qt utilizando señales y slots. En este artículo revisaremos a detalle un widget especial, QMainWindow, el cuál posee características especiales para ser utilizado como la ventana principal de las aplicaciones Qt.

La estructura de la QMainWindow se puede ver en la siguiente imagen Diagrama



Una QMainWindow puede estar compuesta de las siguientes cinco áreas o secciones:

Menu Bar: Como su nombre lo indica esta barra contiene menús, los cuales están compuestos por elementos de texto o etiquetas que indican las acciones que puede realizar la aplicación y que se ejecutan al hacer clic sobre ellas. Normalmente sólo existe una de ellas en la ventana y está colocada en la parte superior, debajo de la barra de título. Algunos ejemplos muy conocidos de menús que suelen colocarse en esta barra son Archivo, Edición, Herramientas o Ayuda.
Toolbars: Al igual que la barra de menús, esta clase de barras, conocida como barra de herramientas está compuesta por las acciones que puede realizar la aplicación, con la diferencia de que en esta barra dichas acciones se muestran mediante iconos en lugar de etiquetas. Estas barras suelen estar ubicadas debajo de la barra de menús y podemos encontrar una o más de estas barras en una ventana. Un ejemplo muy conocido de iconos colocados en este tipo de barras son los de Guardar, representado por un diskette o Imprimir, representado por una impresora.
Status Bar: Esta barra, conocida como barra estatus o de estado, normalmente está colocada en la parte inferior de la ventana, en ella se muestran mensajes informativos sobre el estado de la aplicación, las acciones que está realizando o la descripción del elemento al que estemos apuntando con el mouse.
Dock Widgets: Este es un tipo de widgets que pueden ser acoplados o ajustados alrededor del widget central de una aplicación o permitir que "floten" libremente en el escritorio, al igual que las barras de herramientas contienen iconos y botones que permiten ejecutar acciones que proporciona la aplicación.
Central Widget: Este widget representa el área de trabajo de una aplicación, como la tabla de celdas de una aplicación en hoja de cálculo o el área de texto de un procesador de textos.
Podemos reconocer estás secciones en un buen número de aplicaciones, como ejemplo presentamos la ventana de Open Office Writer (clic para ampliar).

A continuación mostraremos un sencillo ejemplo de una aplicación que basada en una QMainWindow, la cual contendrá una etiqueta como widget central la cual cambiará el texto que muestra dependiendo de la acción que ejecutemos, estas acciones se encontrarán en una barra de menús y una barra de herramientas también incluiremos una barra de estatus.

La aplicación está compuesta por los siguientes tres archivos:


  • ventanaprincipal.h es el archivo de encabezado de la clase VentanaPrincipal, contiene la declaracion de las variables y la definición de las funciones que utilizaremos en esta aplicación.
  • ventanaprincipal.cpp es el archivo fuente de la clase VentanaPrincipal, contiene la implementación de las funciones declaradas en el archivo de encabezado.
  • main.cpp

A continuación revisaremos a detalle los archivos de la aplicación, sólo explicaremos las secciones que no hayan sido revisadas en partes anteriores de este tutorial o que sean relevantes en este contexto

ventanaprincipal.h

Código: cpp
#ifndef VENTANAPRINCIPAL_H
#define VENTANAPRINCIPAL_H

#include <QMainWindow>

class QLabel;
class QToolBar;
class QAction;
class QMenu;

class VentanaPrincipal : public QMainWindow
{
    Q_OBJECT

public:
    VentanaPrincipal(QWidget *parent=0);

private slots:
    void accionNuevoLanzada();
    void accionAbrirLanzada();
    void accionAcercaDeLanzada();
    void accionSalirLanzada();

private:
    QMenu* menuArchivo;
    QMenu* menuAyuda;
    QToolBar* barraDeHerramientas;
    QAction* accionNuevo;
    QAction* accionAbrir;
    QAction* accionAcercaDe;
    QAction* accionSalir;
    QLabel* widgetCentral;
};
#endif


En la línea 4 incluimos el archivo de cabecera QMainWindow el cual contiene la definición de la clase en la que basaremos nuestra ventana principal.
En la línea 11 comenzamos con la definición de nuestra clase VentanaPrincipal e indicamos que heredará de QMainWindow.
De la líneas 18 a 22 declaramos los slots por medio de los cuales implementaremos la funcionalidad de nuestra aplicación.
Y finalmente de las líneas 24 a 32 declaramos las variables que utilizaremos en la aplicación.

Código: cpp
#include "ventanaprincipal.h"

#include <QMenuBar>
#include <QToolBar>
#include <QLabel>
#include <QStatusBar>
#include <QMessageBox>

VentanaPrincipal::VentanaPrincipal(QWidget *parent)
    :QMainWindow(parent)
{
    menuArchivo = menuBar()->addMenu(trUtf8("&Archivo"));
    menuAyuda = menuBar()->addMenu(trUtf8("A&yuda"));

    barraDeHerramientas = addToolBar(trUtf8("&Archivo"));

    widgetCentral = new QLabel(trUtf8("Widget central"));
    widgetCentral->setFont(QFont("Sans-Serif", 25));

    accionNuevo = new QAction(QIcon("iconos/nuevo.png"), trUtf8("&Nuevo"), this);
    accionNuevo->setShortcut(QKeySequence::New);
    accionNuevo->setStatusTip(trUtf8("Crear un nuevo archivo"));

    accionAbrir = new QAction(QIcon("iconos/abrir.png"), trUtf8("&Abrir"), this);
    accionAbrir->setShortcut(QKeySequence::Open);
    accionAbrir->setStatusTip(trUtf8("Abrir un archivo existente"));

    accionSalir = new QAction(QIcon("iconos/salir.png"), trUtf8("&Salir"), this);
    accionSalir->setShortcut(QKeySequence::Quit);
    accionSalir->setStatusTip(trUtf8("Salir de la aplicación"));

    accionAcercaDe = new QAction(QIcon("iconos/salir.png"), trUtf8("&Acerca de"), this);
    accionAcercaDe->setShortcut(QKeySequence("Ctrl+d"));
    accionAcercaDe->setStatusTip(trUtf8("Información sobre esta aplicación"));

    menuArchivo->addAction(accionNuevo);
    menuArchivo->addAction(accionAbrir);
    menuArchivo->addSeparator();
    menuArchivo->addAction(accionSalir);

    menuAyuda->addAction(accionAcercaDe);

    barraDeHerramientas->addAction(accionNuevo);
    barraDeHerramientas->addAction(accionAbrir);
    barraDeHerramientas->addSeparator();
    barraDeHerramientas->addAction(accionSalir);

    setCentralWidget(widgetCentral);
    statusBar()->showMessage(trUtf8("Bienvenido"));
    setWindowTitle("Ventana Principal");
    setMinimumSize(200, 200);

    connect(accionNuevo, SIGNAL(triggered()),
            this, SLOT(accionNuevoLanzada()));

    connect(accionAbrir, SIGNAL(triggered()),
            this, SLOT(accionAbrirLanzada()));

    connect(accionAcercaDe, SIGNAL(triggered()),
            this, SLOT(accionAcercaDeLanzada()));

    connect(accionSalir, SIGNAL(triggered()),
            this, SLOT(accionSalirLanzada()));
}

void VentanaPrincipal::accionNuevoLanzada()
{
    widgetCentral->setText(trUtf8("Acción \"Nuevo\" lanzada"));
    this->resize(QSize(widgetCentral->sizeHint().width(), 200));
}

void VentanaPrincipal::accionAbrirLanzada()
{
    widgetCentral->setText(trUtf8("Acción \"Abrir\" lanzada"));
    this->resize(QSize(widgetCentral->sizeHint().width(), 200));
}

void VentanaPrincipal::accionAcercaDeLanzada()
{
    QMessageBox::about(this, this->windowTitle(), trUtf8("Aquí se debe colocar información sobre la aplicación y la compañia que la desarolla\n\nForto, tutoriales y noticias de Qt en Español en:\nhttp://www.zonaqt.com"));
}

void VentanaPrincipal::accionSalirLanzada()
{
    exit(0);
}


De las líneas 1 a 7 incluimos los archivos de cabecera necesarios para implementar la aplicación.
En las línea 12 inicializamos la variable menuArchivo la cual representa un menú que podemos incluir en la barra de menús de la QMainWindow, para lograrlo utilizamos las funciones

Código: cpp
QMenuBar* QMainWindow::menuBar()


Devuelve una referencia a una barra de menús que es creada al llamar a la función por primera vez.

Código: cpp
QMenu* QMenuBar::addMenu(QString &texto)


Devuelve una referencia a un menú que se creará al llamar a la función y que mostrará en pantalla el texto indicado en la cadena de caracteres que se pasa como parámetro. En la línea 13 realizamos lo mismo para el menú Ayuda.
En la línea 15 inicializamos la variable que representa a una barra de herramientas. La función utilizada es:

Código: cpp
QToolBar* QMainWindow::addToolBar(QString &texto);


y trabaja de manera muy similar a las descritas anteriormente, es decir, devuelve una referencia a una QToolBara que se crea cuando se llama a la función y que mostrará en pantalla la cadena que se pasa como parámetro.
En la línea 17 inicializamos la etiqueta que será utilizada como widget central en la aplicación. Y en la línea 18 establecemos la fuente que queremos que ocupe la etiqueta, esto se hace mediante la instrucción:

Código: cpp
QLabel::setFont(QFont(QString &fuente, int tamanio));


La cual recibe como parámetros un objeto QFont, el cual representa una fuente de texto y que a su vez recibe como parámetros el nombre de la fuente y el tamaño de la misma.
En la línea 20 inicializamos nuestra variable accionNuevo, del tipo QAction, mediante el constructor de la clase

Código: cpp
QAction::QAction(QIcon(QString &archivo), QString &texto, QWidget* padre)


Este constructor recibe como parámetros un objeto QIcon, el cual representa un icono que se mostrará cuando la acción sea colocada dentro de una barra de herramientas y que a su vez recibe como parámetro una cadena de texto con la dirección dónde se encuentra el archivo de imagen que mostrará el icono, el segundo parámetro del constructor de la clase QAction es una cadena que indica el texto que se mostrará cuando la acción sea colocada dentro de una barra de menús y el último parámetro es el widget padre de la acción.

QAction
Antes de seguir debemos definir el concepto de QAction: Una QAction es una representación abstracta de un comando de la aplicación que puede ser ejecutado por el usuario.

En muchas aplicaciones los mismos comandos, acciones o funciones pueden ser ejecutados de diferentes maneras, ya sea mediante un elemento de la barra de menús, un icono de alguna barra de herramientas o una combinación de teclas, por mencionar algunos ejemplos. Una QAction nos es muy útil en este tipo de situación ya que puede estar asociada a múltiples widgets, gracias a esto será posible proporcionar la misma función en distintos lugares de la aplicación y de manera sincronizada, entonces podemos decir que una QAction proporciona una interfaz común para la ejecución de los comandos de la aplicación.

Siguiendo con el código, en la línea 21 establecemos un atajo de teclado para nuestra acción Nuevo, mediante la función

ativo en el que nos encontremos. Por ejemplo en el caso de mi sistemas operativo el elemento QKeySecuence::New esta relacionado con la combinación de teclas Ctrl+N sin embargo esto puede ser distinto en otro sistema operativo, pero al utilizar esta enumeración Qt se encargará de administrarlo y en caso de que se requiera, asociarlo a otra combinación de teclas más adecuada en esa situación.
En la línea 22 establecemos el texto que deseamos que se muestre en la barra de estatus al posicionar el cursor del mouse sobre los widgets relacionados con la acción correspondiente. Esto se logra mediante la función:

Código: cpp
QAction::setStatusTip(QString &texto)


La cual recibe como parámetro una cadena con el texto que deseamos mostrar.
En las líneas 24 a 34 realizamos lo mismo para las acciones restantes.

En las líneas 36 a 39 agregamos tres de las acciones que creamos al menú Archivo. Esto lo hacemos a través de la función

Código: cpp
QMenu::addAction(QAction* accion)


Con el fin de lograr un diseñod e interfaz de usuario más claro y atractivo en la línea 38 agregamos un separardor, utilizando la función

Código: cpp
QMenu::addSeparator()


Un separador es una línea vertical u horizontal que separa las acciones que está colocadas dentro del mismo menú o barra de herramientas pero que pueden ser ubicadas en una categoría específica o diferente del resto de las acciones del mismo menú o barra de herramientas.
En la línea 41 agregamos la acción AcercaDe al menú Ayuda.

En las líneas 43 a 46 agregamos las acciones a la barra de herramientas de la aplicación utilizando las mismas funciones que utilizamos para agregarlas a los menús.

En la línea 49 creamos la barra de estatus mediante la función

Código: cpp
QMainWindow::statusBar()


La cual funciona de manera muy similar a las funciones utilizadas para crear las otras barras de la ventana principal, es decir, creará una barra de estatus para la ventana la primera vez que es llamada.
En la misma línea utilizamos la función

Código: cpp
QStatusBar::showMessage(QString &mensaje)


para mostrar un mensaje en la barra de estatus, en este caso mostramos la palabra "Listo" con el fin de indicar que la aplicación puede comenzar a utilizarse.
En las líneas 53 y 54 conectamos la acción accionNuevo con su slot correspondiente, esto lo realizamos mediante la función connect(), la cual revisamos a detalle en un artículo anterior. Debido a que una acción puede estar asociada a múltiples widgets no siempre será lanzada de la misma forma, debido a ello Qt nos proporciona la señal triggered() (lanzada o disparada) la cual se emitirá cada vez que un widget asociado a la señal sea activado, por ejemplo un clic sobre un botón lanzará una acción asociada al mismo mientras que una combinación de teclas lanzará dicha acción al ser presionada por el usuario.

En las líneas 56 a 63 conectamos las acciones restantes a sus respectivos slots.

En la línea 66 comienza la implementación de los slots, los slots accionNuevoLanzada y accionAbriLanzada cambiarán el texto que se muestra en la etiqueta utilizada como widget central y redimensionarán la ventana para ajustarla al nuevo texto mostrado. El cambio de texto de la etiqueta se realiza mediante la función

Código: cpp
QLabel::setText(QString &texto)


la cual recibe como parámetro una cadena de caracteres con el texto que se desea mostrar.
El cambio de tamaño de la ventana se realiza mediante la función

Código: cpp
QMainWindow::resize(QSize(int width, int height))


esta función recibe como parámetro un objeto QSize, el cual es una estructura que incluye el ancho y alto de un widget y que a su vez recibe como parámetros dos enteros indicando el ancho y alto del widget, respectivamente.
También utilizamos la función sizeHint() la cual devuelve un objeto QSize con el tamaño requerido por el widget sobre el que se llamo la función.
En la línea 78 comienza la implementación del slot asociado a la acción acercaDe, en la línea 80 indicamos que al dispararse dicha acción se mostrará el conocido diálogo acerca de el cuál contiene información sobre la aplicación como versióno fecha de lanzamiento y sobre la compañia que creo la aplicación.
Finalmente en las líneas 83 a 86 implementamos el slot asociado a la acción salir, en la línea 85 utilizamos la función exit(int codigo) para indicar que deseamos salir de la aplicación, la aplicación devolverá un código el código de error indicado como parámetro. Normalmente un valor de cero indica que la aplicación termino normalmente, es decir, sin ningún error.

main.cpp

Código: cpp
#include <QApplication>
#include "ventanaprincipal.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    VentanaPrincipal w;
    w.show();

    return a.exec();
}


En la línea 1 incluimos el archivo de cabecera de la clase VentanaPrincipal, mientras que en la línea 8 creamos e inicializamos un objeto de dicha clase, y finalmente en la línea 10 mostramos nuestra ventana principal.
Al ejecutar esta aplicación de ejemplo obtendremos algo parecido a lo mostrado en las siguientes imagenes:







Diálogos en Qt. Comunicación con el Usuario.

En esta sección del tutorial hablaremos acerca de los diálogos, pequeñas ventanas secundarias que sirven para comunicarse con el usuario.

Los diálogos son útiles en ocasiones en las que requerimos la confirmación del usuario antes de ejecutar una acción, como al guardar o eliminar un archivo o al salir de una aplicación, también suelen utilizarse para obtener pequeñas cantidades de información, como la palabra a buscar en un diálogo de buscar y reemplazar o el nombre que se desea dar a un marcador/favorito en un navegador web.

Podemos utilizar diálogos de Qt de dos formas distintas, una es creándolos nosotros como lo hemos hecho en otros artículos de este tutorial y la otra es utilizar el API estática de diálogos comúnmente utilizados que nos provee Qt, entre estos diálogos se encuentran los de petición de información al usuario o los de selección de carpetas, archivos, fuentes o colores.

En esta sección del tutorial mostramos dos ejemplos de uso de diálogos. El primero trata sobre diálogos modales y no modales y sobre como obtener el valor de una opción seleccionada por un usuario en un diálogo. En el segundo revisamos lso diálogos estándar que ofrece Qt para obtener valores de tipo específico como números, cadenas de texto, colores, tipos de letra, nombres de archivo, etc.

Diálogos modales y no modales.

En este artículo del tutorial crearemos un ejemplo de diálogos modales y no modales.

Podemos mostrar un diálogo en pantalla con una de las siguientes dos funciones:

Código: cpp
void QWidget::show();


Esta función se hereda de QWidget y es equivalente a llamar a la función setVisible con un parámetro booleano con valor de true, mostrar un diálogo a través de esta función es útil si queremos que el usuario pueda continuar interactuando con la ventana que mostró el diálogo, los diálogos que permiten dicha interacción se conocen con el nombre de diálogos no modales.

Código: cpp
int QDialog::exec();


Esta función sirve para mostrar un diálogo modal, este es un tipo de diálogos que bloquean el acceso a las demás ventanas de la aplicación. Mostrar un diálogo con la función exec() permite obtener el valor de la respuesta elegida por el usuario.
A continuación mostramos un ejemplo para observar de mejor manera la diferencia entre show y exec.



El ejemplo se compone de los siguientes archivos:


  • dialogomodal.h
  • dialogomodal.cpp
  • widgetprincipal.h
  • widgetprincipal.cpp
  • main.cpp
La clase DialogoModal, representa un diálogo personalizado muy simple que sólo muestra una etiqueta en el lugar en dónde se colocarían los widgets en un diálogo de una aplicación real, está clase nos servirá para ver cómo podemos gestionar las respuestas que puede emitir un diálogo modal.



El código de la clase DialogoModal es el siguiente:

NOTA: Sólo explicaremos las líneas relevantes para el ejemplo o que no hayan sido explicadas en artículos anteriores de este tutorial.

dialogomodal.h

Código: cpp
#ifndef DIALOGOMODAL_H
#define DIALOGOMODAL_H

#include <QDialog>

class QLabel;
class QPushButton;

class DialogoModal : public QDialog
{
    Q_OBJECT
public:
    explicit DialogoModal(QWidget *parent = 0);

private:
    QLabel *nombre;
    QPushButton *botonAceptar;
    QPushButton *botonRechazar;
    QPushButton *botonOtro;

private slots:
    void botoonOtroPresionado();
};

#endif // DIALOGOMODAL_H


dialogomodal.cpp

Código: cpp
#include "dialogomodal.h"

#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>

DialogoModal::DialogoModal(QWidget *parent) :
        QDialog(parent)
{
    QVBoxLayout* layoutPrincipal = new QVBoxLayout;
    QHBoxLayout* layoutBotones = new QHBoxLayout;

    nombre = new QLabel(trUtf8("Diálogo Modal"));
    botonAceptar = new QPushButton(trUtf8("Aceptar"));
    botonRechazar = new QPushButton(trUtf8("Rechazar"));
    botonOtro = new QPushButton(trUtf8("Otro"));

    nombre->setFont(QFont("Sans-Serif", 15));

    layoutBotones->addStretch();
    layoutBotones->addWidget(botonAceptar);
    layoutBotones->addWidget(botonRechazar);
    layoutBotones->addWidget(botonOtro);

    layoutPrincipal->addWidget(nombre);
    layoutPrincipal->addLayout(layoutBotones);

    setLayout(layoutPrincipal);

    connect(botonAceptar, SIGNAL(clicked()), this, SLOT(accept()));
    connect(botonRechazar, SIGNAL(clicked()), this, SLOT(reject()));
    connect(botonOtro, SIGNAL(clicked()), this, SLOT(botonOtroPresionado()));
}

void DialogoModal::botonOtroPresionado() {
    done(42);
}


Explicaremos sólo la implementación de esta clase, es decir, el archivo cpp.
De la línea 14 a la 17 creamos los widgets que componen nuestro diálogo. Los botones botonAceptar, botonRechazar y botonOtro representan las distintas opciones con las que el usuario puede responder al mensaje en el diálogo.
De las líneas 31 a 33 conectamos la señal clicked() de los botones al slot correspondiente con la acción que queremos que realicen. Los botones botonAceptar y botonRechazar se conectan a los slots accept() y reject(), los cuáles están previamente definidos en QDialog y se encargan de ocultar el diálogo y devolver el código respuesta correspondiente, 1 para accept (aceptar) y 0 para reject (rechazar), como valor de retorno de la función exec.
Por otra parte el botón de nombre botonOtro se conecta a un slot botonOtroPresionado el cuál es implementado por nosotros. Dicha implementación comienza en la línea 36 y en realidad es sólo una "envoltura" para llamar al slot:

Código: cpp
QDialog::done(int r)


El cual también es parte de QDialog, este slot nos permite hacer algo similar a accept() y reject(), es decir, ocultar el diálogo y enviar un código de respuesta, con la diferencia de que en done() podemos especificar el código de respuesta que queremos enviar. En este caso enviamos el número 42, pero puede ser cualquier otro entero.
El código de la clase WidgetPrincipal es el siguiente:

widgetprincipal.h

Código: cpp
#ifndef WIDGETPRINCIPAL_H
#define WIDGETPRINCIPAL_H

#include <QWidget>

class QDialog;
class QLabel;
class QPushButton;
class DialogoModal;

class WidgetPrincipal : public QWidget
{
    Q_OBJECT
public:
    explicit WidgetPrincipal(QWidget *parent = 0);

signals:

private:
    QPushButton *botonDialogoModal;
    QPushButton *botonDialogoNoModal;
    QLabel *resultadoDialogoModal;
    QDialog *dialogoNoModal;
    DialogoModal *dialogoModal;

private slots:
    void ejecutar();
};

#endif // WIDGETPRINCIPAL_H


widgetprincipal.cpp

Código: cpp
#include "dialogomodal.h"
#include "widgetprincipal.h"

#include <QHBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QApplication>
#include <QMessageBox>

WidgetPrincipal::WidgetPrincipal(QWidget *parent) :
        QWidget(parent)
{
    QGridLayout* layoutPrincipal = new QGridLayout;

    botonDialogoModal = new QPushButton(QObject::trUtf8("Mostrar Diálogo Modal"));
    botonDialogoNoModal = new QPushButton(QObject::trUtf8("Mostrar Diálogo No Modal"));

    resultadoDialogoModal = new QLabel("\t\t");

    dialogoModal = new DialogoModal;
    dialogoNoModal = new QDialog(this);

    resultadoDialogoModal->setFrameStyle(1);

    layoutPrincipal->addWidget(botonDialogoModal, 0, 0);
    layoutPrincipal->addWidget(resultadoDialogoModal, 0, 1);
    layoutPrincipal->addWidget(botonDialogoNoModal, 1, 0);

    setLayout(layoutPrincipal);
    setWindowFlags(Qt::Window);
    setWindowTitle(trUtf8("Diálogos Personalizados"));

    connect(botonDialogoModal, SIGNAL(clicked()), this, SLOT(ejecutar()));
    connect(botonDialogoNoModal, SIGNAL(clicked()), dialogoNoModal, SLOT(show()));
    connect(this, SIGNAL(rejected()), this, SLOT(QApplication::exit()));
}

void WidgetPrincipal::ejecutar() {
    int respuesta = dialogoModal->exec();

    QMessageBox::information(this, "", QString::number(respuesta));

    switch(respuesta) {
    case 0:
        resultadoDialogoModal->setText(trUtf8("Rechazar presionado"));
        break;

    case 1:
        resultadoDialogoModal->setText(trUtf8("Aceptar presionado"));
        break;

    default:
        resultadoDialogoModal->setText(trUtf8("Otro presionado"));
        break;
    }
}


Explicaremos sólo la implementación de esta clase, es decir, el archivo cpp.
De las líneas 15 a 21 creamos los widgets que componen nuestra widget. En la línea 18 creamos una etiqueta que mostrará la respuesta de nuestro diálogo modal y le indicamos que muestre dos tabuladores como texto para que tenga un tamaño mínimo que pueda contener la respuesta del diálogo, esto es sólo por motivos estéticos.

En la línea 21 creamos un diálogo que utilizaremos como diálogo no modal e indicamos que nuestra clase WidgetPrincipal será su padre, esto provocará que el diálogo no modal se muestre centrado respecto a la posición de nuestro widget; Este comportamiento no lo observamos para nuestro diálogo modal creado en la línea 20 ya que no hemos establecido un padre para él.



En la línea 23 establecemos un estilo de línea para el borde/marco de la etiqueta que mostrará la respuesta del diálogo modal.
De las líneas 33 a 35 realizamos las conexiones de señales y slots.
La señal rejected() también se emite cuándo se usa el botón cerrar de la barra de título de un diálogo.
En las líneas 33 y 34 conectamos la señal clicked de los botones a los slots que mostrarán el diálogo correspondiente, modal o no modal, el diálogo no modal lo mostramos llamando directamente a show debido a que esta función no devuelve ningún código de respuesta que tengamos que gestionar.
En la línea 35 conectamos la señal, rejected() de nuestra ventana al QApplication::exit(), el cual hará que termine nuestra aplicación.

El diálogo modal lo mostramos en el slot ejecutar(), implementado por nosotros a partir de la línea 38 y asignamos su resultado a una variable, después procesamos el valor de este resultado, en este caso comparamos dicho valor con los códigos de resultado usuales y el que definimos nosotros, dependiendo de con que valor coincida modificamos el valor de la etiqueta resultadoDialogoModal.



Es todo en la primera parte de este artículo, en la siguiente parte continuaremos con el tema de los diálogos; Mostraremos la otra forma de obtener información de parte del usuario: Utilizando los diálogos estándar de Qt.

Dialogos Estándar de Qt

En el artículo anterior revisamos la primera de las dos formas principales de utilizar diálogos en Qt, esto es creando clases que hereden de QDialog, implementando la funcionalidad de estas clases y gestionando las respuestas que recibimos a través de la función QDialog::exec()

En este artículo veremos la segunda forma de utilizar diálogos en una aplicación de Qt, esto es, utilizando las clases de diálogos estándar que nos proporciona Qt, estas clases son:


  • QMessageBox Permite mostrar mensajes de notificación al usuario.
  • QFileDialog Permite mostrar un diálogo que sirve para seleccionar un archivo o carpeta del sistema de archivos.
  • QInputDialog Permite mostrar un diálogo con un widget para que el usuario puede introducir información.
  • QColorDialog Muestra un diálogo para que el usuario pueda seleccionar un color.
  • QPrintDialog Muestra un diálogo para especificar configuraciones de la impresora.
Para este artículo utilizaremos la aplicación de ejemplo "Standard Dialogs" de la documentación oficial de Qt la cual está disponible en el sitio web de Qt y desde la sección Help de Qt Creator.

Este ejemplo se compone de los siguientes archivos:


  • dialogo.h
  • dialogo.cpp
  • main.cpp
Los archivos dialogo.h y ventana.cpp componen la clase Dialogo, la cual representa el widget principal de la aplicación de ejemplo. Esta clase esta compuesta por dos columnas de widgets, una de ellas está compuesta por botones; Cada uno de ellos servirá para mostrar un diálogo estándar que nos permitirá obtener un dato del usuario. La segunda columna se compone de etiquetas y en cada una de ellas escribiremos la información que proporcionó el usuario a través del diálogo correspondientes. Esta ventana se muestra en la siguiente imagen.



El código de la clase Diálogo es el siguiente:

dialogo.h

Código: cpp
#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>

class QCheckBox;
class QLabel;
class QErrorMessage;

class Dialogo : public QDialog
{
    Q_OBJECT

public:
    Dialogo(QWidget *parent = 0);

private slots:
    void setInteger();
    void setDouble();
    void setItem();
    void setText();
    void setColor();
    void setFont();
    void setExistingDirectory();
    void setOpenFileName();
    void setOpenFileNames();
    void setSaveFileName();
    void criticalMessage();
    void informationMessage();
    void questionMessage();
    void warningMessage();
    void errorMessage();

private:
    QCheckBox *native;
    QLabel *integerLabel;
    QLabel *doubleLabel;
    QLabel *itemLabel;
    QLabel *textLabel;
    QLabel *colorLabel;
    QLabel *fontLabel;
    QLabel *directoryLabel;
    QLabel *openFileNameLabel;
    QLabel *openFileNamesLabel;
    QLabel *saveFileNameLabel;
    QLabel *criticalLabel;
    QLabel *informationLabel;
    QLabel *questionLabel;
    QLabel *warningLabel;
    QLabel *errorLabel;
    QErrorMessage *errorMessageDialog;

    QString openFilesPath;
};


dialogo.cpp

Código: cpp
#include <QColorDialog>
#include <QErrorMessage>
#include <QFileDialog>
#include <QFontDialog>
#include <QGridLayout>
#include <QInputDialog>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>

#include "dialogo.h"

#define MESSAGE \
    Dialogo::tr("<p>Message boxes have a caption, a text, " \
               "and any number of buttons, each with standard or custom texts." \
               "<p>Click a button to close the message box. Pressing the Esc button " \
               "will activate the detected escape button (if any).")

Dialogo::Dialogo(QWidget *parent)
    : QDialog(parent)
{
    errorMessageDialog = new QErrorMessage(this);

    int frameStyle = QFrame::Sunken | QFrame::Panel;

    integerLabel = new QLabel;
    integerLabel->setFrameStyle(frameStyle);
    QPushButton *integerButton =
            new QPushButton(tr("QInputDialog::get&Int()"));

    doubleLabel = new QLabel;
    doubleLabel->setFrameStyle(frameStyle);
    QPushButton *doubleButton =
            new QPushButton(tr("QInputDialog::get&Double()"));

    itemLabel = new QLabel;
    itemLabel->setFrameStyle(frameStyle);
    QPushButton *itemButton = new QPushButton(tr("QInputDialog::getIte&m()"));

    textLabel = new QLabel;
    textLabel->setFrameStyle(frameStyle);
    QPushButton *textButton = new QPushButton(tr("QInputDialog::get&Text()"));

    colorLabel = new QLabel;
    colorLabel->setFrameStyle(frameStyle);
    QPushButton *colorButton = new QPushButton(tr("QColorDialog::get&Color()"));

    fontLabel = new QLabel;
    fontLabel->setFrameStyle(frameStyle);
    QPushButton *fontButton = new QPushButton(tr("QFontDialog::get&Font()"));

    directoryLabel = new QLabel;
    directoryLabel->setFrameStyle(frameStyle);
    QPushButton *directoryButton =
            new QPushButton(tr("QFileDialog::getE&xistingDirectory()"));

    openFileNameLabel = new QLabel;
    openFileNameLabel->setFrameStyle(frameStyle);
    QPushButton *openFileNameButton =
            new QPushButton(tr("QFileDialog::get&OpenFileName()"));

    openFileNamesLabel = new QLabel;
    openFileNamesLabel->setFrameStyle(frameStyle);
    QPushButton *openFileNamesButton =
            new QPushButton(tr("QFileDialog::&getOpenFileNames()"));

    saveFileNameLabel = new QLabel;
    saveFileNameLabel->setFrameStyle(frameStyle);
    QPushButton *saveFileNameButton =
            new QPushButton(tr("QFileDialog::get&SaveFileName()"));

    criticalLabel = new QLabel;
    criticalLabel->setFrameStyle(frameStyle);
    QPushButton *criticalButton =
            new QPushButton(tr("QMessageBox::critica&l()"));

    informationLabel = new QLabel;
    informationLabel->setFrameStyle(frameStyle);
    QPushButton *informationButton =
            new QPushButton(tr("QMessageBox::i&nformation()"));

    questionLabel = new QLabel;
    questionLabel->setFrameStyle(frameStyle);
    QPushButton *questionButton =
            new QPushButton(tr("QMessageBox::&question()"));

    warningLabel = new QLabel;
    warningLabel->setFrameStyle(frameStyle);
    QPushButton *warningButton = new QPushButton(tr("QMessageBox::&warning()"));

    errorLabel = new QLabel;
    errorLabel->setFrameStyle(frameStyle);
    QPushButton *errorButton =
            new QPushButton(tr("QErrorMessage::showM&essage()"));

    connect(integerButton, SIGNAL(clicked()), this, SLOT(setInteger()));
    connect(doubleButton, SIGNAL(clicked()), this, SLOT(setDouble()));
    connect(itemButton, SIGNAL(clicked()), this, SLOT(setItem()));
    connect(textButton, SIGNAL(clicked()), this, SLOT(setText()));
    connect(colorButton, SIGNAL(clicked()), this, SLOT(setColor()));
    connect(fontButton, SIGNAL(clicked()), this, SLOT(setFont()));
    connect(directoryButton, SIGNAL(clicked()),
            this, SLOT(setExistingDirectory()));
    connect(openFileNameButton, SIGNAL(clicked()),
            this, SLOT(setOpenFileName()));
    connect(openFileNamesButton, SIGNAL(clicked()),
            this, SLOT(setOpenFileNames()));
    connect(saveFileNameButton, SIGNAL(clicked()),
            this, SLOT(setSaveFileName()));
    connect(criticalButton, SIGNAL(clicked()), this, SLOT(criticalMessage()));
    connect(informationButton, SIGNAL(clicked()),
            this, SLOT(informationMessage()));
    connect(questionButton, SIGNAL(clicked()), this, SLOT(questionMessage()));
    connect(warningButton, SIGNAL(clicked()), this, SLOT(warningMessage()));
    connect(errorButton, SIGNAL(clicked()), this, SLOT(errorMessage()));

    native = new QCheckBox(this);
    native->setText("Use native file dialog.");
    native->setChecked(true);
    QGridLayout *layout = new QGridLayout;
    layout->setColumnStretch(1, 1);
    layout->setColumnMinimumWidth(1, 250);
    layout->addWidget(integerButton, 0, 0);
    layout->addWidget(integerLabel, 0, 1);
    layout->addWidget(doubleButton, 1, 0);
    layout->addWidget(doubleLabel, 1, 1);
    layout->addWidget(itemButton, 2, 0);
    layout->addWidget(itemLabel, 2, 1);
    layout->addWidget(textButton, 3, 0);
    layout->addWidget(textLabel, 3, 1);
    layout->addWidget(colorButton, 4, 0);
    layout->addWidget(colorLabel, 4, 1);
    layout->addWidget(fontButton, 5, 0);
    layout->addWidget(fontLabel, 5, 1);
    layout->addWidget(directoryButton, 6, 0);
    layout->addWidget(directoryLabel, 6, 1);
    layout->addWidget(openFileNameButton, 7, 0);
    layout->addWidget(openFileNameLabel, 7, 1);
    layout->addWidget(openFileNamesButton, 8, 0);
    layout->addWidget(openFileNamesLabel, 8, 1);
    layout->addWidget(saveFileNameButton, 9, 0);
    layout->addWidget(saveFileNameLabel, 9, 1);
    layout->addWidget(criticalButton, 10, 0);
    layout->addWidget(criticalLabel, 10, 1);
    layout->addWidget(informationButton, 11, 0);
    layout->addWidget(informationLabel, 11, 1);
    layout->addWidget(questionButton, 12, 0);
    layout->addWidget(questionLabel, 12, 1);
    layout->addWidget(warningButton, 13, 0);
    layout->addWidget(warningLabel, 13, 1);
    layout->addWidget(errorButton, 14, 0);
    layout->addWidget(errorLabel, 14, 1);
    layout->addWidget(native, 15, 0);
    setLayout(layout);

    setWindowTitle(tr("Standard Dialogs"));
}

void Dialogo::setInteger()
{
    bool ok;
    int i = QInputDialog::getInt(this, tr("QInputDialog::getInteger()"),
                                 tr("Percentage:"), 25, 0, 100, 1, &ok);
    if (ok)
        integerLabel->setText(tr("%1%").arg(i));
}

void Dialogo::setDouble()
{
    bool ok;
    double d = QInputDialog::getDouble(this, tr("QInputDialog::getDouble()"),
                                       tr("Amount:"), 37.56, -10000, 10000, 2, &ok);
    if (ok)
        doubleLabel->setText(QString("$%1").arg(d));
}

void Dialogo::setItem()
{
    QStringList items;
    items << tr("Spring") << tr("Summer") << tr("Fall") << tr("Winter");

    bool ok;
    QString item = QInputDialog::getItem(this, tr("QInputDialog::getItem()"),
                                         tr("Season:"), items, 0, false, &ok);
    if (ok && !item.isEmpty())
        itemLabel->setText(item);
}

void Dialogo::setText()
{
    bool ok;
    QString text = QInputDialog::getText(this, tr("QInputDialog::getText()"),
                                         tr("User name:"), QLineEdit::Normal,
                                         QDir::home().dirName(), &ok);
    if (ok && !text.isEmpty())
        textLabel->setText(text);
}

void Dialogo::setColor()
{
    QColor color;
    if (native->isChecked())
        color = QColorDialog::getColor(Qt::green, this);
    else
        color = QColorDialog::getColor(Qt::green, this, "Select Color", QColorDialog::DontUseNativeDialog);

    if (color.isValid()) {
        colorLabel->setText(color.name());
        colorLabel->setPalette(QPalette(color));
        colorLabel->setAutoFillBackground(true);
    }
}

void Dialogo::setFont()
{
    bool ok;
    QFont font = QFontDialog::getFont(&ok, QFont(fontLabel->text()), this);
    if (ok) {
        fontLabel->setText(font.key());
        fontLabel->setFont(font);
    }
}

void Dialogo::setExistingDirectory()
{
    QFileDialog::Options options = QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly;
    if (!native->isChecked())
        options |= QFileDialog::DontUseNativeDialog;
    QString directory = QFileDialog::getExistingDirectory(this,
                                tr("QFileDialog::getExistingDirectory()"),
                                directoryLabel->text(),
                                options);
    if (!directory.isEmpty())
        directoryLabel->setText(directory);
}

void Dialogo::setOpenFileName()
{
    QFileDialog::Options options;
    if (!native->isChecked())
        options |= QFileDialog::DontUseNativeDialog;
    QString selectedFilter;
    QString fileName = QFileDialog::getOpenFileName(this,
                                tr("QFileDialog::getOpenFileName()"),
                                openFileNameLabel->text(),
                                tr("All Files (*);;Text Files (*.txt)"),
                                &selectedFilter,
                                options);
    if (!fileName.isEmpty())
        openFileNameLabel->setText(fileName);
}

void Dialogo::setOpenFileNames()
{
    QFileDialog::Options options;
    if (!native->isChecked())
        options |= QFileDialog::DontUseNativeDialog;
    QString selectedFilter;
    QStringList files = QFileDialog::getOpenFileNames(
                                this, tr("QFileDialog::getOpenFileNames()"),
                                openFilesPath,
                                tr("All Files (*);;Text Files (*.txt)"),
                                &selectedFilter,
                                options);
    if (files.count()) {
        openFilesPath = files[0];
        openFileNamesLabel->setText(QString("[%1]").arg(files.join(", ")));
    }
}

void Dialogo::setSaveFileName()
{
    QFileDialog::Options options;
    if (!native->isChecked())
        options |= QFileDialog::DontUseNativeDialog;
    QString selectedFilter;
    QString fileName = QFileDialog::getSaveFileName(this,
                                tr("QFileDialog::getSaveFileName()"),
                                saveFileNameLabel->text(),
                                tr("All Files (*);;Text Files (*.txt)"),
                                &selectedFilter,
                                options);
    if (!fileName.isEmpty())
        saveFileNameLabel->setText(fileName);
}

void Dialogo::criticalMessage()
{
    QMessageBox::StandardButton reply;
    reply = QMessageBox::critical(this, tr("QMessageBox::critical()"),
                                    MESSAGE,
                                    QMessageBox::Abort | QMessageBox::Retry | QMessageBox::Ignore);
    if (reply == QMessageBox::Abort)
        criticalLabel->setText(tr("Abort"));
    else if (reply == QMessageBox::Retry)
        criticalLabel->setText(tr("Retry"));
    else
        criticalLabel->setText(tr("Ignore"));
}

void Dialogo::informationMessage()
{
    QMessageBox::StandardButton reply;
    reply = QMessageBox::information(this, tr("QMessageBox::information()"), MESSAGE);
    if (reply == QMessageBox::Ok)
        informationLabel->setText(tr("OK"));
    else
        informationLabel->setText(tr("Escape"));
}

void Dialogo::questionMessage()
{
    QMessageBox::StandardButton reply;
    reply = QMessageBox::question(this, tr("QMessageBox::question()"),
                                    MESSAGE,
                                    QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
    if (reply == QMessageBox::Yes)
        questionLabel->setText(tr("Yes"));
    else if (reply == QMessageBox::No)
        questionLabel->setText(tr("No"));
    else
        questionLabel->setText(tr("Cancel"));
}

void Dialogo::warningMessage()
{
    QMessageBox msgBox(QMessageBox::Warning, tr("QMessageBox::warning()"),
                       MESSAGE, 0, this);
    msgBox.addButton(tr("Save &Again"), QMessageBox::AcceptRole);
    msgBox.addButton(tr("&Continue"), QMessageBox::RejectRole);
    if (msgBox.exec() == QMessageBox::AcceptRole)
        warningLabel->setText(tr("Save Again"));
    else
        warningLabel->setText(tr("Continue"));

}

void Dialogo::errorMessage()
{
    errorMessageDialog->showMessage(
            tr("This dialog shows and remembers error messages. "
               "If the checkbox is checked (as it is by default), "
               "the shown message will be shown again, "
               "but if the user unchecks the box the message "
               "will not appear again if QErrorMessage::showMessage() "
               "is called with the same message."));
    errorLabel->setText(tr("If the box is unchecked, the message "
                           "won't appear again."));
}


A continuación explicaremos el código de la implementación de nuestra clase Dialogo, es decir, el archivo cpp. El código es largo, pero muchas cosas se repiten, así que la explicación se acorta bastante.
De las líneas 1 a 12 incluimos los archivos de cabecera que necesitamos para este ejemplo.
En la línea 20 comienza el constructor de nuestra clase.
En la línea 23 creamos un objeto de la clase QErrorMessage el cual provee un diálogo para mostrar un mensaje de error.
En la línea 25 obtenemos el valor de la opción que indica el estilo que queremos dar a la etiqueta.
De la línea 27 a la 95 creamos los botones y las etiquetas que contendrá el diálogo y establecemos el texto y opciones para cada uno de estos widgets.
De la línea 97 a la línea 116 realizamos las conexiones de cada uno de los botones con su slot correspondiente. En seguida explicaremos el código de cada uno de estos slots.
De la línea 121 a la 155 creamos el layout principal de nuestro widget y organizamos los widgets de acuerdo a él.

El la línea 160 comienza la implementación de los slots de nuestro diálogo, en todos ellos realizamos las siguiente acciones:


  • Creamos una variable para almacenar la información que ingresa el usuario por medio del diálogo.
  • Ejecutamos el diálogo para obtener la información del usuario y el valor de respuesta, este último está determinado por el botón que presiona el usuario.
  • Evaluamos el valor de respuesta del diálogo.
Dependiendo del valor de la respuesta decidimos de que manera procesar la información capturada, si es que la hay. Si el usuario no cierra el diálogo y en cambio presiona algún botón, entonces escribimos el valor de la respuesta en la etiqueta correspondiente.

Comenzaremos con los slots que hacen uso de los diálogos que nos ofrece la clase QInputDialog.

De la línea 160 a la 167 implementamos el slot setInteger, en él mostramos un diálogo que contiene una spinBox que permitirá al usuario establecer un valor numérico entero. Este diálogo lo ejecutamos mediante la función

Código: cpp
int QInputDialog::getInt ( QWidget * parent, const QString & title, const QString & label, int value = 0, int min = -2147483647, int max = 2147483647, int step = 1, bool * ok = 0, Qt::WindowFlags flags = 0 )




Los primeros tres y los dos últimos parámetros de está "familia" de funciones y de algunos de los otros diálogos estándar son iguales, indican el widget padre, el título del diálogo, el texto que se mostrará en el diálogo, un apuntador a la variable donde se almacenará la respuesta al diálogo y una lista de flags u opciones del diálogo.

A partir del cuarto parámetro y .en orden, los parámetros específicos de este diálogo son:


  • El valor predeterminado que al que se establecerá la spinbox cuando se muestre el diálogo.
  • El valor mínimo al que se puede establecer el valor de la spinbox.
  • El valor máximo al que se puede establecer el valor de la spinbox.
  • La cantidad en que se incrementará o decrementará el valor de la spinbox al presionar las flechas del extremo (step o paso).
De las líneas 169 a 176 implementamos el slot setDouble, en él mostramos un diálogo muy similar al que del slot obtenerEntero con la diferencia de que el valor de la spinbox podrá ser un valor decimal o de punto flotante de doble precisión. Este diálogo lo ejecutamos mediante la función

Código: cpp
double QInputDialog::getDouble ( QWidget * parent, const QString & title, const QString & label, double value = 0, double min = -2147483647, double max = 2147483647, int decimals = 1, bool * ok = 0, Qt::WindowFlags flags = 0 )




Los parámetros de esta función son muy similares a la función getInt salvo por el séptimo parámetro, el cuál indica el número de decimales del número, el valor por defecto es de uno.
De la línea 178 a 188 implementamos el slot obtenerElemento, en él mostramos un diálogo que contiene una combobox que permite al usuario elegir un valor de una lista que indiquemos. Este diálogo lo ejecutamos mediante la función

Código: cpp
QString QInputDialog::getItem ( QWidget * parent, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = 0 )




Esta función recibe del cuarto al sexto parámetro:


  • Una lista de cadenas de texto que serán los elementos de la combobox.
  • La posición del elemento seleccionado por defecto, si no se especifica será cero.
  • Un valor booleano que indica si la combobox será editable.
De las líneas 190 a 198 implementamos el slot setText, en él mostramos un diálogo que nos permitirá obtener un valor de texto capturado por el usuario. Este diálogo lo ejecutamos mediante la función

Código: cpp
QString QInputDialog::getText ( QWidget * parent, const QString & title, const QString & label, QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0, Qt::WindowFlags flags = 0 )




Está función recibe como cuarto y quinto parámetro:


  • El modo (EchoMode) en el que se mostrará el texto ingresado. Aqui podemos establecer un valor de la enumeración EchoMode el cual indicará si el texto debe ser visible, que no se muestre o que se muestren caracteres para enmascarar contraseñas (como asteriscos)
  • El valor predeterminado que se mostrará en el campo de texto
De las líneas 200 a 213 implementamos el slot setColor, en él mostramos un diálogo que contiene controles especializados para permitir que el usuario eliga un color. Este diálogo lo mostramos mediante la función

Código: cpp
QColorDialog::getColor( const QColor & initial, QWidget * parent, const QString & title, ColorDialogOptions options = 0 )




Esta función recibe como parámetros:


  • Un objeto de la clase QColor que indica el color inicial que se establecerá en el diálogo, si no se especifica ninguno el color blanco será el seleccionado
  • El widget padre del diálogo
  • El título del diálogo
  • Miembros de la enumeración ColorDialogOptions, que permiten establecer algunas propiedades del diálogo como mostrar canal alfa o usar el diálogo nativo del sistema operativo
De la línea 215 a 223 implementamos el slot setFont, el cual muestra un diálogo que incluye controles especiales para que el usuario pueda seleccionar una fuente para el texto. Dicho diálogo se muestra mediante la función

Código: cpp
QFont QFontDialog::getFont(bool *ok, QWidget *parent)




Dicha función tiene múltiples sobrecargas en las cuales se pueden establecer valores iniciales y configuraciones del diálogo similares a las de los diálogos anteriores pero en este ejemplo utilizamos una función que sólo recibe como parámetros el apuntador a la variable que almacena la respuesta que da el usuario al diálogo y un apuntador al widget padre de este diálogo.
De las líneas 225 a 286 implementamos los slots que hacen uso de las funciones estáticas de la clase QFileDialog.
De la línea 225 a 236 implementamos el slot setExistentDirectory en el cual ejecutamos un diálogo que mostrará una ventana de exploración del sistema de archivos y que nos permitirá seleccionar una carpeta o directorio. Este diálogo lo mostramos con la función

Código: cpp
QString getExistingDirectory ( QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), Options options = ShowDirsOnly )




Esta función recibe como parámetros:


  • Un apuntador al widget padre del diálogo.
  • El título del diálogo.
  • La ruta del directorio en dónde se desea comenzar a buscar.
  • Miembros de la enumeración QFileDialog::Options que permiten configurar opciones del diálogo como el sólo mostrar directorios o utilizar un diálogo no nativo.
De la línea 238 a 252 implementamos el slot setOpenFileName el cual funciona de manera muy similar al slot anterior con la diferencia de que este permite seleccionar un archivo en lugar de un directorio. Mostramos el diálogo utilizado en este slot con la función

Código: cpp
QString QFileDialog::getOpenFileName ( QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString(), QString * selectedFilter = 0, Options options = 0 )




En esta función el último y los tres primeros parámetros son iguales al de la función del slot anterior. Los parámetros propios de esta función son:


  • Como cuarto parámetro, una cadena que representa los filtros que podrán ser aplicados al diálogo, este filtro determina que tipo de archivo se muestra. Para cada tipo de archivo se escribe una descripción y entre paréntesis la extensión del tipo de archivo. Separamos cada tipo de archivo mediante dos caracteres punto y coma (;).
  • Como quinto parámetro, una cadena que indique el filtro seleccionado actualmente.
De la línea 254 a la 270 implementamos el slot setOpenFileNames el cual muestra un diálogo casi igual al del slot anterior con la diferencia de que este permite al usuario seleccionar más de un archivo. Mostramos el diálogo utilizado en este slot con la función

Código: cpp
QStringList     QFileDialog::getOpenFileNames ( QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString(), QString * selectedFilter = 0, Options options = 0 )




Los parámetros de la función utilizada para ejecutar el diálogo son iguales a los de la función utilizada para abrir un solo archivo.
De la línea 272 a 286 implementamos el slot setSaveFileName el cual muestra un diálogo que permite guardar un archivo al usuario y eligiendo su ubicación y especificando su nombre. El diálogo utilizado en este slot lo ejecutamos mediante la función

Código: cpp
QString QFileDialog::getSaveFileName ( QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString(), QString * selectedFilter = 0, Options options = 0 )




Los parámetros de esta función son iguales que los de la función utilizada para ejecutar el diálogo que permite abrir archivos. Si especificamos un nombre que coincida con el de un archivo existente se mostrará otro diálogo solicitando la confirmación del usuario, este diálogo muestra dos botones de respuesta: Aceptar y Cancelar.
A partir de la línea 288 y hasta el fin del archivo implementamos los slots que muestran los diálogos de la case QMessageBox. Entre las líneas 288 y 300 implementamos el slot criticalMessage el cual permite mostrar un diálogo con un icono de error que normalmente sirve para indicar que ocurrió algún error grave en la aplicación. Mostramos este diálogo mediante la función

Código: cpp
StandardButton QMessageBox::critical ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton )




Dicha función nos devuelve un valor de la enumeración StandarButttons, la cual contiene todos los posibles valores que devuelve un diálogo al presionar cada uno de los botones disponibles, como: Aceptar, Cancelar, Reintentar, Sí, Sí a todo, No, Cancelar.
Las cuatro funciones de la clase QMessageBox que revisaremos y que nos sirven para mostrar un diálogo de información reciben los mismos parámetros, los cuales son:


  • Un apuntador al widget padre del diálogo.
  • El título del diálogo.
  • El texto que deseamos mostrar en el diálogo.
  • Una lista de valores de la enumeración StandarButton separadados por caracteres 2de barra", (el que se usa para un OR a nivel bit en C/C++) en donde cada valor indica que se debe mostrar su botón correspondiente.
  • Un valor de la enumeración StandarButtons el cual indica que botón se establecerá como predeterminado, es decir sobre cual se hará un efectro de clic al presionar la tecla enter al estar el diálgo activo.
Entre las líneas 302 y 310 implementamos el slot informationMessage en el cual mostramos un diálogo que sirve para mostrar información al usuario, como un aviso sobre la conclusión de una tarea o una notificación de algún evento. Mostramos el diálogo utilizado mediante la función

Código: cpp
StandardButton QMessageBox::information ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton )




De la línea 312 a 324 implementamos el slot questionMessage en el cual mostramos un diálogo con icono de un signo de interrogación, este diálogo sirve para realizar una pregunta al usuario o solicitar su confirmación u aprobación para un proceso, como al guardar o borrar un archivo. Mostramos este diálogo mediante la función

Código: cpp
StandardButton QMessageBox::question ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton )




De la la línea 326 a la 337 implementamos el slot warningMessage en el cual mostramos un diálogo con que sirve para mostrar un mensaje de advertencia, como información sobre un error poco grave. Este diálogo lo mostramos mediante la función

Código: cpp
StandardButton QMessageBox::warning ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton )




De la línea 339 a la 350 implementamos el slot errorMessage en el cual mostramos un diálogo que contiene un mensaje de error. Este diálogo lo mostramos mediante la función

Código: cpp
void QErrorMessage::showMessage( const QString & message )




Esta función recibe como parámetro una cadena de texto con el mensaje de error que se desea mostrar al usuario.
En el archivo main.cpp simplemente creamos un objeto de esta clase y lo mostramos en pantalla.

Fuente: ZonaQt

Saludos

un aportazo! se agradece.

saludos!

 :o esta exelente! Me voy a poner a practicar