Interfaces gráficas I: Probamos la librería en los programas de diferentes tipos y en el terminal MetaTrader 4 (Capítulo 5)

Anatoli Kazharski | 22 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 capítulo anterior de la primera parte de la serie sobre las interfaces gráficas, en la clase del formulario han sido añadidos los métodos que permiten manejar el formulario con los clics en los controles. Hasta ahora las pruebas se realizaban en los programas tipo “Asesor Experto” y sólo en el terminal MetaTrader 5. En el presente artículo vamos a testear el trabajo realizado en diferentes tipos de programas MQL, como indicadores y scripts. En vista de que se ha planteado la tarea de diseñar una librería multiplataforma (en marco de las plataformas comerciales MetaTrader), también realizaremos las pruebas en MetaTrader 4.

 

Uso del formulario en los indicadores

Por favor, cree una carpeta separada para el nuevo indicador en el directorio de los indicadores del terminal MetaTrader 5 (<carpeta_de_datos>\MQL5\Indicators). Igual que en el caso con el Asesor Experto (EA) que ha sido probado anteriormente, en esta carpeta hay que crear el archivo principal del programa y el archivo Program.mqh en el que va a ubicarse la clase CProgram. En realidad, copie simplemente el archivo Program.mqh desde la carpeta de aquél EA en la carpeta con el indicador. Por ahora este código será suficiente para testear el formulario de controles en el indicador. En el archivo principal hay que crear el código, como se muestra más abajo.

La línea marcada con el color amarillo significa que el indicador va a encontrarse en la ventana principal del gráfico. Se puede poner el número de los búferes de indicador a cero, puesto que ahora simplemente estamos probando los controles de la interfaz gráfica en el indicador y no vamos a realizar los cálculos con los arrays de precios.

//+------------------------------------------------------------------+
//|                                                  ChartWindow.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0
//--- Incluir la clase del panel de trading
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(void)
  {
   program.OnInitEvent();  
//--- Colocamos el panel de trading
   if(!program.CreateTradePanel())
     {
      ::Print(__FUNCTION__," > ¡Fallo al crear la interfaz gráfica!");
      return(INIT_FAILED);
     }
//--- Inicialización con éxito
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   program.OnDeinitEvent(reason);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int    rates_total,     // tamaño del array price[]
                 const int    prev_calculated, // barras procesadas en la llamada anterior
                 const int    begin,           // inicio de datos importantes
                 const double &price[])        // array para el cálculo
  {
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
   program.OnTimerEvent();
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   program.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Por favor, compile el indicador y cárguelo en el gráfico. Tiene que obtener el resultado que se muestra en la captura de pantalla de abajo. Igual que en el EA, Usted puede desplazar el formulario por el gráfico, minimizar/maximizar, sus controles van a reaccionar al movimiento del cursor, eliminar el indicador del gráfico haciendo clic en el botón “Cerrar” (cruz en la esquina superior derecha).

Fig. 1. Prueba del formulario en el indicador en la ventana principal del gráfico.

Fig. 1. Prueba del formulario en el indicador en la ventana principal del gráfico

Fíjense también en el icono del formulario en la esquina superior izquierda. El programa ha identificado automáticamente el tipo del programa y ha colocado la imagen por defecto. No olvide que en esta fase del desarrollo de la clase CWindow ya existe la posibilidad de restablecer esta imagen.

El formulario para los controles funciona en el indicador sin algún cambio significativo del código. Pero si ahora intentamos crear un indicador con el mismo formulario fuera de la ventana principal del gráfico, no todo va a funcionar tal como se espera: (1) el formulario se colocará en la ventana principal del gráfico, en vez de la subventana del indicador como se debe, (2) el clic en el botón “Cerrar” no provocará la eliminación del indicador del gráfico. ¿Qué es los que pasa? A continuación, vamos a tratar de corregirlo.

En realidad, no hay nada que corregir de lo que hemos hecho antes. Bastará con crear un método en la clase CWndEvents en el que va a determinarse automáticamente el número de la ventana en el programa dependiendo de su tipo y la cantidad de otros indicadores en el gráfico, que se encuentran en las subventanas. Vamos a llamar este método DetermineSubwindow(). En el inicio de este método tiene que haber una verificación que comprueba que este programa es un indicador. Si no es un indicador, no tiene sentido seguir adelante, ya que de todos los tipos de los programas MQL sólo los indicadores se ubican en subventanas.

Luego, hay que obtener el número de la ventana del indicador usando la función ChartWindowFind(). Si eso no se ha conseguido y la función ha devuelto -1, en el registro se muestra un mensaje sobre ello y el método finaliza su trabajo. Si el número de la ventana es más de cero, o sea no se trata de la ventana principal del gráfico, es necesario comprobar si hay otros indicadores en esta subventana. Si es así, nuestro indicador va a eliminarse del gráfico, avisando sobre la razón de la eliminación en el registro. Para eso, hay que obtener el número total de los indicadores en la subventana especificada usando la función ChartIndicatorsTotal(). Hay que obtener el nombre corto del último indicador en la lista, y luego, eliminar el programa del gráfico si el número de indicadores no es igual a 1. Es obligatorio obtener el nombre corto del indicador porque éste puede ser eliminado del gráfico sólo si se indica su nombre corto.

Por favor, añada la declaración e implementación del método DetermineSubwindow() en la clase CWndEvents, como se muestra a continuación:

class CWndEvents : public CWndContainer
  {
private:
   //--- Determinar el número de la subventana
   void              DetermineSubwindow(void);
  };
//+------------------------------------------------------------------+
//| Determinar el número de la subventana                                       |
//+------------------------------------------------------------------+
void CWndEvents::DetermineSubwindow(void)
  {
//--- Si no es un indicador, salimos
   if(PROGRAM_TYPE!=PROGRAM_INDICATOR)
      return;
//--- Resetear el último error
   ::ResetLastError();
//--- Determinar el número de la ventana del indicador
   m_subwin=::ChartWindowFind();
//--- Si no se ha podido determinar el número, salimos
   if(m_subwin<0)
     {
      ::Print(__FUNCTION__," > Fallo al determinar el número de la subventana: ",::GetLastError());
      return;
     }
//--- Si no es la ventana principal del gráfico
   if(m_subwin>0)
     {
      //--- Obtenemos el número total de los indicadores en la subventana especificada
      int total=::ChartIndicatorsTotal(m_chart_id,m_subwin);
      //--- Obtenemos el nombre corto del último indicador en la lista
      string indicator_name=::ChartIndicatorName(m_chart_id,m_subwin,total-1);
      //--- Si en la ventana hay un indicador, eliminar el programa del gráfico
      if(total!=1)
        {
         ::Print(__FUNCTION__," > Esta ventana ya contiene un indicador.");
         ::ChartIndicatorDelete(m_chart_id,m_subwin,indicator_name);
         return;
        }
     }
  }

La llamada a este método tiene que realizarse en el constructor de la clase CWndEvents:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWndEvents::CWndEvents(void)
  {
//--- Determinar el número de la subventana
   DetermineSubwindow();
  }

Pues ahora, nuestra librería está lista para los indicadores que se crean fuera de la ventana principal del gráfico, y a continuación hay que probar como eso funciona. Si en el proceso de la prueba se detectan errores o defectos, vamos a corregirlos.

Para las interfaces gráficas que van a ubicarse en las subventanas, están previstos tres modos principales:

  1. Modo libre. El alto de la subventana no es fijo, es decir el usuario puede cambiarlo libremente. Al minimizar el formulario de los controles, el alto de la subventana del indicador no se cambia y el usuario puede modificarlo libremente.
  2. Fijo. Se puede fijar el alto de la subventana del indicador según el tamaño del alto especificado del formulario de controles. Al minimizar el formulario, el alto de la subventana queda el mismo en el modo fijo también.
  3. Fijo con posibilidad de minimizar. Eso significa que cuando el indicador se carga en el gráfico, el tamaño de la subventana se fija según el tamaño del alto especificado del formulario, pero cuando se minimiza obtiene el tamaño del encabezado del formulario.

Para establecer uno de estos modos, en la clase CWindow ya ha sido creado el método RollUpSubwindowMode(). Pronto demostraremos cómo se debe usarlo.

Luego, vamos a crear tres indicadores para demostrar cada uno de los modos. Para que sea más interesante, del parámetro interno de estos indicadores va a servir la enumeración (lista desplegable) en la que se puede elegir una de tres opciones de colocación del formulario:

  1. en la parte izquierda de la ventana del gráfico;
  2. en la parte derecha de la ventana del gráfico;
  3. ocupando el ancho entero del gráfico.

Al seleccionar la tercera opción, el ancho del formulario debe cambiarse cuando se cambia el ancho del gráfico. Actualmente, en la clase CWindow todavía no hay método que permita hacerlo. Vamos a crear este método y llamarlo ChangeWindowWidth(). En el inicio del método el ancho actual se compara con el valor que ha sido pasado en el método. Si se pasa un valor distinto, entonces hay que modificar el ancho de objetos del formulario y actualizar las coordenadas de los botones.

Declaración e implementación del método CWindow::ChangeWindowWidth():

class CWindow : public CElement
  {
public:
   //--- Cambia el ancho de la ventana
   void              ChangeWindowWidth(const int width);
  };
//+------------------------------------------------------------------+
//| Cambia el ancho de la ventana                                             |
//+------------------------------------------------------------------+
void CWindow::ChangeWindowWidth(const int width)
  {
//--- Si el ancho no ha cambiado, salimos
   if(width==m_bg.XSize())
      return;
//--- Actualizamos el ancho para el fondo y encabezamiento
   CElement::XSize(width);
   m_bg.XSize(width);
   m_bg.X_Size(width);
   m_caption_bg.XSize(width);
   m_caption_bg.X_Size(width);
//--- Actualizamos las coordenadas y sangrías para todos los botones:
//--- Botón de cierre
   int x=CElement::X2()-CLOSE_BUTTON_OFFSET;
   m_button_close.X(x);
   m_button_close.XGap(x-m_x);
   m_button_close.X_Distance(x);
//--- Botón para maximizar
   x=CElement::X2()-ROLL_BUTTON_OFFSET;
   m_button_unroll.X(x);
   m_button_unroll.XGap(x-m_x);
   m_button_unroll.X_Distance(x);
//--- Botón para minimizar
   m_button_rollup.X(x);
   m_button_rollup.XGap(x-m_x);
   m_button_rollup.X_Distance(x);
//--- Botón “Ayudas emergentes” (si está activado)
   if(m_tooltips_button)
     {
      x=CElement::X2()-TOOLTIP_BUTTON_OFFSET;
      m_button_tooltip.X(x);
      m_button_tooltip.XGap(x-m_x);
      m_button_tooltip.X_Distance(x);
     }
  }

Haga tres copias del indicador que ha sido creado antes para las pruebas en la ventana principal del gráfico. Cada uno de ellos será para uno de los modos especificados anteriormente. Llame a cada uno de ellos con un nombre único.

Por ejemplo:

  • FreeHeight — para el modo libre.
  • RollUp — para el modo fijo.
  • DownFall — para el modo fijo con posibilidad de minimizar.

Hay que reemplazar sólo una línea en el código del archivo principal de estos indicadores. En vez de poner que el indicador va a crearse en la ventana principal, es necesario especificar que el indicador va a crearse en una subventana:

//+------------------------------------------------------------------+
//|                                                   FreeHeight.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property indicator_separate_window
#property indicator_buffers 0
#property indicator_plots   0

A continuación, en el inicio de cada archivo incluido Program.mqh hay que añadir la enumeración (enum) y el parámetro externo (input) para elegir el modo de colocación del formulario:

//--- Enumeración de los modos de colocación de la ventana
enum ENUM_WINDOW_MODE
  {
   LEFT  =0,
   RIGHT =1,
   FULL  =2
  };
//--- Parámetros externos
input ENUM_WINDOW_MODE WindowMode=LEFT;

Si en los ajustes externos se selecciona la opción RIGHT o FULL, en el momento cuando va a cambiarse el tamaño de la ventana del gráfico, el programa va a corregir las coordenadas del formulario (en el modo RIGHT) o su tamaño (en el modo FULL) de tal manera que su lado derecho no salga fuera de los límites del gráfico.

Creo que no tiene ningún sentido hacer que el formulario sea movible en un espacio tan reducido como la subventana del indicador. En la implementación actual del formulario, si en sus propiedades se indica que no se puede desplazarlo, la corrección no va a realizarse al cambiarse el tamaño del gráfico. Por eso para los formularios inmóviles, la corrección se realiza en el manejador de eventos del gráfico de la aplicación MQL que se desarrolla. En todos los archivos Program.mqh de los indicadores creados, hay que añadir el código de abajo en el manejador CProgram::OnEvent():

//+------------------------------------------------------------------+
//| Manejador de eventos del gráfico                                       |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Si está seleccionado el modo del ancho entero del gráfico
      if(WindowMode==FULL)
         m_window.ChangeWindowWidth(m_chart.WidthInPixels()-2);
      //--- Si está seleccionado el modo de colocación del formulario a la derecha
      else if(WindowMode==RIGHT)
         m_window.X(m_chart.WidthInPixels()-(m_window.XSize()+1));
     }
  }

Como podemos ver, en este momento todo es absolutamente igual en todos los archivos de nuestros indicadores. Por fin, hemos llegado a la respuesta de cómo utilizar el método CWindow::RollUpSubwindowMode() para establecer el modo de la subventana del indicador en la que se ubica el formulario de los controles.

Por defecto, los valores de las variables de la clase CWindow, a los que se orienta el programa para determinar el modo de la subventana del indicador, se inicializan en el constructor de la clase para el uso del modo, cuando se puede cambiar el alto de la subventana del indicador. Por eso para el indicador FreeHeight se puede no usar este método en absoluto. El código del método para colocar el formulario en la subventana, en la clase CProgram va a tener el siguiente aspecto, como se muestra más abajo. Fíjense que el ancho del formulario y sus coordenadas se determinan en función de la opción seleccionada en los ajustes externos. Con el color amarillo se marca el lugar donde hay que usar el método CWindow::RollUpSubwindowMode() en los indicadores con otros modos.

//+------------------------------------------------------------------+
//| Crea el formulario para los controles                           |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- Añadimos el puntero de la ventana en el array de ventanas
   CWndContainer::AddWindow(m_window);
//--- Tamaños
   int x_size=(WindowMode!=FULL)? 205 : m_chart.WidthInPixels()-2;
   int y_size=243;
//--- Coordenadas
   int x=(WindowMode!=RIGHT)? 1 : m_chart.WidthInPixels()-(x_size+1);
   int y=1;
//--- Propiedades
   m_window.XSize(x_size);
   m_window.YSize(y_size);
// ...
 //--- Creación del formulario
   if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

El indicador RollUp va a utilizarse en el modo cuando la subventana del indicador tenga el alto fijo y no va a cambiarse al minimizar el formulario. Por eso hay que añadir la siguiente línea en el lugar marcado con amarillo en el código más arriba:

m_window.RollUpSubwindowMode(false,true);

El valor del primer parámetro significa que no hay que minimizar la subventana al minimizar el formulario, y el valor del segundo parámetro significa que la subventana tiene que ser del alto fijo, igual al alto del formulario. Para el indicador DownFall los parámetros en el método CWindow::RollUpSubwindowMode() tienen que ser los siguientes:

m_window.RollUpSubwindowMode(true,true);

Es decir, el valor del primer parámetro establece el modo cuando, al minimizar el formulario, el alto de la subventana será igual al alto del encabezado del formulario.

Por favor, compile los archivos modificados y los archivos de los indicadores. Pruebe cada uno de ellos en el gráfico. En cuanto a los modos establecidos en ellos, todo funciona correctamente. Pero va a notar que las coordenadas (en el modo RIGHT) y el ancho del formulario (en el modo FULL) no se corrigen cuando se cambian los tamaños de la ventana del gráfico. ¡No tiene nada de asombro! Es que ahora el flujo de eventos no llega al manejador de eventos del gráfico OnEvent(), en la clase CProgram. Antes, durante el diseño del esquema principal de la librería, hemos mencionado que de la clase CWndEvents en el método ChartEvent() se puede enviar el flujo de eventos del gráfico al manejador local OnEvent() que se encuentra en la clase CProgram, es decir en la clase de la aplicación desarrollada.

Vamos a colocar el envío del evento justo después del repaso de todos los manejadores de todos los controles en el método CWndEvents::CheckElementsEvents():

//+------------------------------------------------------------------+
//| Comprobación de los eventos de controles                            |
//+------------------------------------------------------------------+
void CWndEvents::CheckElementsEvents(void)
  {
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].OnEvent(m_id,m_lparam,m_dparam,m_sparam);
//--- Envío del evento en el archivo de la aplicación
   OnEvent(m_id,m_lparam,m_dparam,m_sparam);
  }

Pero ahora la corrección de las coordenadas del formulario en el modo RIGHT tampoco va a efectuarse de forma correcta. Es que cuando el programa entra en el manejador de la clase CWindow según el evento CHARTEVENT_CHART_CHANGE, no actualizará las coordenadas del formulario, ya que en el método UpdateWindowXY() la actualización de las coordenadas se realiza si el formulario es móvil. Recordamos que en los indicadores (como hemos concertado antes) el formulario va a ser fijo. Después de repasar todos los controles, el programa irá al manejador de eventos del gráfico en la clase de la aplicación CProgram, como ha sido hecho en el listado del código de arriba, y actualizará ahí la coordenada X de acuerdo con el ancho actual de la ventana del gráfico.

Luego el programa sale del método CheckElementsEvents() en la clase CWndEvents, entra en el método CWndEvents::ChartEventMouseMove() y sale de él en seguida ya que el evento no pertenece a los eventos del ratón. Actualmente, la actualización de la posición del formulario de acuerdo con las coordenadas actuales en las propiedades del formulario se realiza sólo en el método CWndEvents::ChartEventMouseMove(). Por eso ha llegado el momento para añadir en el método CWndEvents::ChartEvent() el procesamiento del evento CHARTEVENT_CHART_CHANGE (marcado con amarillo en el código).

//+------------------------------------------------------------------+
//| Manejo de eventos del programa                                      |
//+------------------------------------------------------------------+
void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Si el array está vacío, salimos
   if(CWndContainer::WindowsTotal()<1)
      return;
 //--- Inicialización de los campos de los parámetros de eventos
   InitChartEventsParams(id,lparam,dparam,sparam);
//| Comprobación de los eventos de controles de la interfaz
   CheckElementsEvents();
//--- Evento de desplazamiento
   ChartEventMouseMove();
//--- Evento del cambio de propiedades del gráfico
   ChartEventChartChange();
  }

En el cuerpo del método CWndEvents::ChartEventChartChange() hay que añadir la llamada a la función del desplazamiento de la ventana según las coordenadas actuales y el redibujo del gráfico:

//+------------------------------------------------------------------+
//| Evento CHARTEVENT CHART CHANGE                                  |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventChartChange(void)
  {
//--- Evento del cambio de propiedades del gráfico
   if(m_id!=CHARTEVENT_CHART_CHANGE)
      return;
//--- Desplazamiento de la ventana
   MovingWindow();
//--- Redibujamos el gráfico
   m_chart.Redraw();
  }

Por favor, compile los archivos de la librería e indicadores. Intente ahora probarlos cargando en el gráfico. Ahora las coordenadas del formulario en el modo RIGHT se actualizan de forma correcta.

Hay un detalle más que no ha sido considerado hasta este momento. En la implementación actual, cuando en el gráfico se carga uno de los indicadores creados con el formulario de los controles, el programa determina automáticamente el número de la subventana en el método CWndEvents::DetermineSubwindow() en el constructor de la clase. Pero si cargamos uno de estos indicadores en el gráfico que ya contiene algún otro indicador que también se ubica en una subventana, todo va a funcionar bien hasta que este indicador siga encontrándose en el gráfico. Con eliminarlo del gráfico, la ventana de nuestro indicador recibe un nuevo número en la lista de las ventanas del gráfico, y como la librería necesita el número actual de la ventana en la que trabaja, por ejemplo cuando se determina la coordenada relativa, entonces algunas funcionalidades van a funcionar de forma incorrecta. Por eso hay que controlar el número de las ventanas del gráfico. En cuanto su cantidad se cambie, hay que actualizar los valores en los campos de las clases donde es necesario.

Cuando se cargan y se eliminan los programas del gráfico, se genera el evento CHARTEVENT_CHART_CHANGE. Por eso ahora hay que colocar la comprobación en el método CWndEvents::ChartEventChartChange(). Vamos a crear un nuevo método para esta comprobación que llamaremos CWndEvents::CheckSubwindowNumber(). En el listado del código de abajo se muestra la declaración e implementación de este método. Primero, se lleva a cabo la comprobación de la correspondencia del número de la ventana del programa con el número que ha sido guardado al cargarse el programa en el gráfico. Si no coinciden, es necesario determinar de nuevo el número actual de la ventana en la que se encuentra el programa, y luego actualizarlo en todos los controles del formulario.

class CWndEvents : public CWndContainer
  {
private:
   //--- Comprobación de los eventos de controles
   void              CheckSubwindowNumber(void);
   //---
  };
//+------------------------------------------------------------------+
//| Comprobación y actualización del número de la ventana del programa                       |
//+------------------------------------------------------------------+
void CWndEvents::CheckSubwindowNumber(void)
  {
//--- Si el programa en la subventana y los números no coinciden
   if(m_subwin!=0 && m_subwin!=::ChartWindowFind())
     {
      //--- Determinar el número de la subventana
      DetermineSubwindow();
      //--- Guardar en todos los controles
      int windows_total=CWndContainer::WindowsTotal();
      for(int w=0; w<windows_total; w++)
        {
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].SubwindowNumber(m_subwin);
        }
     }
  }

Es necesario realizar la llamada a este método en el método CWndEvents::ChartEventChartChange():

//+------------------------------------------------------------------+
//| Evento CHARTEVENT CHART CHANGE                                  |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventChartChange(void)
  {
//--- Evento del cambio de propiedades del gráfico
   if(m_id!=CHARTEVENT_CHART_CHANGE)
      return;
//--- Comprobación y actualización del número de la ventana del programa
   CheckSubwindowNumber();
//--- Desplazamiento de la ventana
   MovingWindow();
//--- Redibujamos el gráfico
   m_chart.Redraw();
  }

Ahora todo va a funcionar tal como se ha planteado. Al final del artículo, se puede descargar todos los indicadores para la prueba de los modos mencionados.

Fig. 2. Prueba del formulario en los indicadores en las subventanas del gráfico.

Fig. 2. Prueba del formulario en los indicadores en las subventanas del gráfico

En la implementación actual, para un correcto trabajo no se recomienda usar en un gráfico más de una aplicación en la que durante su desarrollo se utilizaba la librería para la construcción de interfaces gráficas. En la captura de pantalla de arriba, en un solo gráfico se muestran varios indicadores sólo para una demostración compacta de todos los modos analizados, y con el fin de ahorrar el espacio en el artículo. El conflicto entre las aplicaciones que utilizan la librería va a surgir en el momento de activación/desactivación del desplazamiento del gráfico. Ya existen algunas ideas sobre cómo se puede evitar este conflicto entre las aplicaciones, por eso si las pruebas se superan y la solución resulta adecuada, será publicada en uno de los futuros artículos de esta serie.

 

Uso del formulario en los scripts

Ya hemos considerado el uso del formulario en los EAs e indicadores. ¿Y se puede usar el formulario en los scripts? La respuesta es sí. Claro que en los scripts todo va a funcionar de una forma muy limitada. En los script no hay manejadores de eventos. Por esta razón, (1) no se puede mover el formulario, (2) no tiene sentido colocar los controles en el formulario que sirven para algunas manipulaciones, (3) los objetos gráficos no van a reaccionar a los movimientos del ratón.

Pero si necesita crear rápidamente un panel informativo que trabaja durante el trabajo del script mostrando al usuario, por ejemplo, algunos datos estadísticos, ¿por qué no utilizar el formulario como base para eso? Además, debido al uso de una librería, todas sus aplicaciones van a tener el mismo diseño independientemente del tipo del programa MQL al que pertenecen.

Por favor, cree la carpeta para el archivo principal del script y el archivo Program.mqh. La clase CProgram en este archivo va a ser derivada de CWndEvents, como ha sido en el EA e indicadores. En total, todo será lo mismo, salvo que va a haber sólo un manejador OnEvent(). El evento para este manejador va a generarse de forma artificial en el archivo principal del script usando el ciclo infinito. Este método tendrá sólo un parámetro que significa el número de milisegundos para la pausa antes de salir del método.

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                    |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
protected:
   //--- Ventana
   CWindow           m_window;
   //---
public:
                     CProgram(void);
                    ~CProgram(void);
   //--- Manejadores del evento
   virtual void      OnEvent(const int milliseconds);
   //--- Crea el panel informativo
   bool              CreateInfoPanel(void);
   //---
protected:
   //--- Crea el formulario
   bool              CreateWindow(const string text);
  };

Como ejemplo, con el fin de imitar por lo menos algún proceso, en el método CProgram::OnEvent() vamos a cambiar el texto del encabezado del formulario dentro de los intervalos iguales al número de milisegundos traspasados. Vamos a hacer los puntos suspensivos en movimiento que muestran la ejecución de algún proceso. En el código de abajo se muestra su implementación:

//+------------------------------------------------------------------+
//| Eventos                                                          |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int milliseconds)
  {
   static int count =0;  // Contador
   string     str   =""; // Barra de encabezado
//--- Formación del encabezado con la marcha del proceso
   switch(count)
     {
      case 0 : str="SCRIPT PANEL";     break;
      case 1 : str="SCRIPT PANEL .";   break;
      case 2 : str="SCRIPT PANEL ..";  break;
      case 3 : str="SCRIPT PANEL ..."; break;
     }
//--- Actualizar la barra del encabezado
   m_window.CaptionText(str);
 //--- Redibujar el gráfico
   m_chart.Redraw();
//--- Aumentamos el contador
   count++;
//--- Si es más de tres, poner a cero
   if(count>3)
      count=0;
//--- Pausa
   ::Sleep(milliseconds);
  }

Ahora en el archivo principal, en la función OnStart() bastará con crear el formulario y organizar la llamada continua del método CProgram::OnEvent(). En el código de abajo se muestra que este método va a llamarse cada 250 milisegundos. Esto va a funcionar hasta que la función IsStopped() vaya devolviendo el valor false. Para detener este ciclo eterno, simplemente hay que eliminar el script manualmente del gráfico. Cuando el usuario elimina el programa, la función IsStopped() devuelve el valor false, el ciclo se detiene y el script termina su trabajo.

//+------------------------------------------------------------------+
//|                                                    InfoPanel.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.0"
//--- Incluir la clase del panel de trading
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart(void)
  {
//--- Colocamos el panel informativo
   if(!program.CreateInfoPanel())
     {
      ::Print(__FUNCTION__," > ¡Fallo al crear la interfaz gráfica!");
      return;
     }
//--- El script va a seguir trabajando hasta que no sea eliminado
   while(!::IsStopped())
     {
      //--- Generar el evento cada 250 milisegundos
      program.OnEvent(250);
     }
  }
//+------------------------------------------------------------------+

Compile los archivos e inicie el script en el gráfico:

Fig. 3. Prueba del formulario en el script.

Fig. 3. Prueba del formulario en el script

En la captura de pantalla se ve que la librería ha identificado el tipo del programa y ha colocado la imagen predeterminada como icono. Al final del artículo se puede descargar este script. Luego, para otros ejemplos será completado con los controles que pertenecen al grupo informativo. Este script, así como el EA e indicadores de este artículo, se puede utilizar como plantillas durante el desarrollo de sus propios programas.

 

Uso de la librería en MetaTrader 4

