Imaginemos un mundo libre

La paz interior comienza en el momento en el que decides no permitir, que ninguna persona o evento, tome el control de tus emociones.

Cálculo mental con Qt: QTableWidget, QTimer y Layouts

with 3 comments

Este es un tutorial sencillo donde intento mostrar el funcionamiento de algunas clases de Qt, haciendo un pequeño programa de Cálculo Mental.

Me acuerdo cuando estaba en primaria y mi gran amigo Omar escribió un programa muy simple de cálculo mental pero muy adictivo. Creo que lo hizo en Visual Basic, bueno ahora Omar es usuario de Linux.

El programa, te mostraba un menú donde elegías una operación a realizar (suma, resta, multiplicación, división), después de tu elección se mostraban 50 operaciones a realizar de acuerdo a la que elegiste y el punto estaba en cuanto tiempo te demorabas en hacer esas operaciones.

Te mostraba las respuestas correctas, incorrectas, el tiempo, promedio de operación por segundo y guardaba estadísticas de los nombres con mejores tiempos.

Aquél que diga: “Esto es para niños”.

Entonces Responde lo siguiente:
7843 + 5629 =
9251 / 57 =
882 x 3747 =
6391 – 3472 =

¿Cuánto te demoraste? ¿Fácil?

Bueno, tampoco es un big deal, pero sirve y al menos hacer estos ejercicios es algo más saludable que ver televisión.

Vamos intentar hacer esta mini-aplicación con Qt, paso a paso. ¿Qué necesitamos?:

• Una tabla para almacenar los números con los que se hará los cálculos y se mostrará los resultados de las operaciones.
•Generar números aleatorios en las tablas.
• Un cronómetro que nos mostrará cuanto tiempo tardamos en realizar las operaciones.
• Los botones de start, stop, reset para iniciar, detener el tiempo y limpiar la tabla cuando queramos empezar nuevamente.
• Un texto que nos indique cuantas respuestas fueron correctas y cuantas incorrectas de los cálculos que realizamos.
• Y por supuesto un área donde podamos agrupar todos estos elementos.

Por el momento, esto son los ingredientes (Qt clases):

• QWidget: La clase QWidget es la clase base de todos los objetos de una GUI.

Qt proporciona una forma sencilla y potente de agrupar widgets unos con otros. Existen 3 clases en Qt que nos permiten realizar esta tarea de distinta forma:

• QVBoxLayout: Agrupa widgets horizontalmente de derecha a izquierda.
• QHBoxLayout: Agrupa widgets verticalmente de arriba abajo.
• QGridLayout: Agrupa widgets en una cuadrícula, en forma de tabla.
• QTableWidget: Permite crear una tabla e interactuar con sus elementos.
• QTableWidgetItem: Es una subclase de QTableWidget, que nos permite trabajar con un elemento (item) en específico dentro de una tabla creada con QTableWidget. Es decir, modificar el comportamiento y apariencia de una celda.
• QPushButton: Clase para crear botones.
• QLabel: Para crear etiquetas, texto , títulos que acompañen a los demás elementos.
• QLCDNumber: Muestra un número con dígitos al estilo LCD.
• QTime: Definir texto en formato de tiempo.
• QTimer: La clase QTimer, permite crear un temporizador y provee eventos para el funcionamiento del mismo.
• Qstring: Esta clase provee un cadena de caracteres tipo Unicode y permite manipular y convertir esta cadena a distintos formatos.

Además QString provee muchas funciones para convertir cadenas a números y números a cadenas.

TODO:
• Crear un menú, para elegir la operación a realizar, por el momento solo trabajamos con sumas.

• Cuando me encuentro en una celda, al presionar la tecla ENTER, pasar a la siguiente fila. Investigar acerca de la clase QKeyEvent y específicamente para este caso keyPressEvent y InstallEventFilter.

• Permitir que sólo se puedan ingresar números a la tabla.
Bueno, aquí está el código y OJO que muestra el funcionamiento básico de estas clases Qt.

Bueno, aquí está el código y OJO que este ejemplo muestra el funcionamiento básico de estas clases Qt.

Definición de la clase
Kalculate.h

#ifndef KALCULATE_H
#define KALCULATE_H

#include <QtGui/QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QTableWidget>
#include <QPushButton>
#include <QLCDNumber>
#include <QTime>
#include <QTimer>
#include <QLabel>

class Kalculate : public QWidget
{
    Q_OBJECT
public:
    Kalculate(QWidget *parent = 0);
    ~Kalculate();

private:
   QTableWidget *tableWidget;
   QTableWidgetItem *item;
   QStringList hlabels;
   QTime *time;
   QTimer *timer;

   QHBoxLayout *hLayout;
   QVBoxLayout *vLayout;
   QGridLayout *mainLayout;

   QPushButton *resetButton;
   QPushButton *startButton;
   QPushButton *stopButton;
   QPushButton *finishButton;

   QLCDNumber *num;
   QLabel *rightText;
   QLabel *wrongText;
   QLabel *nroRightAnswers;
   QLabel *nroWrongAnswers;

   int random;
   int rowCount;
   int colCount;
   int total;
   int seconds;

   void fillTable();

private slots:
    void startTime();
    void stopTime();
    void resetTime();
    void showTime();
    void finish();

};

#endif // KALCULATE_H

Implementación de la clase
Kalculate.cpp

#include "kalculate.h"
#include <QString>

//constructor
Kalculate::Kalculate(QWidget *parent)
    : QWidget(parent)
{
     // creamos la tabla y definimos nro de filas y columnas
     tableWidget = new QTableWidget(this);
     tableWidget->setRowCount(50);
     tableWidget->setColumnCount(4);

     // almacenamos en variables el nro de filas y columnas
     rowCount = tableWidget->rowCount();
     colCount = tableWidget->columnCount();

     // Texto en la cabecera de la tabla
     hlabels << "A" << "B" << "A+B" << "Result";
     tableWidget->setHorizontalHeaderLabels(hlabels);

     // botones de inicio, pausa, reinicio y de finalizar
     startButton = new QPushButton("Start");
     stopButton = new QPushButton("Stop");
     resetButton = new QPushButton("Reset");
     finishButton = new QPushButton("Finish");

     // Etiquetas para mostrar respuetas correctas e incorrectas
     rightText = new QLabel("Right Answers");
     nroRightAnswers = new QLabel();
     nroRightAnswers->setFixedWidth(50);

     wrongText = new QLabel("Wrong Answers");
     nroWrongAnswers = new QLabel();
     nroWrongAnswers->setFixedWidth(50);

     // Número con digitos tipo LCD
     num = new QLCDNumber();
     num->setNumDigits(8);

     // Formato tiempo
     time = new QTime;
     time->setHMS(0,0,0,0);

     // Creamos el temporizador
     timer = new QTimer(this);

     // Realizamos la conexión para que el método(Slot) showTime sea llamado cada segundo
     connect(timer, SIGNAL(timeout()), this, SLOT(showTime()));

     // Iniciamos los segundos con valor 0
     seconds=0;

     // relacionamos los botones con sus respectivac acciones (Slots)
     connect(startButton,SIGNAL(clicked()),this,SLOT(startTime()));
     connect(stopButton,SIGNAL(clicked()),this,SLOT(stopTime()));
     connect(resetButton,SIGNAL(clicked()),this,SLOT(resetTime()));
     connect(finishButton,SIGNAL(clicked()),this,SLOT(finish()));

     // convertimos el formato tiempo a cadena(horas,min,sec) y le damos estilos (color, fondo)
     QString text = time->toString("hh:mm:ss");
     num->display(text);
     num->setStyleSheet("* { background-color:rgb(128,64,0);color:rgb(255,255,255);}}");
     num->setSegmentStyle(QLCDNumber::Filled);

     // Agrupamos los botones en un layout horizontal
     hLayout = new QHBoxLayout;
     hLayout->addWidget(startButton);
     hLayout->addWidget(stopButton);
     hLayout->addWidget(resetButton);
     hLayout->addWidget(finishButton);

     // Agrupamos el número LCD y las etiquetas en un layout vertical
     vLayout = new QVBoxLayout;
     vLayout->addWidget(rightText);
     vLayout->addWidget(nroRightAnswers);
     vLayout->addWidget(wrongText);
     vLayout->addWidget(nroWrongAnswers);
     vLayout->addWidget(num);
     vLayout->addStretch();

     /* Creamos el layout principal de tipo Grid (Cuadrícula)
     y agrupamos los layouts horizontal y vertical más la tabla */
     mainLayout = new QGridLayout;
     mainLayout->addLayout(hLayout,0,0);
     mainLayout->addWidget(tableWidget,1,0);
     mainLayout->addLayout(vLayout,1,1);

     // Instalamos el layout en el widget y definimos el titulo de la ventana
     setLayout(mainLayout);
     setWindowTitle(tr("Cálculo mental"));
}

// destructor
Kalculate::~Kalculate()
{

}

// Para iniciar el cronómetro
void Kalculate::startTime()
{
    // habilitamos y deshabilitamos los botones según la lógica de inicio
    startButton->setDisabled(1);
    stopButton->setEnabled(1);
    resetButton->setEnabled(1);
    finishButton->setEnabled(1);

    // iniciamos el cronómetro en milisegundos y llenamos la tabla
    timer->start(1000);
    fillTable();
}

