Manual
logo



Programación C++ con AGU desde 0

GTK++ con CLASES

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 grandes programas esta función main() resultaría bastante complicada.

Lo habitual es utilizar las clases de C++. Las clases son un tipo de dato definido por el usuario, que no sólo almacenan los datos, sino que también establecen las operaciones a realizar por el mismo, ya hemos visto un pequeño ejemplo.

Ahora vamos a realizar nuestro programa con clases.

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 ventana:public Gtk::Window
{
     public:
         ventana(); //  declaración constructor
        virtual ~ventana(); // declaración destructor
    protected:
        int veces; // ahora veces es miembro de ventana
        // 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
ventana::ventana():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,&ventana::pulsa_contador) );
  
 
    set_title("maestrodenada.com");
    resize(260,110);
}

// destructor
ventana::~ventana(){std::cout<<"Eso es todo amigo"<<std::endl;}
 
// función para manipular la señal
void ventana::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);
 
    ventana miventanica; 
 
    Gtk::Main::run(miventanica);
    return 0;
}

Explicación Código:

Empecemos con la función main(), que cortita se ha quedado. La única diferencia respecto a nuestra primera aplicación es que donde aparecía Gtk::Window miventanica; aparece
ventana miventanica;

Cuando declaramos la clase, aparece class ventana: public Gtk::Window, esto quiere decir que ventana es un tipo de Gtk::Window, con características especiales. A este proceso se denomina herencia, y con ello heredamos todos los métodos de la clase madre Gtk::Window (add, resize, set_title,...); a nosotros solo nos queda añadir las peculiaridades del tipo ventana.
Así cuando declaramos miventanica, este objeto es del tipo ventana, que a su vez es un Gtk::Window.
Cuando se declara un objeto de una clase, se procesa una función especial denominada constructor. El método constructor tiene siempre el mismo nombre que la clase. Así que en la declaración de la clase ventana encontramos el método ventana(); , pero en la definición del constructor encontramos:
ventana::ventana():principal(),linea(),etiqueta("Pulsa abajo"),contador("¡AQUÍ!")Los dos puntos indican que antes de procesar está función debe llamar a los constructores de los diferentes widgets de Gtk (clases) que igualmente requieren ser llamados 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í objetos.Observa por ejemplo que en la declaración de la clase, encontramos
  Gtk::Button contador;
que quiere decir que en la clase existirá un botón con nombre contador. Pero es en el constructor donde aparece contador("AQUÍ"), con lo cual se procesarálo mismo que con
  Gtk::Button contador("AQUÍ");como en los programas anteriores.
Una vez declarados los widgets, se inicializa veces (variable que ha pasado a ser de la clase y no global), y se procede como en el programa anterior. Quizás extrañe que no utilizamos el operador punto [.]. En vez de miventanica.add(principal), se utiliza add(principal); e igual ocurre con los métodos set_title y resize. La razón es sencilla, en main podríamos tener varios objetos definidos y debemos determinar a que objeto nos referimos, pero aquí, el objeto está implícito.

contador.signal_clicked().connect(sigc::mem_fun(*this,&ventana::pulsa_contador) );Si existen diferencias en la conexión o manejo de señales. La función sigc::ptr_fun(&función) ha sido modificada por sigc::mem_fun(*this, &función). la función es diferente e incluye dos argumentos: el objeto y la función.
this es una palabra clave de C++ 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).

public, protected, private

En la declaración de la clase se observa que algunos métodos o datos son declarados public, y otros protected. Estos son palabras clave que determinan el alcance de los métodos y datos.
Esto es, si declaro un elemento private o protected, ningún objeto podrá manipularlo. Por ejemplo, si en main, antes de correr el proceso pongo miventanica.veces=5; obtendré un error en la compilación porque es un dato protegido. La manipulación de este dato solo puede realizarse internamente, en los métodos de la clase.
A los métodos o datos públicos, public, si puede accederse con el objeto.
Lo normal al crear una clase es crear unos métodos públicos para el control de los datos, privados, evitando que el programador pueda realizar un uso incorrecto de los mismos.

virtual

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 ventana, al menos en este ejemplo. Puedes suprimirlo (/* virtual */ ) y verás como sigue funcionando.

ventana::~ventana(){std::cout<<"Eso es todo amigo"<<std::endl;}Esta función especial que tiene el mismo nombre que la clase, pero va precedido por ~, se denomina destructor. Y es la función que se ejecuta cuando se libera un objeto. miventanica se crea en main, al concluir main se libera ejecutándose. Observa que la salida se realiza por consola.