Práctica II-7:
En Anjuta,
crea un proyecto
nuevo,
selecciona programa
proyecto Gtkmm 2.0 (por defecto el lenguaje de
programación es
C++), como nombre gladeconclase
(o el que se te antoje). |
|
Recuerda que dará error al generar el proyecto.
|
|
REPETIMOS... En Anjuta, selecciona [Proyecto->Editar GUI de la aplicación]. Seleccionamos windows1, editamos las siguientes propiedades: Nombre-> miventanica Título-> maestrodenada.com Añadimos un VBox, aceptamos el tamaño (3) por defecto. En la zona superior del VBox, insertamos una etiqueta (label) y modificamos las siguientes propiedades: Nombre-> etiqueta etiqueta-> Pulsa el botón de abajo empaquetado. expandir->Sí En la zona intermedia del VBox, añadimos una línea horizontal y empaquetado. expandir->NO En la zona inferior, ponemos una caja de botones horizontal (indicando 1 columna). Seleccionamos el botón y editamos las siguientes propiedades: Nombre-> boton etiqueta-> ¡AQUÍ! Editamos los iconos del botón y la ventana (en propiedades) y nos queda algo así: ![]() Por supuesto,
Grabamos antes de salir.
|
|
Una "primera" solución...Tal y como hícimos con Gtk(con clase), uno tiene la tentación de simplificar al máximo la función main(), introduciendo casi todas las operaciones en el constructor de la clase. |
|
#include
<gtkmm.h> #include <iostream> #include <stdlib.h> #include <libglademm/xml.h> // DECLARACIÓN DE LA CLASE class miventanica { public: miventanica(); virtual ~miventanica(); Gtk::Window * yo; protected: int veces; Glib::RefPtr<Gnome::Glade::Xml> refXml; Gtk::Button* boton; Gtk::Label* etiqueta; // señales virtual void pulsa_contador(); }; // DEFINICIÓN DE LOS MÉTODOS DE LA CLASE miventanica::miventanica() { veces=0; refXml = Gnome::Glade::Xml::create("../gladeconclase.glade"); refXml->get_widget("miventanica", yo); refXml->get_widget("boton", boton); boton->signal_clicked().connect(sigc::mem_fun(*this,&miventanica::pulsa_contador)); refXml->get_widget("etiqueta", etiqueta); } // 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); } int main(int argc, char *argv[]) { Gtk::Main programa(argc, argv); miventanica ventana; Gtk::Main::run(*ventana.yo); return 0; } |
|
Así,
funciona... Básicamente, hemos escondido el código de la práctica anterior en una clase. Hemos de destacar que la clase miventanica, en nuestro caso no hereda de Gtk::Window (no es una Gtk::Window), sino que contiene una Gtk::Window ( Gtk::Window yo). No es lo mismo "tener" que "ser" (ser un coche es distinto a tener un coche). Me he visto obligado a hacerlo así porque ninguna de las instrucciones siguientes eran reconocidas: refXml->get_widget ("miventanica", this); refXml->get_widget ("miventanica", (Gtk::Window* )this); [seguiré investigando aunque carezca de interés para mí] Por lo que, nos vemos obligados a considerarla como un miembro de nuestra clase (una clase que tiene una ventana). Miembro que debe estar en la parte pública para poder acceder desde main (Gtk::Main::run (*ventana.yo)) ... Sin embargo, no es muy buena opción, ¿? En nuestro ejemplo, existe solo una ventana, ¿pero qué ocurre con proyectos que utilizan muchas ventanas (fuentes, colores, grabar archivos, configuración, impresión,...)? Si cada ventana tiene su clase (como es normal) y cada ventana "carga" el documento .glade, tendremos nuestra RAM almacenando innecesariamente el mismo documento varias veces: ![]() La solución pasa por crear en main a refXml y que las clases tengan una referencia al mismo: ![]() Para ello, debemos
pasar la dirección de memoria del documento a la clase...
Modifiquemos nuestro archivo glade para que incluya una segunda ventana: un cuadro de diálogo, con un botón [aceptar]. Tal que así: ![]() ventana: Nombre-> dialogo botón: Nombre-> botonAceptar y SOBRETODO no se nos olvide poner dialogo como NO visible (en Propiedades- Comunes-Visible), porque de lo contrario no aparecerá cuando nosotros queramos, sino al principio. Si prefieres puedes grabar el archivo gladeconclase.glade en el directorio reemplazando al generado por Anjuta. A continuación os presento "MI" solución, que no se parece a la propuesta en la documentación de libglademm. Que yo sepa presenta como desventaja que las clases no son derivadas de clases Gtk, sino que contienen objetos Gtk, y a la hora de hacer referencia a las propiedades específicas de los objetos Gtk, debo manipularlos. [Probablemente tengan más problemas... con el tiempo irán saliendo]. Aquí está el código. |
|
/* Created by Anjuta version
1.2.4a */ /* This file will not be overwritten */ #include <gtkmm.h> #include <iostream> #include <stdlib.h> #include <libglademm/xml.h> // DECLARACIÓN DE LA CLASE // //////////////////////// primera clase /////////////////////////////// class miventanica { public: miventanica(Glib::RefPtr<Gnome::Glade::Xml>); virtual ~miventanica(); Gtk::Window * yo; protected: int veces; Glib::RefPtr<Gnome::Glade::Xml> refXml; Gtk::Button* boton; Gtk::Label* etiqueta; virtual void pulsa_contador(); }; /////////////////////////////////// segunda clase //////////////////////////// class miventanica2 { public: miventanica2(Glib::RefPtr<Gnome::Glade::Xml>); virtual ~miventanica2(); Gtk::Dialog * yo; protected: Glib::RefPtr<Gnome::Glade::Xml> refXml; Gtk::Button* botonAceptar; virtual void pulsa_botonAceptar(); }; // DEFINICIÓN DE LOS MÉTODOS DE LA CLASE // constructor de la primera clase miventanica::miventanica(Glib::RefPtr<Gnome::Glade::Xml> xxx) { veces=0; refXml = xxx; refXml->get_widget("miventanica", yo); refXml->get_widget("boton", boton); boton->signal_clicked().connect(sigc::mem_fun(*this,&miventanica::pulsa_contador)); refXml->get_widget("etiqueta", etiqueta); } // destructor miventanica::~miventanica(){std::cout<<"Eso es todo amigo"<<std::endl;} // función para manipular la señal void miventanica::pulsa_contador(){ if (veces==0){ miventanica2 ventanita(refXml); ventanita.yo->run(); } char h[30]; sprintf (h, "has pulsado %i veces el botón", ++veces); etiqueta->set_text(h); } /////////////////////////////////////// 2ª clase miventanica2::miventanica2(Glib::RefPtr<Gnome::Glade::Xml> xxx) { refXml = xxx; refXml->get_widget("dialogo", yo); refXml->get_widget("botonAceptar", botonAceptar); botonAceptar->signal_clicked().connect(sigc::mem_fun(*this,&miventanica2::pulsa_botonAceptar)); } miventanica2::~miventanica2(){} void miventanica2::pulsa_botonAceptar(){ yo->hide();} //////////////////////////// MAIN ////////////////////////////// int main(int argc, char *argv[]) { Gtk::Main programa(argc, argv); Glib::RefPtr<Gnome::Glade::Xml> refXml; try { refXml = Gnome::Glade::Xml::create("../gladeconclase.glade"); } catch(const Gnome::Glade::XmlError& ex) { std::cerr << ex.what() << std::endl; return 1; } miventanica ventana(refXml); Gtk::Main::run(*ventana.yo); return 0; } |
|
Explicación:La función main(): En este caso, hemos utilizado try ... catch. Estas son dos palabras claves que permiten al código informar de los errores, y manipularlos sin necesidad de abortar el programa. Cuando verificamos con un if ( xxxx ), donde xxxx es un puntero al que hemos asignado una parte de la memoria dinámica, esta operación solo comprueba si el puntero era NULL, operación fallida, o no lo era (asignación correcta). Pero carecemos de las razones por las que se ha producido el error, lo cual en programas extensos puede ser desalentador. Con try...catch, throw, la clase exception, podemos generar excepciones y gestionarlas, informando de ellas. La clase exception, de la cual heredará Gnome::Glade::XmlError, tiene una función virtual what() que devuelve una cadena de texto (para informar del tipo de error). El programa intenta ejecutar el contenido de try, el método Gnome::Glade::Xml::create(), si hubiera algún problema éste lanzará la excepción correspondiente (con throw -cosa que aquí no se ve-) , la excepción se gestiona en catch, que no hace sino imprimir la cadena del error correspondiente y salir del programa de forma errónea (return 1;). miventanica ventana(refXml); Llegado este punto, refXml se habrá creado correctamente. Con esta instrucción, creo el objeto ventana, llamando a la función miventanica::miventanica (Glib::RefPtr<Gnome::Glade::Xml> xxx) refXml (de main) es pasado como xxx al constructor, allí la instrucción refXml=xxx; asigna a refXml (de la clase) el valor de xxx, o sea el valor refXml de main. pulsar_contador(): Dentro de la función pulsar_contador si veces es igual a 0 [la primera vez] se crea una clase que contiene el cuadro de diálogo de la misma forma que con ventana y se lanza. Ventanita que se cierra al pulsar en [Aceptar] o en la cruz del cuadro de diálogo. Si la clase fuese derivada de Gtk::Dialog, tan sólo sería necesario ventanita->run() o ventanita->hide(), pero como no lo es, es preciso ventanita.yo->run() o ventanita.yo->hide() Pero veamos como se pueden utilizar clases derivadas (la propuesta de gtk.org): Existe un método en Gnome::Glade::Xml: get_widget_derived("nombre_glade_widget", puntero_clase_derivada);
que permite asignar una widget de un archivo glade a una clase derivada, pero impone dos obligaciones al constructor:
|
|
/* Created by Anjuta version 1.2.4a */ /* This file will not be overwritten */ #include <gtkmm.h> #include <iostream> #include <stdlib.h> #include <libglademm/xml.h> // DECLARACIÓN DE LA CLASE // //////////////////////// primera clase /////////////////////////////// class miventanica:public Gtk::Window { public: miventanica(BaseObjectType* www, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade); virtual ~miventanica(); protected: int veces; Glib::RefPtr<Gnome::Glade::Xml> refXml; Gtk::Button* boton; Gtk::Label* etiqueta; // señales virtual void pulsa_contador(); }; /////////////////////////////////// segunda clase //////////////////////////// class miventanica2:public Gtk::Dialog { public: miventanica2(BaseObjectType* www, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade); virtual ~miventanica2(); protected: Glib::RefPtr<Gnome::Glade::Xml> refXml; Gtk::Button* botonAceptar; virtual void pulsa_botonAceptar(); }; // DEFINICIÓN DE LOS MÉTODOS DE LA CLASE // constructor de la primera clase miventanica::miventanica(BaseObjectType* www, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade):Gtk::Window(www),refXml(refGlade) { veces=0; refXml->get_widget("boton", boton); boton->signal_clicked().connect(sigc::mem_fun(*this,&miventanica::pulsa_contador)); refXml->get_widget("etiqueta", etiqueta); } // destructor miventanica::~miventanica(){std::cout<<"Eso es todo amigo"<<std::endl;} // función para manipular la señal void miventanica::pulsa_contador(){ if (veces==0){ miventanica2* ventanita; refXml->get_widget_derived("dialogo", ventanita); ventanita->run(); } char h[30]; sprintf (h, "has pulsado %i veces el botón", ++veces); etiqueta->set_text(h); } /////////////////////////////////////// 2ª clase miventanica2::miventanica2(BaseObjectType* www, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade):Gtk::Dialog(www),refXml(refGlade) { refXml->get_widget("botonAceptar", botonAceptar); botonAceptar->signal_clicked().connect(sigc::mem_fun(*this,&miventanica2::pulsa_botonAceptar)); } miventanica2::~miventanica2(){} void miventanica2::pulsa_botonAceptar(){ hide();} //////////////////////////// MAIN ////////////////////////////// int main(int argc, char *argv[]) { Gtk::Main programa(argc, argv); Glib::RefPtr<Gnome::Glade::Xml> refXml; try { refXml = Gnome::Glade::Xml::create("../gladeconclase.glade"); } catch(const Gnome::Glade::XmlError& ex) { std::cerr << ex.what() << std::endl; return 1; } miventanica* ventana; refXml->get_widget_derived("miventanica", ventana); if(ventana){ Gtk::Main::run(*ventana); } return 0; } |
|
Puedes observar varias cosas:
Afortunadamente, es algo sencillo. Justo antes de return 0; debemos añadir delete ventana; y después de ventanita->run y antes de la llave }, delete ventanita; Y así, O.K. |
|
Glademm, Dejando solito a Glade... |