La misma librería para la creación de las interfaces gráficas se puede utilizar en la plataforma comercial MetaTrader 4. Por favor, copie todos los archivos de la librería y programas que han sido creados en el artículo del directorio del terminal MetaTrader 5 al directorio de MetaTrader 4. Pues, la librería prácticamente ya está lista para el uso.

Lo único, para que durante la compilación no surjan errores, en algunos archivos de la librería es necesario añadir un parámetro específico que va a indicar al compilador que hay que usar un modo estricto especial para la comprobación de errores. Para eso, en el inicio de los archivos claves de la librería hay que insertar la siguiente línea del código:

#property strict

Hágalo en los archivos WndContainer.mqh, Element.mqh, así como en los archivos principales de los programas. Ahora no va a producirse ningún error durante la compilación del código en estos archivos.

Si ha hecho todo de forma correcta y ya tiene compilados todos los archivos, pruebe todos los programas del artículo en el terminal MetaTrader 4. Todo tiene que funcionar como en MetaTrader 5.

Fig. 4. Prueba de la librería en el terminal MetaTrader 4.

Fig. 4. Prueba de la librería en el terminal MetaTrader 4

 

Conclusión

Se puede hacer un resumen intermedio del desarrollo de la librería para la creación de interfaces gráficas. En esta fase se puede representar la estructura de la librería tal como se muestra en el esquema de abajo. Creo que si ha leído el artículo con atención, el contenido de este esquema tiene que ser bastante simple, lógico y claro para la comprensión.

Fig. 5. Estructura de la librería en la fase actual del desarrollo

Fig. 5. Estructura de la librería en la fase actual del desarrollo

Sin embargo, voy a recordar brevemente qué es lo que significan los elementos en este esquema:

  • Las flechas azules significan la interacción entre las clases: base -> derivada 1 -> derivada 2 -> derivada N.
  • Las flechas amarillas significan la inclusión de un archivo en otro archivo. Pero si el archivo incluido contiene una clase, está no es una clase base para la clase del archivo en el que se incluye. Puede utilizarse como objeto dentro de la clase o clases de una cadena (base -> derivadas).
  • Los rectángulos de color marón claro con negrita representan las clases de la librería estándar del lenguaje MQL, los demás corresponden a la librería que desarrollamos (personalizada).
  • Los rectángulos blancos con el marco gris son las clases en un archivo separado, o bien los complementos que pertenecen a un determinado grupo, como: macros (define) o enumeraciones (enum).
  • Si el rectángulo gris con el marco azul grueso tiene el encabezado con el nombre del archivo, eso significa que todas las clases que se muestran dentro como rectángulos blancos con el marco azul se encuentran en el archivo indicado en el encabezado.
  • Si el rectángulo gris con el marco azul grueso no tiene encabezado, todas las clases dentro se ubican en archivos diferentes pero tienen una clase base común. Es decir si al rectángulo gris con el marco azul grueso se conecta una clase con la flecha azul, significa que es la clase base para todas las clases que se encuentran dentro del rectángulo gris.
  • El rectángulo de color marrón claro con el texto CChartObject… conectado al rectángulo gris con el marco azul grueso significa que una de las clases de la librería estándar (siendo un objeto primitivo) puede ser la clase base para una de las clases que se ubican dentro del rectángulo gris.

En la primera parte de esta serie de artículos ha sido mostrado al detalle el proceso de preparación de la estructura principal de la librería para la creación de interfaces gráficas, así como la creación del elemento principal de la interfaz, el “formulario” o “ventana”. Los demás controles van a adjuntarse a este formulario. En los siguientes artículos de esta serie vamos a mostrar el proceso de su creación. La continuación de esta serie va a ser útil ya que será mostrado varias veces y detalladamente el proceso de creación de su propio control, y cómo adjuntarlo a esta librería para que forme su parte integrante y no provoque conflictos con otros controles de la interfaz.

Más abajo se puede descargar los archivos comprimidos con los ficheros de la librería en esta fase del desarrollo, imágenes y ficheros de los programas examinados en el artículo (EA, indicadores y script) para realizar las pruebas en los terminales MetaTrader 4 y MetaTrader 5. 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: