sábado, 6 de septiembre de 2014

Primeros pasos en Qt: Creación de diálogos

Ahora vamos a ver como crear ventanas de diálogos con Qt. En este ejemplo vamos a programar una ventana de búsqueda. Las ventanas de diálogos le permiten al usuario definir una configuración o definir una acción. Además crearemos este dialogo de dos formas diferentes, una con solo código y la otra utilizando en diseñador de interfaces de Qt.

El código fuente se encontrará en 2 archivos finddialog.h y finddialog.cpp. Veamos finddialog.h:

#ifndef FINDDIALOG_H
#define FINDDIALOG_H
#include <QDialog>
class QCheckBox;
class QLabel;
class QLineEdit;
class QPushButton;

En la primera parte incluimos QDialog, clase que vamos a extender y definimos los componentes que vamos a utilizar.

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

Q_OBJECT es una macro que es necesario agregar para que Qt incluya nuestro componente y nos permita utilizar SIGNAL y SLOT. Además extendemos de QDialog que a la vez extiende de QWidget. También definimos un constructor que permite asignar un componente contenedor, por defecto este puntero es nulo.

signals:
void findNext(const QString &str, Qt::CaseSensitivity cs);
void findPrevious(const QString &str, Qt::CaseSensitivity cs);

Con la palabra clave signals indicamos las señales de nuestro componente, en este caso definimos dos. Estas señales van a ser emitidas cuando el usuario presione el botón de buscar.

La palabra clave signals, también es una macro. El compilador en la etapa de preprocesamiento utiliza estas palabras claves para introducir código y transforma nuestro código en C++ estándar, aumentando nuestra producción y ahorrándonos escribir código repetitivo.

private slots:
void findClicked();
void enableFindButton(const QString &text);

private:
QLabel *label;
QLineEdit *lineEdit;
QCheckBox *caseCheckBox;
QCheckBox *backwardCheckBox;
QPushButton *findButton;
QPushButton *closeButton;
};

#endif

En la sección private slot declaramos 2 slots. Para implementar estos slots es necesario acceder a los componentes que son contenidos por el dialogo, por lo que debemos mantener punteros a los mismos. Slot es una palabra clave similar a signal y que es una extensión del lenguaje a través de macros.

Ahora vamos a ver finddialog.cpp:

#include <QtGui>
#include "finddialog.h"

Primero, nosotros incluimos <QtGui> este archivo es un archivo de cabecera que contiene todas las definiciones de los componentes visuales de Qt. Qt esta formado por diferentes módulos, los módulos más importantes son: QtCore, QtGui, QtNetwork, QtOpenGL, QtSql, QtSvg y QtXml. El archivo <QtGui> contiene las definiciones de los módulos QtCore y QtGui.

En el archivo de cabecera finddialog.h solo incluimos <QDialog> y luego utilizamos declaraciones de QCheckBox, QLabel, QLineEdit y QPushButton, tal vez simplemente podríamos haber incluido <QtGui>. Sin embargo, en general no es buena práctica incluir un archivo de cabecera tan grande de otro archivo de cabecera.

FindDialog::FindDialog(QWidget *parent) : QDialog(parent)
{
label = new QLabel(tr("Find &what:"));
lineEdit = new QLineEdit;
label->setBuddy(lineEdit);
caseCheckBox = new QCheckBox(tr("Match &case"));
backwardCheckBox = new QCheckBox(tr("Search &backward"));
findButton = new QPushButton(tr("&Find"));
findButton->setDefault(true);
findButton->setEnabled(false);
closeButton = new QPushButton(tr("Close"));

En el constructor nos encargamos de crear los diferentes componentes. La función tr es una función que esta declarada en QObject y se utiliza para que la aplicación se pueda traducir fácilmente. Con '&’ indicamos el shortcut que permite lanzar la funcionalidad. Por ultimo indicamos el botón por defecto, es la acción que se va a llamar si se presiona enter.

Luego, conectamos los eventos o señales con funcionalidad que debe ejecutarse:

connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(enableFindButton(const QString &)));
connect(findButton, SIGNAL(clicked()), this, SLOT(findClicked()));
connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));

Como se puede ver si cambia el texto de lineEdit se ejecutara la función enableFindButton, y a la vez si se ejecuta clicked de findButton y closeButton se ejecutara la función asignada. Dado que nuestro dialogo de busqueda es un QObject, no necesitamos llamar a la función connect con el prefijo QObject:: por ultimo organizamos la aplicación con layouts y agregamos el titulo de nuestra ventana:

QHBoxLayout *topLeftLayout = new QHBoxLayout;
topLeftLayout->addWidget(label);
topLeftLayout->addWidget(lineEdit);

QVBoxLayout *leftLayout = new QVBoxLayout;
leftLayout->addLayout(topLeftLayout);
leftLayout->addWidget(caseCheckBox);
leftLayout->addWidget(backwardCheckBox);

QVBoxLayout *rightLayout = new QVBoxLayout;
rightLayout->addWidget(findButton);
rightLayout->addWidget(closeButton);
rightLayout->addStretch();

QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addLayout(leftLayout);
mainLayout->addLayout(rightLayout);
setLayout(mainLayout);
setWindowTitle(tr("Find"));
setFixedHeight(sizeHint().height());
}

Ahora debemos programar las señales:

void FindDialog::findClicked()
{
QString text = lineEdit->text();
Qt::CaseSensitivity cs =
caseCheckBox->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
if (backwardCheckBox->isChecked()) {
    emit findPrevious(text, cs);
} else {
    emit findNext(text, cs);
}
}

void FindDialog::enableFindButton(const QString &text)
{
findButton->setEnabled(!text.isEmpty());
}

findClicked() es un Slot que se llama cuando presionamos el botón findButton y este método llama a la función  findPrevious o findNext dependiendo si backwardCheckBox fue checkeado. La palabra clave emit no es estándar de C++, es utilizada por Qt y por macros se transforma a C++ estándar.  Con emit se lanzaran las señales  findNext o  findPrevious.

enableFindButton() es llamado cuando se cambia el texto cambia y activa el botón findButton.

Veamos main.cpp:

#include <QApplication>
#include "finddialog.h"

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
FindDialog *dialog = new FindDialog;
dialog->show();
return app.exec();
}

Si probamos nuestra ventana de dialogo, vamos a ver que no busca en ningún lado y eso porque solo lanza las señales  findPrevious y findNext pero ningún objeto intercepta estas señales y hace algo con ellas. Más adelante vamos a utilizar este dialogo...