Capítulo 3: ¡Qué aburrido! yo quiero interactuar

De doc.ubuntu-es
Saltar a: navegación, buscar

Contenido

Introducción

En este capítulo comenzaremos a preparar la interfaz para que pueda interactuar con el usuario, e introduciremos (ya que son la misma cosa) los eventos. Este será un paso de gigante, ya que nos permitirá introducir un par de órdenes esenciales para todo código que vaya a entrar en bucles largos, para ello nos crearemos una barra de progreso que no hara más que rellenarse y vaciarse, y unos botones de start y stop.

Sería interesante introducir una pantalla para imprimir cosas, pero eso parece ya demasiado para este capítulo.

Así que nuestro plan de trabajo será tal que:

  • Plan de trabajo del capítulo 3:
    • Manejo de eventos (I):
      Crearemos nuestra primera tabla de eventos, y aprenderemos a manejar nuestros primeros eventos
      • Evento cerrar:
        • Evento cerrar del menú:
          Crearemos la respuesta al evento de cerrar a través del menú "file/exit..."
        • Evento cerrar de la barra de título:
          Crearemos la respuesta al evento de cerrar a través de la "X" de la barra de título.
    • Creación de una ProgressBar:
      Crearemos una Barra de progresos, y sus controles.
      • Barra de progreso:
        Haciendo uso de la documentación encontraremos cual es el objeto que buscamos, y lo implementaremos.
      • Controles de la barra:
        Crearemos una pequeña barra de herramientas para controlar a esta barra de progreso.
    • Manejo de eventos (II):
      Crearemos todo lo necesario para manejar la barra de control.
      • Evento de "Start":
        El que nos permitirá poner a funcionar la barra de progreso.
      • Evento de "Stop":
        El que nos permitirá parar la barra.
        • Cola de eventos:
          No impacientarse, veremos que es lo que ocurre...


Manejo de eventos

En la programación con interfaz gráfica, el manejo de eventos se vuelve la llave que abre todas las puertas, y es que nos permite hacer cosas que en un principio solo se podrían hacer acudiendo a los multiprocesos. La idea básicamente consistirá en lo siguiente, cada uno de los elementos de la interfaz gráfica, cada vez que se actúe sobre él, emitirá un evento, de una clase o de otra dependiendo la acción que se haya llevado (porque evidentemente no deberá comportarse igual cuando cambies su tamaño, que cuando lo cierres). Ese evento lo anclaremos mediante tablas de eventos, a un método (método = función) que ejecutará las acciones pertinentes (las que nosotros deseemos para esa actuación).


Evento cerrar

Para manejar nuestro primer evento, ya dejamos preparado un menú tal que "file/exit...". Este evento será muy interesante, ya que deberá compartir sus acciones con el evento de cerrar presionando sobre la "X" de la ventana. Eso nos enseñará dos cosas, como manejar eventos que necesiten ID, y como manejar los que no lo necesiten (que no tienen ninguna diferencia, pero que nos enseñará que en algunos casos, los eventos no necesitan ID), y lo que es más importante, nos enseñará a hacer las cosas bien (para no duplicar código).

Evento cerrar del menú

Bien, así que recuperamos nuestro trabajo donde lo dejamos, y acudimos presto a abrir "topframe.h". Las tablas de eventos hay que predeclararlas, y eso se hace en nuestra cabecera, simplemente añadiendo las siguientes líneas:

 class TopFrame : public wxFrame
 {
 public:
     TopFrame(wxString Title = _T("Welcome to wxWidgets"));
 private:
     // any class wishing to process wxWidgets events must use this macro
     DECLARE_EVENT_TABLE()
 };

Lo cierto es que nuestros eventos serán una cosa privada (private:), que sólo la clase en cuestión debe manejar, ya no sólo porque es intuitivamente lo lógico, sino porque nos puede ahorrar algunos problemillas.

Bueno, hemos declarado la tabla de eventos, ahora vamos a declarar un método para controlar el evento. Llamémoslo por ejemplo "OnQuit", y lo pondremos como privado, ya que será el que maneje el evento... No obstante, puede ocurrir que más tarde algún elemento pueda tener la necesidad de cerrar el programa, así que la gestión de cierre deberá ser algo publico, así que crearemos otro método llamado "Quit", que será realmente el que nos cierre la aplicación (todo esto tendrá un transfondo mucho más interesante, que entenderemos más adelante). También necesitamos saber que tipo de evento manejaremos, así que en nuestra API (a partir de ahora denominaremos así al wx.chm) buscamos wxMenu, y en el apartado "Event handling", nos da este evento: "wxCommandEvent", que será el que habremos de usar...

Así pues, nuestra cabecera deberá modificarse así:

 class TopFrame : public wxFrame
 {
 public:
     TopFrame(wxString Title = _T("Welcome to wxWidgets"));    
     void Quit();
 private:
     void OnQuit(wxCommandEvent& event);    
     // any class wishing to process wxWidgets events must use this macro
     DECLARE_EVENT_TABLE()

};

Donde efectivamente llevamos a cabo todas las operaciones que planteabamos...

El siguiente punto sin duda debería ser implementar todo esto, así que abrimos "topframe.cpp", y lo primero de todo añadimos la tabla de eventos:

 BEGIN_EVENT_TABLE(TopFrame, wxFrame)  
 END_EVENT_TABLE()
 
 TopFrame::TopFrame(wxString Title)
 : wxFrame(NULL, wxID_ANY, Title)
 {
   (...)

Ahora recuperamos nuestra documentación, justo en el lugar donde la dejamos, y nos encontramos que el macro en la tabla de eventos es "EVT_MENU", pero necesitamos saber como funciona, así que lo buscamos, y nos encontramos "wxCommandEvent", lo abrimos, y alli esta: "EVT_MENU(id, func) "

  • id: Ya lo conocemos, le pusimos wxID_EXIT.
  • func: La función a la queremos anclarlo la hemos declarado como OnQuit.

Así que la tabla de eventos quedará de la forma:

 BEGIN_EVENT_TABLE(TopFrame, wxFrame)
     EVT_MENU(wxID_EXIT,  TopFrame::OnQuit)
 END_EVENT_TABLE()

Ahora debemos implementar nuestros dos métodos, empezemos con el que maneja el evento (OnQuit), para ello, al final del archivo añadimos las siguientes líneas:

 void TopFrame::OnQuit(wxCommandEvent& WXUNUSED(event))
 {
 }

Como no queremos sacrale más partido a este evento, pues con saber que se ha emitido la orden de cerrar nos es suficiente, ponemos como entrada "wxCommandEvent& WXUNUSED(event)". No actuaremos así por ejemplo cuando manejemos el ratón, pues necesitaremos información sobre donde estaba, o que botón apretó.

Bueno, este método lo único que hará será llamar a la función que realmente gestiona el cierre:

 void TopFrame::OnQuit(wxCommandEvent& WXUNUSED(event))
 {
     Quit();
 }

Con esta argucia hemos arreglado un problema interesante, pues imaginemos que algún otro objeto, o algún otro método quisieran cerrar la aplicación, de ser el gestor el anterior método, para llamarle sería necesario generar un evento, pues necesitariamos ese parámetro, pero ahora van a poder llamar a Quit(), y listos.

  • Esto se puede hacer de muchas maneras, una sería por ejemplo poner un evento por defecto, tal y como hicimos con el título de TopFrame, no obstante, esta manera me parece más legible y elegante.

Ahora toca, casi a la fuerza, implementar nuestro método "Quit", así que nuevamente al final de archivo añadimos:

 void TopFrame::Quit()
 {
 }

Y este método lo único que habrá de hacer bserá cerrar la aplicación:

 void TopFrame::Quit()
 {
     // true is to force the frame to close
     Close(true);
 }

Esa orden que hemos añadido lo que hace es crear un evento de cerrar, que es un evento que tiene la peculiaridad de tener una función ya asociada que se encarga de cerrarlo por nosotros, pero en breves momentos cambiaremos esa situación a nuestro favor.

Bueno, ya podemos hacer alguna prueba, compilamos, y ejecutamos. Vamos al menú "file", y seleccionamos "Exit Alt-F4". ¡Y se cierra! ¡Lo hemos logrado!

Ya sabemos crear menús, y anclarlos a eventos que apuntaran a los métodos que nos interese, pero vamos a seguir rizando el rizo...

Evento cerrar de la barra de título

Bueno, hasta el momento nos las hemos apañado para que nuestro "Exit Alt-F4" sea capaz de cerrar la aplicación, y como el botoncito de cerrar de la barra de título ya lo tenía implementado, pues efectivamente, en primera instancia habríamos terminado. Pero supongamos que nuestro programa puede estar manejando algun archivo, o requiere, al cerrar, borrar una serie de archivos temporales (algo muy típico por otra parte), o que por ejemplo queramos que el programa pregunte si queremos guardar anters de salir (más típico todavia). En la concepción actual, podríamos meterlo en la función Quit(), pero entonces al darle a cerrar en la barra de título no funcionaría, necesitamos un nuevo evento, y un método asociado a él.

Así que lo primero es encontrar información de lo que buscamos, para ello vamos a nuestra API, y buscamos "close event", e inmediatamente nos aparece "wxCloseEvent", así que lo ojeamos, y nos da la solución: "EVT_CLOSE(func)".

Pues bien, ya tenemos lo que necesitamos para empezar, y por tanto abrimos "topframe.h" para añadir el siguiente método:

 class TopFrame : public wxFrame
 {
 public:
     TopFrame(wxString Title = _T("Welcome to wxWidgets"));
 
     void Quit();
 private:
     void OnQuit(wxCommandEvent& event);
     void OnQuitX(wxCloseEvent& event);
     // any class wishing to process wxWidgets events must use this macro
     DECLARE_EVENT_TABLE()
 };

Y ahora añadimos a la tabla de eventos en "topfrmae.cpp":

 BEGIN_EVENT_TABLE(TopFrame, wxFrame)
     EVT_MENU(wxID_EXIT,  TopFrame::OnQuit)
     EVT_CLOSE(TopFrame::OnQuitX)
 END_EVENT_TABLE()

Y al final del archivo el método:

 void TopFrame::OnQuitX(wxCloseEvent& WXUNUSED(event))
 {
 }

La orden que permitirá el "escape" de la aplicación será la destrucción de TpoFrame. A los locos de C++, en wxWidgets las cosas no se destruyen con delete, sino que implementan una función llamada Destroy() que hace todas las operaciones necesarias, y termina por llamar a delete. Con esta premisa ya podemos completar nuestro método de la forma:

 void TopFrame::OnQuitX(wxCloseEvent& WXUNUSED(event))
 {
     /**************************************/
     /***** | Aqui todas las ordenes | *****/
     /***** |  previas al cerrado    | *****/
     /******V************************V******/
     // Por ejemplo
     // remove( "temporal.tmp" );
     /******A************************A******/
     /***** | Aqui todas las ordenes | *****/
     /***** |  previas al cerrado    | *****/
     /**************************************/  
 
     Destroy();
 }

Compilar, y vereis como el programa se comporta exactamente igual.

  • Resumiendo... ¿Qué esta ocurriendo realmente? Pues lo que esta ocurriendo es lo siguiente:
    • Cuando se presiona el botón de cerrar de la barra de título:
      1.- Se crea un wxCloseEvent
      2.- Se acude a la tabla de eventos a este punto: EVT_CLOSE(TopFrame::OnQuitX)
      3.- Le redirecciona a la función: TopFrame::OnQuitX
      4.- Este método llama a: Destroy(); Que destruye "TopFrame"
      5.- Con TopFrame destruido el programa queda fuera de flujo, y por tanto se termina.
    • Cuando se usa la barra de menús:
      1.- Se crea un wxCommandEvent
      2.- Se acude a la tabla de eventos a este punto: EVT_MENU(wxID_EXIT, TopFrame::OnQuit)
      3.- Le redirecciona a la función:TopFrame::OnQuit
      4.- Esta llama a:Close(true);
      5.- Se genera un wxCloseEvent
      6.- Se repite todo el punto anterior.

¿A que ahora si veís claro por qué reservamos una función especial para gestionar el cierre, y cómo funciona todo el sistema de eventos?

¡Ya esta! ¿Qué más puedo necesitar saber? Pues aún hay algo que te puedo enseñar (realmente el único conocimiento nuevo de verdad que me queda por dar, ya que los próximos capítulos afianzarán ideas, y mostrarán algunos elementos, pero no introducirán nada nuevo).

Y para ver este nuevo conocimiento necesitamos algunas cosas, como pueda ser una progress bar, y unos controles...

Creación de una ProgressBar:

 Contruyendo...
Capítulo 2.- ¡Esta vivo! ¡vivoooooo! ¡JAJAJA! Nuestra primera interfaz gráfica con CodeBlocks y wxWidgets Capítulo 4: Soy un artista... ¡No me coartes!
Herramientas personales