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.