Capítulo II: Programa Gráfico en C++.


5 Interactuando... 

Programación con clases de Gtk:

En el estilo básico utilizamos punteros (*) y referencias a objetos (&) para poder interactuar con el usuario, y para que las funciones tengan conocimiento de los objetos creados en la función main().

En C++ existe la posibilidad de definir un nuevo tipo de dato, donde no sólo se incorporan los datos básicos que lo configuran sino también las operaciones del mismo. En el capítulo I, vimos un ejemplo con la clase punto; la clase punto, no sólo incorpora las coordenadas x e y, sino las operaciones básicas.

Las clases tienen dos funciones básicas, el constructor y el destructor, que son ejecutadas en la inicialización de los distintos objetos y en su destrucción respectivamente.

Ahora, crearemos una nueva clase que contiene todos los componentes de nuestra aplicación, tendrá unos métodos o funciones encargadas de gestionar la señal, y en el constructor realiza todas las funciones de diseño.

Veamos como hacerlo:
Práctica II-5: En Anjuta, crea un proyecto nuevo, selecciona programa proyecto Gtkmm 2.0 (por defecto el lenguaje de programación es  C++), como nombre practica_II_5 (o el que se te antoje).

Recuerda que dará error al generar el proyecto. Edita el archivo configure en la carpeta correspondiente /home/[usuario]/Projects/practica_II_5 y reemplaza las alusiones a gtkmm-2.0 por gtkmm-2.4 (que es la librería que nosotros hemos instalado).

Si al generar el proyecto no te aparece el archivo main.cc, sino otros distintos (nombre_programa.cc, windows1.cc, windows1.hh, windows1_glade.cc,  windows1_glade.hh ) tienes instalado el programa glademm que genera dichos archivos.  
Para seguir las siguientes instrucciones deberías desinstalarlo (provisionalmente).

Modificamos el archivo de main.cc para que quede tal que así:
/* *************************
 *    MAESTRODENADA.COM      *
 *        presenta         *
 *           el            *
 *      programa con clase    *
 *      de Gtk           *
 ***************************/

#include <gtkmm.h>
#include <iostream>
#include <stdlib.h>

// DECLARACIÓN DE LA CLASE

class miventanica:public Gtk::Window
{
     public:
         miventanica(); //  declaración constructor
        virtual ~miventanica(); // declaración destructor
    protected:
        int veces; // ahora veces es miembro de miventanica
        // Relación de widgets
         Gtk::VBox principal;

         Gtk::HSeparator linea;
         Gtk::Label etiqueta;
         Gtk::Button contador;

        // señales
        virtual void pulsa_contador();
   
};

// DEFINICIÓN DE LOS MÉTODOS DE LA CLASE
// constructor de la clase
miventanica::miventanica():principal(),linea(),etiqueta("Pulsa abajo"),contador("¡AQUÍ!")
{
    veces=0;
    add(principal);
    principal.pack_start(etiqueta);
    principal.pack_start(linea);
    principal.pack_start(contador);
    principal.show();
    etiqueta.show();
    linea.show();
    contador.show();

    contador.signal_clicked().connect(sigc::mem_fun(*this,&miventanica::pulsa_contador) );
    
   
    set_title("maestrodenada.com");
    resize(260,110);
}

// destructor
miventanica::~miventanica(){std::cout<<"Eso es todo amigo"<<std::endl;}
 
// función para manipular la señal
void miventanica::pulsa_contador(){
   
    char h[30];
    sprintf (h, "has pulsado %i veces el botón", ++veces);

    etiqueta.set_text(h);
}

// la función main, ¡qué pequeña!
int main(int argc, char *argv[])
{  
        
    Gtk::Main programa(argc, argv); 
   
    miventanica ventana;   
   
    Gtk::Main::run(ventana);
    return 0;
}

Explicación:

Empecemos con main(), que cortita se ha quedado. Tiene cuatro instrucciones: una primera para crear el proceso, una segunda donde se declara el objeto ventana, una tercera donde se ejecuta el proceso, y finalmente una que devuelve cero si el programa no termina antes por algún error.

Observa cuánto código puede contener una simple instrucción. Gracias gtk.org, por vuestra inestimable colaboración.

En nuestro caso, cuando nosotros declaramos ventana [miventanica ventana;], estamos creando un objeto del tipo miventanica y llamando al constructor de dicha clase, a la función miventanica() de miventanica (miventanica::miventanica()). Observa que el método constructor tiene siempre el mismo nombre que la clase.

En la definición del constructor [ miventanica::miventanica():principal(),linea(),etiqueta("Pulsa abajo"),contador("AQUÍ") {...}] aparecen dos puntos[:] y la llamada a los constructores de los distintos widgets de Gtk, los cuales requieren igualmente ser llamados cuando se crea el objeto. Es en el constructor cuando se "crea" el objeto. La definición de la clase es como una descripción de los métodos y datos que contiene un objeto de este tipo, pero no son en sí un objeto.

En el código del constructor, se observa que los métodos add, set_title o resize, se utilizan sin el operador [.] (acuerdate de los miventanica.add(principal)) que hemos estado utilizando. Esto es así, porque add, resize,... son métodos propios de la clase miventanica. ¿PERO... si en la declaración de miventanica no se ve ninguna función con estos nombres? Cierto, pero existen, y es que miventanica se ha declarado de la siguiente forma:
class miventanica: public Gtk::Window{...};
 Esto quiere decir que miventanica es un tipo de Gtk::Window que incorpora ciertos métodos y datos. Esto se denomina herencia, y es uno de los conceptos fundamentales de la programación orientada a objetos.

También puedes ver, que los métodos se han declarado virtuales (virtual). Lo hago así por razones de diseño,( lo hacen así en los ejemplos que he podido consultar), pero es igualmente cierto que en este ejemplo no hace falta. La definición de métodos virtuales indican al compilador que las clases derivadas tendrán implementaciones diferentes en métodos con igual nombre, pero lo cierto es que nosotros no vamos a obtener una nueva clase a partir de miventanica, al menos en este ejemplo. Puedes suprimirlo (/* virtual */ ) y verás como sigue funcionando.

Si es importante observar como hemos conectado la función con la señal:

contador.signal_clicked().connect(sigc::mem_fun(*this,&miventanica::pulsa_contador) );

la función es otra e incluye dos argumentos: el objeto y la función.

*this, es algo que verás mucho en C++. this es una palabra clave que designa la dirección de memoria del objeto, un puntero al objeto; por lo que *this es el objeto en sí. De hecho, cuando antes hemos dicho que add, ni resize utilizaban el operador [.], en realidad el compilador interpreta add(principal) como *this.add(principal).

Un programa con varios archivos...


Lo habitual, aunque no es necesario, es que la clase se escriba en archivos distintos al archivo principal (main.cc).

Y decimos archivos, en plural, porque en C++ existen dos tipos de archivo: los .h y los .cc

Los .h son archivos de cabecera e incluyen las declaraciones de funciones y variables.
Los .cc son los archivos de código e incluyen todas las definiciones de las funciones, el código.

En nuestro caso los archivos deben quedar tal que así:
miventanica.h miventanica.cc main.cc
#ifndef _MIVENTANICA_H
#define _MIVENTANICA_H
#include <gtkmm.h>

class miventanica:public Gtk::Window
{
     public:
         miventanica();
         virtual ~miventanica();
    protected:
        int veces;
        // Relación de widgets
         Gtk::VBox principal;
         Gtk::HSeparator linea;
         Gtk::Label etiqueta;
         Gtk::Button contador;

        // señales
        virtual void pulsa_contador();
  
};

#endif
#include <gtkmm.h>
#include <iostream>
#include <stdlib.h>
#include "miventanica.h"

miventanica::miventanica():principal(),linea(),etiqueta("Pulsa abajo"),contador("¡AQUÍ!")
{
    veces=0;
    add(principal);
    principal.pack_start(etiqueta);
    principal.pack_start(linea);
    principal.pack_start(contador);
    principal.show();
    etiqueta.show();
    linea.show();
    contador.show();

    contador.signal_clicked().connect(sigc::mem_fun(*this,&miventanica::pulsa_contador) );
   
  
    set_title("maestrodenada.com");
    resize(260,110);
}


miventanica::~miventanica(){std::cout<<"Eso es todo amigo"<<std::endl;}
 

void miventanica::pulsa_contador(){
  
    char h[30];
    sprintf (h, "has pulsado %i veces el botón", ++veces);

    etiqueta.set_text(h);
}
#include <gtkmm.h>
#include "miventanica.h"


int main(int argc, char *argv[])

    Gtk::Main programa(argc, argv);
    miventanica ventana;
    Gtk::Main::run(ventana);
    return 0;
}

Para incluir archivo, selecciona  Archivo->Nuevo, te aparecerá el cuadro de la derecha. Elige C++ Header File para el archivo de cabecera (.h) en Tipo de archivo; y  C++ Source File para el archivo de código o fuente (.cc).

Al grabarlos, presta atención que estos se graben en el directorio src del proyecto.

Como veremos esta opción abre nuevos ficheros en Anjuta, pero no los incluye al proyecto.

Como hay archivos nuevos, debe cambiar las opciones del Configure, por lo que, en primer lugar debemos [Construir->Limpiar todo] y posteriormente [Construir->Auto generar]

Al compilar todo (Mayúsculas + F11), nos da error; pero si vemos detenidamente el mismo, el error está en main.o (esto es, ha compilado bien , sino sería main.cc, y es un error en el enlazado) ¿Por qué? Sencillamente, porque no están incluidos en el proyecto, o lo que es lo mismo, al ejecutarse g++ entre sus argumentos no están los nuevos archivos.
Para conseguir esto debemos añadir al proyecto todos los archivos fuente, de código (.cc); lo cual se consigue con la [Proyecto->Añadir Archivo->archivo de código fuente]
Resumiendo:
  1. Editamos los dos nuevos archivos [Archivo->Nuevo].
  2. Grabamos los archivos en el mismo directorio que main.cc
  3. Modificación del configure del proyecto [Construir->Limpiar todo]
  4. Incluir los archivos fuente (.cc) al proyecto [Proyecto ->Añadir Archivo ->archivo de código fuente]. En nuestro caso seleccionamos miventanica.cc (observa la ventana donde aparece el proyecto-arriba a la izquierda.
  5. Crear un nuevo make para el proyecto [Construir->Auto Generar]
  6. Compilar todo [Construir->Compilar todo]
  7. Ejecutar [construir->ejecutar]




Observa que el código es el mismo, las diferencias están en los include:
  • En main(): hacemos alusión a Gtk::Main, luego necesitamos la libreria gtkmm.h, y a miventanica, luego precisamos la inclusión del archivo miventanica.h.
  • En miventanica.h: solo hacemos alusión a objetos de Gtk, luego debemos incluir únicamente a gtkmm.h
  • En miventanica.cc: usamos sprintf (stdlib.h), funciones de Gtk (gtkmm) y std::cout (iostream), pero antetodo es la definición de la clase declarada en miventanica.h

Como quiera que el archivo no está en los directorios propios del sistema, sino que es un archivo que se encuentra en el mismo directorio que el programa; NO SE UTILIZA <> sino " ".  
Fijate que es #include "miventanica.h"

Otra cuestión son las directivas del preprocesador incluidas en el archivo miventanica.h:
  • #ifndef XXXXXXXXX            ...         #endif
Esta directiva le dice al compilador que si no se encuentra definida la macro XXXXXXXXX se proceda a la lectura e interpretación del texto hasta #endif.
  • define XXXXXXXXXX
Esta directiva define durante la compilación la siguiente macro.

Con esto, conseguimos que la declaración de la clase se lea una única vez por el compilador. La primera vez que se accede al archivo se define la macro, en futuras ocasiones ya se encuentra definida la macro y no se lee el archivo. Si esto no fuera así, y tuviesemos varios ficheros leyendo miventanica.h, la clase sería declarada en varias ocasiones, generando confusión al compilador y por tanto error.




Y por fin...con Glade: modo básico
Inicio