// Para llenar la tabla
void Kalculate::fillTable()
{
    // llenamos las columnas 1 y 2 de la tabla con nros aleatorios
    for(int row = 0; row < rowCount; ++row) {
        for(int col = 0; col < 2; ++col) {
            random = qrand() % 10000; // generamos un número aleatorio

            QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(random));
            // para que los elementos no sean editables
            newItem->setFlags(newItem->flags() & (~Qt::ItemIsEditable));
            newItem->setTextColor(Qt::blue); // color de los items
            tableWidget->setItem(row,col,newItem);
        }

        // los elementos de la cuarta columna(respuestas correctas) tampoco pueden ser editables
        QTableWidgetItem *resultItem = new QTableWidgetItem();
        tableWidget->setItem(row,3,resultItem);
        resultItem->setFlags(resultItem->flags() & (~Qt::ItemIsEditable));
    }
}

// Para detener el cronómetro
void Kalculate::stopTime()
{
     // habilitamos y deshabilitamos los botones según la lógica de pausa
     stopButton->setDisabled(1);
     startButton->setEnabled(1);
     resetButton->setEnabled(1);
     finishButton->setDisabled(1);

     // detenemos el cronómetro
     timer->stop();
}

// Para reiniciar el cronómetro
void Kalculate::resetTime()
{
    // ponemos el tiempo en blanco y lo convertimos a cadena para mostrarlo
    time->setHMS(0,0,0);
    QString text = time->toString("hh:mm:ss");
    num->display(text);

    // volvemos  a poner los segundos a 0 y habilitamos y deshabilitamos los botones según la lógica de reinicio
    seconds=0;
    stopButton->setDisabled(1);
    startButton->setEnabled(1);

    // detenemos el tiempo
    stopTime();

    // limpiamos el texto que se generó en las etiquetas Right-Wrong
    nroRightAnswers->clear();
    nroWrongAnswers->clear();

    // limpiamos el contenido de la tabla
    tableWidget->clearContents();
}

// Para mostrar el tiempo con el formato correcto
void Kalculate::showTime()
{
    // incrementamos los segundos de 1 en 1 y lo agregamos el tiempo
    QTime newtime;
    seconds = seconds + 1;
    newtime = time->addSecs(seconds);

    // convertimos el tiempo a cadena para mostrar
    QString text = newtime.toString("hh:mm:ss");
    num->display(text);
}

// Para hacer click cuando se finalizen las operaciones
void Kalculate::finish()
{
    // respuesta del usuario, resultado correcto, nro de respuestas correctas e incorrectas
    int userAnswer = 0;
    int result = 0;
    int corrects = 0;
    int incorrects = 0;

    for(int row = 0; row < rowCount; ++row) {
        total = 0;

        // calculamos la respuesta correcta con los elementos 1 y 2 de determinada fila
        for(int col = 0; col < 2; col++) {
            QTableWidgetItem *item = tableWidget->item(row,col);
            random = item->text().toInt(); // convertimos a entero el elemento
            total += random;
        }

        // colocamos la respuesta correcta en su correspondiente fila
        tableWidget->item(row,3)->setText(tr("%1").arg(total));

        // Convertimos a entero los elementos de las columnas 2 y 3 para hacer comparaciones
        userAnswer = tableWidget->item(row,2)->text().toInt();
        result = tableWidget->item(row,3)->text().toInt();

        // formato para las respuestas correctas e incorrectas
        QFont font( "Times New Roman", 10, QFont::Bold );
        tableWidget->item(row,2)->setFont(font);

        /* Si la respuesta del usuario es acertada incrementamos el nro de correctas
        sino el nro de incorrectas y damos otro color para distinguirlos */
        if ( userAnswer == result ) {
            ++corrects;
            tableWidget->item(row,2)->setTextColor(Qt::black);

        } else {
            ++incorrects;
            tableWidget->item(row,2)->setTextColor(Qt::red);
        }
    }

    // colocamos el nro de respuestas correctas e incorrectas en las etiquetas
    nroRightAnswers->setNum(corrects);
    nroWrongAnswers->setNum(incorrects);

    // detenemos el tiempo
    stopTime();
}

main.cpp

#include <QtGui/QApplication>
#include "kalculate.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Kalculate w;
    w.setFixedSize(700,650);
    w.show();
    return a.exec();
}

Resultado:

Al iniciar

Iniciando los cálculos - Tiempo corriendo

Gracias por tu visita al blog.  Puedes seguirme en Twitter haciendo click en el siguiente enlace:

 

Written by Ronny Yabar

March 12, 2010 at 6:33 pm

3 Responses

Subscribe to comments with RSS.

  1. This post couldnt be more right on!!!

    expressing opinions

    November 23, 2011 at 4:50 pm

  2. What a really incredible read!

    download films for free

    December 6, 2011 at 9:30 am

  3. Gracias!!!
    has aclarado muchas dudas que poseía con respecto a las TableWidgets.

    :D

    November 4, 2012 at 10:06 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: