Interfaces gráficas I: Funciones para los botones del formulario y eliminación de los elementos de la interfaz (Capítulo 4)

Anatoli Kazharski | 18 febrero, 2016

Índice

 

Introducción

Este artículo es la continuación de la primera parte de la serie sobre las interfaces gráficas. El primer artículo de la serie nos cuenta con más detalles para qué sirve esta librería: Interfaces gráficas I: Preparación de la estructura de la librería (Capítulo 1). Al final de cada capítulo se puede encontrar la lista completa de los enlaces a los artículos de la primera parte, así como descargar la versión completa de la librería en la fase actual del desarrollo del proyecto. Es necesario colocar los ficheros en los mismos directorios, tal como están ubicados en el archivo.

En el artículo anterior, hemos introducido en la clase CWindow los cambios adicionales que permiten desplazar el formulario por el gráfico, y sus controles ahora pueden reaccionar al movimiento del cursor. En el presente artículo vamos a continuar desarrollando la clase CWindow. La clase será ampliada con los métodos que permitirán gestionar el formulario haciendo clics en sus controles. Vamos a implementar la posibilidad de cerrar el programa usando el botón en el formulario, así como minimizar y maximizar el formulario en caso de necesidad.

 

Funciones para los botones del formulario

En el formulario de nuestra librería hay dos botones principales que son obligatorios para los Asesores Expertos e indicadores, y un botón adicional que se puede omitir:

  1. El primer botón a la derecha debe contener la función del cierre de la ventana. Aquí hay que precisar de qué manera eso va a implementarse exactamente. Si hacemos clic en este botón en el cuadro de diálogo que ha sido abierto desde la ventana principal u otro cuadro de diálogo, entonces este cuadro de diálogo será cerrado. Si hacemos clic en este botón en la ventana principal, el programa se elimina del gráfico.

  2. El segundo botón se compone de dos partes. Mejor dicho, se trata de dos botones cada uno de los cuales se visualiza dependiendo del modo actual. Si la ventana está ampliada, se muestra el botón para minimizar la ventana. Si la ventana está reducida, se muestra el botón para maximizarla.

  3. El tercer botón sirve para activar el modo de las ayudas emergentes personalizadas. Aquí hay que precisar qué es lo que se sobrentiende con las “ayudas emergentes personalizadas”. El lenguaje MQL permite mostrar las descripciones emergentes del texto para cada objeto gráfico que se encuentra en el foco del cursor del ratón.

    Usted ya ha debido de notar que en el código de la clase CWindow en los métodos de la creación de cada objeto del formulario, durante la indicación de los parámetros del objeto, se utilizaba el método CChartObject::Tooltip(). Si hace falta que un objeto tenga su descripción emergente, en el método Tooltip() hay que pasar un texto que debe aparecer cuando el cursor se sitúa sobre este objeto. Si es necesario que la descripción emergente no aparezca en absoluto, hay que pasar el texto "\n".

    Esta propiedad tiene sus limitaciones: (1) no se puede mostrar el texto que tenga más de 128 caracteres y (2) no se puede manejar las propiedades del texto (negrita, color, fuente). Pues, éstas no son ayudas emergentes personalizadas. Vamos a clasificarlas como las ayudas reglamentarias del lenguaje MQL. Estas descripciones son buenas para pequeñas aclaraciones, pero a veces hace falta visualizar el texto de un tamaño considerablemente más grande, de varias líneas, destacando algunas palabras.

    Creo que todo el mundo conoce el programa Excel. Como ejemplo, se puede echar un vistazo a las ayudas emergentes para todas las opciones en este programa (véase la captura de pantalla de abajo). En la estructura de nuestra librería vamos a crear su propia clase que permitirá visualizar las ayudas emergentes de similar calidad. Pues, precisamente a estas ayudas las vamos a llamar personalizadas, ya que nosotros mismos implementamos la funcionalidad para su creación. Sin embargo, de esta cuestión nos ocuparemos sólo después de que la clase del formulario para los controles esté implementada por completo, y después de que haya por lo menos un control que se puede adjuntar al formulario.

Fig. 1. Ayudas emergentes en Excel

Fig. 1. Ayudas emergentes en Excel

Bien, empezamos con el botón para el cierre de la ventana. Vamos a crear el método CloseWindow() en la clase CWindow. Será llamado en el manejador de eventos del gráfico CWindow::OnEvent(), en el cuerpo de la condición del procesamiento del clic en el objeto gráfico según el identificador CHARTEVENT_OBJECT_CLICK. El evento con este identificador se genera cuando se hace clic en cualquier objeto gráfico. El parámetro string de los eventos del gráfico (sparam) contiene el nombre del objeto el que ha sido pulsado. Por eso hay que comprobar el nombre del objeto pulsado para asegurarse de que se trata del objeto necesario.

El método CWindow::CloseWindow() va a recibir un solo parámetro: nombre del objeto pulsado. En el inicio del código del método va a comprobarse si ha sido pulsado el objeto que según nuestra intención es el botón para el cierre de la ventana. Luego el código del método se divide en dos partes: para la ventana principal y para el cuadro de diálogo. Puesto que el modo de ventanas múltiples todavía no está terminado, dejaremos la parte para el cuadro de diálogo en blanco. Volveremos a ella un poco más tarde. Para el trabajo con la ventana principal ya está todo listo. Por eso en el cuerpo de esta condición hacen falta las comprobaciones del tipo del programa (EA o indicador), porque para la eliminación de diferentes tipos de programas se utilizan funciones diferentes del lenguaje MQL.

El lenguaje MQL propone su función para la llamada del cuadro de diálogo para la confirmación de diferentes acciones del usuario. Es la función MessageBox(). Vamos a usarla temporalmente para confirmar la eliminación del Asesor Experto del gráfico. Para la eliminación del indicador, no se puede usar este cuadro porque pertenece al tipo modal. Es que, los indicadores se ejecutan en el flujo de la interfaz y no se puede frenarlos. Cuando tengamos implementado el modo de ventanas múltiples y el control “Botón”, podremos usar nuestro propio cuadro de diálogo, construido con los medios de la librería.

Antes de eliminar el programa del gráfico, en el registro de los Asesores Expertos va a mostrarse el mensaje sobre la eliminación del programa por la decisión del usuario.

Introduzca las adiciones en la clase CWindow donde se muestra en el código de abajo.

Declaración e implementación del método CWindow::CloseWindow() para el cierre de la ventana:

class CWindow: public CElement
  {
public:
   //--- Cierre de la ventana
   bool              CloseWindow(const string pressed_object);
  };
//+------------------------------------------------------------------+
//| Cierre del cuadro de diálogo o la ventana del programa           |
//+------------------------------------------------------------------+
bool CWindow::CloseWindow(const string pressed_object)
  {
//--- Si es clic fuera del botón del cierre de la ventana
   if(pressed_object!=m_button_close.Name())
      return(false);
//--- Si es laa ventana principal
   if(m_window_type==W_MAIN)
     {
      //--- Si es Asesor Experto
      if(CElement::ProgramType()==PROGRAM_EXPERT)
        {
         string text=“¿Desa eliminar el programa del gráfico?”;
         //--- Abrimos el cuadro de diálogo
         int mb_res=::MessageBox(text,NULL,MB_YESNO|MB_ICONQUESTION);
         //--- Si se pulsa el botón “Aceptar”, eliminamos el programa del gráfico
         if(mb_res==IDYES)
           {
            ::Print(__FUNCTION__," > ¡Programa se ha eliminado con éxito por su decisión!");
            //--- Eliminación del Asesor Experto del gráfico
            ::ExpertRemove();
            return(true);
           }
        }
      //--- Si es indicador
      else if(CElement::ProgramType()==PROGRAM_INDICATOR)
        {
         //--- Eliminación del indicador del gráfico
         if(::ChartIndicatorDelete(m_chart_id,m_subwin,CElement::ProgramName()))
           {
            ::Print(__FUNCTION__," > ¡Programa se ha eliminado con éxito por su decisión!");
            return(true);
           }
        }
     }
   //--- Si es cuadro de diálogo
   else if(m_window_type==W_DIALOG)
     {
     }
//---
   return(false);
  }

Llamada del método CWindow::CloseWindow() en el manejador de eventos del gráfico:

//+------------------------------------------------------------------+
//| Manejador de eventos del gráfico                                 |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del clic en el objeto
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Cerrar la ventana
      CloseWindow(sparam);
      return;
     }
  }

Por favor, compile los archivos de la librería y EA para las pruebas que ha sido utilizado anteriormente, y cárguelo en el gráfico. Si ahora hace clic en el botón del cierre de la ventana del formulario, aparece una ventana con el mensaje:

Fig. 2. Prueba del cierre del programa con el clic en el botón del formulario

Fig. 2. Prueba del cierre del programa con el clic en el botón del formulario

Luego vamos a crear los métodos para el segundo botón que permite minimizar y maximizar el formulario:

Estos métodos son sencillos y tienen el contenido muy parecido. En el inicio de cada método se reemplaza el icono, luego se establece y se memoriza el tamaño del fondo (alto), luego se pone a cero el foco y se establece el estatus correspondiente a la selección del usuario. Si el programa es un indicador fuera de la ventana principal del gráfico, y durante la creación del formulario ha sido establecido el modo del alto fijo de la subventana del indicador, así como ha sido establecido el modo que permite cambiar el tamaño de la subventana del indicador, entonces se llama el método CWindow::ChangeSubwindowHeight() que ha sido creado anteriormente y debe encontrarse en la clase CWindow.

El código de los métodos CWindow::RollUp() y CWindow::Unroll() se muestra con detalles a continuación:

//+------------------------------------------------------------------+
//| Minimiza la ventana                                              |
//+------------------------------------------------------------------+
void CWindow::RollUp(void)
  {
//--- Reemplazar el botón
   m_button_rollup.Timeframes(OBJ_NO_PERIODS);
   m_button_unroll.Timeframes(OBJ_ALL_PERIODS);
//--- Establecer y recordar los tamaños
   m_bg.Y_Size(m_caption_height);
   CElement::YSize(m_caption_height);
//--- Deshabilitar el botón
   m_button_unroll.MouseFocus(false);
   m_button_unroll.State(false);
//--- Estado del formulario “minimizado”
   m_is_minimized=true;
//--- Si es un indicador en la subventana con el alto fijo y el modo de reducción de la subventana,
//    establecemos el tamaño de la subventana del indicador
   if(m_height_subwindow_mode)
      if(m_rollup_subwindow_mode)
         ChangeSubwindowHeight(m_caption_height+3);
  }
//+------------------------------------------------------------------+
//| Maximiza la ventana                                              |
//+------------------------------------------------------------------+
void CWindow::Unroll(void)
  {
//--- Reemplazar el botón
   m_button_unroll.Timeframes(OBJ_NO_PERIODS);
   m_button_rollup.Timeframes(OBJ_ALL_PERIODS);
//--- Establecer y recordar los tamaños
   m_bg.Y_Size(m_bg_full_height);
   CElement::YSize(m_bg_full_height);
//--- Deshabilitar el botón
   m_button_rollup.MouseFocus(false);
   m_button_rollup.State(false);
//--- Estado del formulario “maximizado”
   m_is_minimized=false;
//--- Si es un indicador en la subventana con el alto fijo y el modo de reducción de la subventana,
//    establecemos el tamaño de la subventana del indicador
   if(m_height_subwindow_mode)
      if(m_rollup_subwindow_mode)
         ChangeSubwindowHeight(m_subwindow_height);
  }

Ahora hay que crear otro método que va a recibir el parámetro string de los eventos del gráfico según el cual va a comprobarse el nombre del objeto pulsado, igual que ha sido hecho en el método CWindow::CloseWindow(). Dependiendo del botón que ha sido pulsado, va a llamarse el método correspondiente de los mencionados en el código anterior. Vamos a llamar este método CWindow::ChangeWindowState(). Por favor, añada su declaración, implementación y la llamada en el manejador de eventos del gráfico en la clase CWindow, como se muestra a continuación:

class CWindow: public CElement
  {
public:
   //--- Cambio del estado de la ventana
   bool              ChangeWindowState(const string pressed_object);
  };
//+------------------------------------------------------------------+
//| Manejador de eventos del gráfico                                 |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del clic en el objeto
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Minimizar/maximizar la ventana
      ChangeWindowState(sparam);
      return;
     }
  }
//+------------------------------------------------------------------+
//| Comprobación del evento de minimizar/maximizar la ventana        |
//+------------------------------------------------------------------+
bool CWindow::ChangeWindowState(const string pressed_object)
  {
//--- Si ha sido pulsado el botón “Minimizar la ventana”
   if(pressed_object==m_button_rollup.Name())
     {
      RollUp();
      return(true);
     }
//--- Si ha sido pulsado el botón “Maximizar la ventana”
   if(pressed_object==m_button_unroll.Name())
     {
      Unroll();
      return(true);
     }
//---
   return(false);
  }

Por favor, compile los archivos de la librería que han sufrido modificaciones, y el archivo del programa destinado para las pruebas. El resultado del trabajo realizado tiene que ser la posibilidad de minimizar y maximizar el formulario para los controles. En la captura de pantalla de abajo se muestra el formulario en modo minimizado:

Fig. 3. Prueba de la funcionalidad del formulario.

Fig. 3. Prueba de la funcionalidad del formulario

Todo funciona bastante bien. Podemos desplazar el formulario para los controles por el gráfico sin ningún problema, cada objeto reacciona al cursor del ratón, y los botones trabajan de acuerdo con la funcionalidad declarada en ellos.

 

Eliminación de los controles de la interfaz

Si ha llegado a este momento y ha hecho todo en el mismo orden como se expone en el artículo, ha podido notar que cuando el EA se elimina del gráfico, todos los objetos de la interfaz gráfica se eliminan. Pero no hemos considerado aún los métodos para la eliminación de los objetos gráficos del gráfico. ¿Por qué los objetos se eliminan cuando se elimina el EA? Este momento se atiende en la librería estándar de las clases. A saber, en el destructor de la clase CChartObject, cuyas clases derivadas se utilizan en nuestra librería. Al ser eliminado el programa del gráfico, se llaman los destructores de las clases, inclusive éste. Y si el objeto ha sido adjuntado a este gráfico, entonces se elimina (véase el código):

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CChartObject::~CChartObject(void)
  {
   if(m_chart_id!=-1)
      ::ObjectDelete(m_chart_id,m_name);
  }

Pero si cambiamos el símbolo del gráfico o su período de tiempo cuando el EA se encuentra en el gráfico, los destructores no se llaman, y los objetos gráficos en este caso no se eliminan. Y como la interfaz gráfica se crea en la función de inicialización OnInit() en el archivo principal del programa, y cuando se cambia el símbolo o el período de tiempo del EA primero se realiza la deinicialización y luego la inicialización, entonces la interfaz gráfica se creará por encima de la que ya existe. Como resultado, la primera manipulación de este tipo le dará dos copias de objetos, y si va a seguir cambiando el símbolo o el período, obtendrá varias copias de objetos de la interfaz.

Fig. 4. Prueba del formulario al cambiar el símbolo y período de tiempo del gráfico.

Fig. 4. Prueba del formulario al cambiar el símbolo y período de tiempo del gráfico

Hay que tomarlo en cuenta en nuestra librería. Es decir, hay que hacer que todos los objetos gráficos se eliminen en el momento de la deinicialización del programa. Y todos los arrays que contienen los punteros a estos objetos se liberen de ellos, adquiriendo el tamaño de sus dimensiones igual a cero. Entonces, en el momento de la inicialización, la colocación de los objetos será realizada correctamente, sin que se coloquen los clones. Vamos a empezar la implementación del mecanismo descrito.

En la clase CElement ha sido declarado el método Delete(). En cada clase derivada de CElement va a haber su implementación de este método. Antes, en la clase CWindow ya ha sido declarado el método Delete() y ahora hay que implementarlo (véase el código de abajo). En este método van a ponerse a cero algunas variables, van a eliminarse todos los objetos del control, así como liberarse el array de punteros a los objetos en la clase base.

//+------------------------------------------------------------------+
//| Eliminación                                                      |
//+------------------------------------------------------------------+
void CWindow::Delete(void)
  {
//--- Puesta a cero de las variables
   m_right_limit=0;
//--- Eliminación de objetos
   m_bg.Delete();
   m_caption_bg.Delete();
   m_icon.Delete();
   m_label.Delete();
   m_button_close.Delete();
   m_button_rollup.Delete();
   m_button_unroll.Delete();
   m_button_tooltip.Delete();
//--- Liberación del array de objetos
   CElement::FreeObjectsArray();
//--- Puesta a cero del foco del control
   CElement::MouseFocus(false);
  }

Podemos acceder a los métodos Delete() de todos los controles de la interfaz desde la clase CWndEvents. Por eso a continuación, vamos a crear el método CWndEvents::Destroy() para eliminar la interfaz gráfica. Ahí hay que repasar sucesivamente los controles de todos los formularios usando el método Delete() de cada control. Antes de llamar el método Delete(), hay que comprobar la validez del puntero. Después de eliminar los objetos, hay que liberar los arrays de controles. Después de salir del ciclo de formularios, hay que liberar sus arrays también.

En el listado del código de abajo se muestra la declaración e implementación del método CWndEvents::Destroy():

class CWndEvents : public CWndContainer
  {
protected:
   //--- Eliminación de la interfaz
   void              Destroy(void);
  };
//+------------------------------------------------------------------+
//| Eliminación de todos los objetos                                 |
//+------------------------------------------------------------------+
void CWndEvents::Destroy(void)
  {
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         //--- Si el puntero no es válido, ir al siguiente
         if(::CheckPointer(m_wnd[w].m_elements[e])==POINTER_INVALID)
            continue;
         //--- Eliminar los objetos del control
         m_wnd[w].m_elements[e].Delete();
        }
       //--- Liberar los arrays de controles
      ::ArrayFree(m_wnd[w].m_objects);
      ::ArrayFree(m_wnd[w].m_elements);
     }
//--- Liberar los arrays de formularios
   ::ArrayFree(m_wnd);
   ::ArrayFree(m_windows);
  }

Ahora se puede llamar al método Destroy() en el método CProgram::OnDeinitEvent() que está relacionado con la función OnDeinit() en el archivo principal del programa. Por favor, añádalo ahí como se muestra:

//+------------------------------------------------------------------+
//| Deinicialización                                                 |
//+------------------------------------------------------------------+
void CProgram::OnDeinitEvent(const int reason)
  {
   //--- Eliminación de la interfaz
   CWndEvents::Destroy();  
  }

Por favor, compile todos los archivos de la librería recién modificados, así como el archivo del programa. Cargue el EA en el gráfico y cambie varias veces su símbolo y período de tiempo. Ahora todo debe funcionar correctamente. Los clones de objetos ya no aparecen. El problema está resuelto.

 

Conclusión

En el siguiente artículo vamos a realizar las pruebas para ver cómo se comporta el formulario en otros tipos de programas, como indicadores y scrpts. Además de eso, haremos las pruebas en el terminal MetaTrader 4. Pues, desde el principio teníamos puesta la tarea que la librería para la creación de las interfaces esté diseñada sin vinculación a una sola plataforma.

Más abajo puede descargar el material de la primera parte de la serie para poder probar cómo funciona todo eso. Si le surgen preguntas sobre el uso del material de estos archivos, puede dirigirse a la descripción detallada del proceso de desarrollo de la librería en uno de los artículos listados más abajo, o bien hacer su pregunta en los comentarios para el artículo.

Lista de artículos (Capítulos) de la primera parte: