English Русский 中文 Deutsch 日本語 Português
Interfaces gráficas I: "Animar" la interfaz gráfica (Capítulo 3)

Interfaces gráficas I: "Animar" la interfaz gráfica (Capítulo 3)

MetaTrader 5Ejemplos | 17 febrero 2016, 09:42
1 125 0
Anatoli Kazharski
Anatoli Kazharski

Índice

 

Introducción

Este artículo es la continuación de la primera parte de la serie sobre las interfaces gráficas. El artículo Interfaces gráficas I: Preparación de la estructura de la librería (Capítulo 1) cuenta con más detalles para qué sirve esta librería. 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 de esta serie sobre la creación de las interfaces gráficas hemos empezado a desarrollar la clase del formulario para los controles. En el presente artículo continuaremos el desarrollo de la clase llenándola con los métodos para poder mover el formulario dentro del área del gráfico, así como integraremos este elemento de la interfaz en el núcleo de la librería. Además de eso, configuraremos todo de tal manera que, al situar el cursor sobre los controles del formulario, éstos cambien su color.

 

Gestión de la interfaz gráfica

El formulario para los controles se coloca en el gráfico pero en este momento se encuentra absolutamente inactivo. Nuestra tarea actual consiste en hacer que el formulario y sus controles empiecen a reaccionar a las acciones del usuario. Para poder realizarlo, hay que seguir la posición del cursor sobre el gráfico. El programa tiene que “saber” las coordenadas del cursor en cualquier momento. No se puede realizar esta tarea con los parámetros del gráfico predefinidos en la aplicación MQL. Es necesario activar el seguimiento de las posiciones del cursor y los cliqueos de los botones del ratón. Adelantándome un poco, quiero señalar que en el proceso del desarrollo nos hará falta utilizar algunas propiedades más del gráfico, por eso incluiremos la clase de la librería estándar CChart en el archivo WndEvents.mqh de nuestra librería, y crearemos su instancia en el cuerpo de la clase.

//+------------------------------------------------------------------+
//|                                                    WndEvents.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Defines.mqh"
#include "WndContainer.mqh"
#include <Charts\Chart.mqh>
//+------------------------------------------------------------------+
//| Clase para procesar los eventos                                  |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
protected:
   CChart            m_chart;
  };

En el constructor de la clase adjuntamos el objeto al gráfico actual, obteniendo su identificador, y activamos el seguimiento del desplazamiento del cursor. En el destructor hay que desadjuntarlo del gráfico (marcado en verde más abajo). De lo contrario, al eliminar el programa del gráfico, el mismo gráfico también va a cerrarse.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWndEvents::CWndEvents(void) : m_chart_id(0),
                               m_subwin(0),
                               m_indicator_name(""),
                               m_program_name(PROGRAM_NAME)
  {
//--- Obtenemos ID del gráfico actual
   m_chart.Attach();
//--- Activamos el seguimiento de los eventos del ratón
   m_chart.EventMouseMove(true);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CWndEvents::~CWndEvents(void)
  {
//--- Desadjuntar del gráfico
   m_chart.Detach();
  }

Como ya se mencionado antes, en la clase de cada control habrá su manejador de eventos. Vamos ahora a la clase CWindow y crearemos varios métodos que nos harán falta para gestionar el formulario. Todos ellos van a llamarse en el método CWindow::OnEvent(). Definiremos la funcionalidad que vamos a añadir en la clase CWindow:

1. Puesto que el gráfico puede componerse de varias partes (es decir, el gráfico principal + subventanas de indicadores, además de que la aplicación MQL puede ser el indicador que se encuentra fuera de la ventana principal), hay que determinar en qué ventana exactamente se encuentra el cursor.

2. Si la aplicación MQL es un indicador y se encuentra fuera de la ventana principal, habrá que corregir la coordenada Y.

3. Hay que comprobar el estado del botón izquierdo del ratón, así como dónde ha sido pulsado. Hay cuatro estados:

  • NOT_PRESSED — botón no pulsado.
  • PRESSED_OUTSIDE — botón pulsado fuera del área del formulario.
  • PRESSED_INSIDE_WINDOW — botón pulsado dentro del área del formulario.
  • PRESSED_INSIDE_HEADER — botón pulsado dentro del área del encabezado.

Añadimos la enumeración ENUM_WMOUSE_STATE en el archivo Enums.mqh:

//+------------------------------------------------------------------+
//|                                                        Enums.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Enumeración de los estados del botón izquierdo del ratón para    |
//| el formulario                                                    |
//+------------------------------------------------------------------+
enum ENUM_WMOUSE_STATE
  {
   NOT_PRESSED           =0,
   PRESSED_OUTSIDE       =1,
   PRESSED_INSIDE_WINDOW =2,
   PRESSED_INSIDE_HEADER =3
  };

4. Si el cursor se encuentra dentro del área del formulario o cualquier otro control de la interfaz, hay que desactivar el desplazamiento del gráfico y gestión de niveles comerciales.

5. Si el cursor se encuentra dentro del área de captura del encabezado, y además el botón izquierdo se mantiene pulsado, el programa pasa al modo de actualización de coordenadas del formulario.

6. Durante la actualización de las coordenadas, se llevará a cabo la comprobación respecto a la salida fuera del área del gráfico y la corrección correspondiente. Para esta comprobación necesitaremos (1) el objeto de la clase CChart, (2) las variables y el método para obtener las dimensiones del gráfico.

7. Hace falta el método para mover todos los objetos del formulario respecto a las coordenadas actualizadas. Para este propósito, ya tenemos declarado el método CWindow::Moving(). Ahora hay que implementarlo.

8. Además de eso, hay que crear los métodos auxiliares para la determinación del cursor en el área del encabezado y puesta a cero de algunas variables de servicio.

9. El seguimiento del foco del ratón va a funcionar para todos los objetos que componen la ventana. Por eso vamos a crear el método en el que va a llevarse a cabo esta comprobación.

10. No siempre es necesario crear la interfaz gráfica con posibilidades de su desplazamiento por el gráfico. Por eso, vamos a añadir el método que nos permita activar/desactivar esta opción.

 

Funcionalidad para poder desplazar el formulario

Vamos a empezar a implementar todo lo mencionado anteriormente. En el cuerpo de la clase añadimos las declaraciones de los métodos necesarios y las variables. Incluimos el archivo con la clase CChart y creamos su instancia (marcado en amarillo):

//+------------------------------------------------------------------+
//|                                                       Window.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include <Charts\Chart.mqh>
//+------------------------------------------------------------------+
//| Clase de creación del formulario para los controles              |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   CChart            m_chart;
   //--- Posibilidad de desplazar la ventana en el gráfico
   bool              m_movable;
   //--- Dimensiones del gráfico
   int               m_chart_width;
   int               m_chart_height;
   //--- Variables relacionadas con el desplazamiento
   int               m_prev_x;        // Punto de fijación X al pulsar
   int               m_prev_y;        // Punto de fijación Y al pulsar
   int               m_size_fixing_x; // Distancia de la coordenada X hasta el punto de fijación X
   int               m_size_fixing_y; // Distancia de la coordenada Y hasta el punto de fijación Y
   //--- Estado del botón del ratón tomando en cuenta dónde ha sido pulsado
   ENUM_WMOUSE_STATE m_clamping_area_mouse;
   //---
public:
   //--- Posibilidad de desplazamiento de la ventana
   bool              Movable(void)                                     const { return(m_movable);                  }
   void              Movable(const bool flag)                                { m_movable=flag;                     }
   //--- Obtener las dimensiones del gráfico
   void              SetWindowProperties(void);
   //--- Convierte la coordenada Y en la relativa
   void              YToRelative(const int y);
   //--- Comprobación del cursor dentro del área del encabezado
   bool              CursorInsideCaption(const int x,const int y);
   //--- Puesta a cero de las variables
   void              ZeroPanelVariables(void);
   //--- Comprobación del foco del ratón
   void              CheckMouseFocus(const int x,const int y,const int subwin);
   //--- Comprobación del estado del botón izquierdo del ratón
   void              CheckMouseButtonState(const int x,const int y,const string state);
   //--- Establecer el modo del gráfico
   void              SetChartState(const int subwindow_number);
   //--- Actualización de las coordenadas del formulario
   void              UpdateWindowXY(const int x,const int y);
  };

El código de los métodos SetWindowProperties(), YToRelative(), CursorInsideCaption(), ZeroPanelVariables() es bastante sencillo y no requiere explicaciones adicionales. Lo único que merece la pena recordar, hay que estar atento cuando el número de la subventana (m_subwin) se pasa en los métodos del objeto CChart. Tiene que ser el número de la subventana en la que se encuentra el programa MQL.

//+------------------------------------------------------------------+
//| Obtener las dimensiones del gráfico                              |
//+------------------------------------------------------------------+
void CWindow::SetWindowProperties(void)
  {
//--- Obtenemos el ancho y el alto de la ventana del gráfico
   m_chart_width  =m_chart.WidthInPixels();
   m_chart_height =m_chart.HeightInPixels(m_subwin);
  }
//+------------------------------------------------------------------+
//| Convierte la coordenada Y en la relativa                         |
//+------------------------------------------------------------------+
int CWindow::YToRelative(const int y)
  {
//--- Obtenemos la distancia desde la parte superior del gráfico hasta la subventana del indicador
   int chart_y_distance=m_chart.SubwindowY(m_subwin);
//--- Convierte la coordenada Y en la relativa
   return(y-chart_y_distance);
  }
//+------------------------------------------------------------------+
//| Comprobación de la posición del cursor dentro del área del       |
//| encabezado                                                       |
//+------------------------------------------------------------------+
bool CWindow::CursorInsideCaption(const int x,const int y)
  {
   return(x>m_x && x<X2()-m_right_limit && y>m_y && y<m_caption_bg.Y2());
  }
//+------------------------------------------------------------------+
//| Puesta a cero de las variables relacionadas con el desplazamiento|
//| de la ventana y el estado del botón izquierdo del ratón          |
//+------------------------------------------------------------------+
void CWindow::ZeroPanelVariables(void)
  {
   m_prev_x              =0;
   m_prev_y              =0;
   m_size_fixing_x       =0;
   m_size_fixing_y       =0;
   m_clamping_area_mouse =NOT_PRESSED;
  }

En el método CWindow::CheckMouseButtonState() se realiza la comprobación del estado del botón izquierdo del ratón respecto al formulario. Para eso en el método se pasan la coordenadas del cursor y el parámetro string del modelo eventual del gráfico, que muestra el estado del botón izquierdo del ratón cuando se procesa el evento CHARTEVENT_MOUSE_MOVE. Es decir, este parámetro puede mostrar el valor “0” cuando el botón está suelto, y “1” cuando está pulsado.

Si el botón está suelto, todas las variables de servicio se ponen a cero y el trabajo del método se finaliza. Si el botón está pulsado, en el método CWindow::CheckMouseButtonState() se comprueba en qué área del gráfico está pulsado. Si resulta que ya tiene un estado registrado (es decir, ya ha sido pulsado en alguna área del gráfico), el programa saldrá del método (return).

//+------------------------------------------------------------------+
//| Comprueba el estado del botón del ratón                          |
//+------------------------------------------------------------------+
void CWindow::CheckMouseButtonState(const int x,const int y,const string state)
  {
//--- Si el botón está suelto
   if(state=="0")
     {
      //--- Ponemos las variables a cero
      ZeroPanelVariables();
      return;
     }
//--- Si el botón está pulsado
   if(state=="1")
     {
      //--- Salimos si el estado ya está registrado
      if(m_clamping_area_mouse!=NOT_PRESSED)
         return;
      //--- Fuera del área del panel
      if(!CElement::MouseFocus())
         m_clamping_area_mouse=PRESSED_OUTSIDE;
      //--- Dentro del área del panel
      else
        {
         //--- Si dentro del área del encabezado
         if(CursorInsideCaption(x,y))
           {
            m_clamping_area_mouse=PRESSED_INSIDE_HEADER;
            return;
           }
         //--- Si dentro del área de la ventana
         m_clamping_area_mouse=PRESSED_INSIDE_WINDOW;
        }
     }
  }

En todas las clases de los objetos primitivos del archivo Objects.mqh han sido previstos los métodos para determinar los límites del objeto. Ahora estos métodos nos permiten averiguar rápidamente si se encuentra un objeto dentro del foco del ratón. Usando el método CheckMouseFocus() en la clase CWindow, se puede comprobar el foco para el formulario y para todos sus objetos. En este método van a pasarse las coordenadas actuales del cursor y el número de la subventana en la que se encuentra. Mostraremos más tarde cómo obtener el número de la subventana en la que se encuentra el cursor.

//+------------------------------------------------------------------+
//| Comprobación del foco del ratón                                  |
//+------------------------------------------------------------------+
void CWindow::CheckMouseFocus(const int x,const int y,const int subwin)
  {
//--- Si el cursor está en el área del programa
   if(subwin==m_subwin)
     {
      //--- Si ahora no es el modo de desplazamiento del formulario
      if(m_clamping_area_mouse!=PRESSED_INSIDE_HEADER)
        {
         //--- Comprobamos la posición del cursor
         CElement::MouseFocus(x>m_x && x<X2() && y>m_y && y<Y2());
         //---
         m_button_rollup.MouseFocus(x>m_button_rollup.X() && x<m_button_rollup.X2() && 
                                    y>m_button_rollup.Y() && y<m_button_rollup.Y2());
         m_button_close.MouseFocus(x>m_button_close.X() && x<m_button_close.X2() && 
                                   y>m_button_close.Y() && y<m_button_close.Y2());
         m_button_unroll.MouseFocus(x>m_button_unroll.X() && x<m_button_unroll.X2() && 
                                    y>m_button_unroll.Y() && y<m_button_unroll.Y2());
        }
     }
   else
     {
      CElement::MouseFocus(false);
     }
  }

Según los resultados de comprobación del foco del formulario y estado del botón del ratón respecto al formulario, se puede saber si hace falta activar o desactivar el desplazamiento (scrolling) del gráfico y la gestión de niveles comerciales. En caso de no desactivar estas propiedades del gráfico en el momento cuando el cursor se encuentra sobre el formulario, el scrolling del gráfico va a realizarse junto con el desplazamiento del formulario, y la gestión de los niveles comerciales va a realizarse debajo del formulario, lo que no nos interesa en absoluto.

//+------------------------------------------------------------------+
//| Determinamos el estado del gráfico                               |
//+------------------------------------------------------------------+
void CWindow::SetChartState(const int subwindow_number)
  {
//--- Si (el cursor está dentro del área del panel y el botón está suelto) o
//    el botón ha sido pulsado dentro del área del formulario o encabezado
   if((CElement::MouseFocus() && m_clamping_area_mouse==NOT_PRESSED) || 
      m_clamping_area_mouse==PRESSED_INSIDE_WINDOW ||
      m_clamping_area_mouse==PRESSED_INSIDE_HEADER)
     {
      //--- Desactivamos el scroling y la gestión de niveles comerciales
      m_chart.MouseScroll(false);
      m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,false);
     }
//--- Activamos la gestión si el cursor está fuera del área de la ventana
   else
     {
      m_chart.MouseScroll(true);
      m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,true);
     }
  }

La actualización de las coordenadas de la ventana se realiza en el método CWindow::UpdateWindowXY(). Al principio se comprueba el modo de la ventana. Si el formulario está establecido en el modo fijo, el programa sale del método ya que las actualizaciones de las coordenadas no tienen sentido. Luego, si el botón del ratón está pulsado, se guardan las coordenadas actuales, se guarda la distancia desde el punto extremo del formulario hasta el cursor, se calculan los límites para las salidas fuera del área del gráfico, se hacen las comprobaciones, y si hace falta, las correcciones de las coordenadas para el formulario. Puede estudiar este código más abajo:

//+------------------------------------------------------------------+
//| Actualización de las coordenadas de la ventana                   |
//+------------------------------------------------------------------+
void CWindow::UpdateWindowXY(const int x,const int y)
  {
 //--- Si está establecido el modo del formulario fijo
   if(!m_movable)
      return;
//---
   int new_x_point =0; // Nueva coordenada X
   int new_y_point =0; // Nueva coordenada Y
//--- Límites
   int limit_top    =0;
   int limit_left   =0;
   int limit_bottom =0;
   int limit_right  =0;
//--- Si el botón del ratón está pulsado
   if((bool)m_clamping_area_mouse)
     {
      //--- Recordamos las coordenadas actuales XY del cursor
      if(m_prev_y==0 || m_prev_x==0)
        {
         m_prev_y=y;
         m_prev_x=x;
        }
      //--- Recordamos la distancia desde el punto extremo del formulario hasta el cursor
      if(m_size_fixing_y==0 || m_size_fixing_x==0)
        {
         m_size_fixing_y=m_y-m_prev_y;
         m_size_fixing_x=m_x-m_prev_x;
        }
     }
//--- Establecemos los límites
   limit_top    =y-::fabs(m_size_fixing_y);
   limit_left   =x-::fabs(m_size_fixing_x);
   limit_bottom =m_y+m_caption_height;
   limit_right  =m_x+m_x_size;
//--- Si no salimos de los límites del gráfico abajo/arriba/derecha/izquierda
   if(limit_bottom<m_chart_height && limit_top>=0 && 
      limit_right<m_chart_width && limit_left>=0)
     {
      new_y_point =y+m_size_fixing_y;
      new_x_point =x+m_size_fixing_x;
     }
//--- Si hemos salido de los límites del gráfico
   else
     {
      if(limit_bottom>m_chart_height) // > abajo
        {
         new_y_point =m_chart_height-m_caption_height;
         new_x_point =x+m_size_fixing_x;
        }
      if(limit_top<0) // > arriba
        {
         new_y_point =0;
         new_x_point =x+m_size_fixing_x;
        }
      if(limit_right>m_chart_width) // > a la derecha
        {
         new_x_point =m_chart_width-m_x_size;
         new_y_point =y+m_size_fixing_y;
        }
      if(limit_left<0) // > a la izquierda
        {
         new_x_point =0;
         new_y_point =y+m_size_fixing_y;
        }
     }
//--- Actualizamos las coordenadas si ha habido desplazamiento
   if(new_x_point>0 || new_y_point>0)
     {
      //--- Corregimos las coordenadas del formulario
      m_x =(new_x_point<=0)? 1 : new_x_point;
      m_y =(new_y_point<=0)? 1 : new_y_point;
      //---
      if(new_x_point>0)
         m_x=(m_x>m_chart_width-m_x_size-1) ? m_chart_width-m_x_size-1 : m_x;
      if(new_y_point>0)
         m_y=(m_y>m_chart_height-m_caption_height-1) ? m_chart_height-m_caption_height-2 : m_y;
      //--- Ponemos a cero los puntos de fijación
      m_prev_x=0;
      m_prev_y=0;
     }
  }

Al procesar el evento de desplazamiento del ratón (CHARTEVENT_MOUSE_MOVE) en el manejador de eventos del gráfico OnEvent() de la clase CWindow, se puede obtener el número de la subventana usando la función ChartXYToTimePrice(). Si la función ha devuelto true, a través del método CWindow::YToRelative() creado anteriormente obtenemos la coordenada relativa Y y después de eso pasamos por todos los métodos enumerados arriba:

//+------------------------------------------------------------------+
//| Manejador de eventos del gráfico                                 |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      int      x      =(int)lparam; // Coordenada por el eje X
      int      y      =(int)dparam; // Coordenada por el eje Y
      int      subwin =WRONG_VALUE; // Número de ventana en el que se encuentra el cursor
      datetime time   =NULL;        // Tiempo correspondiente a la coordenada X
      double   level  =0.0;         // Nivel (precio) correspondiente a la coordenada Y
      int      rel_y  =0;           // Para obtener la coordenada relativa Y
      //--- Obtenemos la posición del cursor
      if(!::ChartXYToTimePrice(m_chart_id,x,y,subwin,time,level))
         return;
      //--- Obtenemos la coordenada relativa Y
      rel_y=YToRelative(y);
      //--- Comprobamos y recordamos el estado del botón del ratón
      CheckMouseButtonState(x,rel_y,sparam);
      //--- Comprobación del foco del ratón
      CheckMouseFocus(x,rel_y,subwin);
      //--- Establecemos el estado del gráfico
      SetChartState(subwin);
      //--- Si la gestión ha sido pasada a la ventana, determinamos su posición
      if(m_clamping_area_mouse==PRESSED_INSIDE_HEADER)
        {
         //--- Actualización de las coordenadas de la ventana
         UpdateWindowXY(x,rel_y);
        }
      return;
     }
  }

Nos queda implementar el método CWindow::Moving() para poder probar el desplazamiento del formulario. Se puede pensar que este método va a utilizarse en el manejador interno de la clase inmediatamente después del método UpdateWindowXY(), pero no es así. En realidad, si lo hacemos ahora, cuando en el formulario no hay otros controles, el resultado será perfecto. Siguiendo esta lógica, va a pensar que hay que hacer lo mismo en las clases de los demás controles. En total, cuando en el formulario habrá varios controles, durante el desplazamiento del formulario todos los controles van a moverse inmediatamente después con un cierto retraso. Y eso sí que será un resultado bastante negativo.

Es que si cada método para el desplazamiento de un control de la interfaz se hace en el manejador interno, múltiples condiciones y comprobaciones diferentes van a separarlos. Precisamente por esta razón va a surgir el retardo. Para que todos los controles se desplazcan simultáneamente, no tendrá que haber ninguna otra operación entre el uso de los métodos Moving() de cada uno de ellos. Todo eso se puede implementar en la clase CWndEvents, porque siendo la clase derivada de CWndContainer, tiene el acceso directo a todos los punteros a objetos que van a guardarse ahí.

El contenido del método CWindow::Moving() se compone de dos partes. Primero, se guardan las coordenadas para todos los objetos que forman parte del formulario, luego se realiza la actualización de las coordenadas de los objetos en el gráfico. De la misma manera, el método Moving() será implementado para cada control en el futuro.

//+------------------------------------------------------------------+
//| Desplazamiento de la ventana                                     |
//+------------------------------------------------------------------+
void CWindow::Moving(const int x,const int y)
  {
//--- Guardando las coordenadas en las variables
   m_bg.X(x);
   m_bg.Y(y);
   m_caption_bg.X(x);
   m_caption_bg.Y(y);
   m_icon.X(x+m_icon.XGap());
   m_icon.Y(y+m_icon.YGap());
   m_label.X(x+m_label.XGap());
   m_label.Y(y+m_label.YGap());
   m_button_close.X(x+m_button_close.XGap());
   m_button_close.Y(y+m_button_close.YGap());
   m_button_unroll.X(x+m_button_unroll.XGap());
   m_button_unroll.Y(y+m_button_unroll.YGap());
   m_button_rollup.X(x+m_button_rollup.XGap());
   m_button_rollup.Y(y+m_button_rollup.YGap());
   m_button_tooltip.X(x+m_button_tooltip.XGap());
   m_button_tooltip.Y(y+m_button_tooltip.YGap());
//--- Actualizando las coordenadas de objetos gráficos
   m_bg.X_Distance(m_bg.X());
   m_bg.Y_Distance(m_bg.Y());
   m_caption_bg.X_Distance(m_caption_bg.X());
   m_caption_bg.Y_Distance(m_caption_bg.Y());
   m_icon.X_Distance(m_icon.X());
   m_icon.Y_Distance(m_icon.Y());
   m_label.X_Distance(m_label.X());
   m_label.Y_Distance(m_label.Y());
   m_button_close.X_Distance(m_button_close.X());
   m_button_close.Y_Distance(m_button_close.Y());
   m_button_unroll.X_Distance(m_button_unroll.X());
   m_button_unroll.Y_Distance(m_button_unroll.Y());
   m_button_rollup.X_Distance(m_button_rollup.X());
   m_button_rollup.Y_Distance(m_button_rollup.Y());
   m_button_tooltip.X_Distance(m_button_tooltip.X());
   m_button_tooltip.Y_Distance(m_button_tooltip.Y());
  }

 

Prueba de desplazamiento del formulario por el gráfico

Todos los eventos deben procesarse en el método CWndEvents::ChartEvent() que ha sido creado anteriormente y actualmente está vacío. Primero, se hará la comprobación del tamaño del array de los punteros de las ventanas. Si está vacío, el trabajo del método no tiene sentido y el programa saldrá de él (return). Luego, se llevará a cabo la inicialización de los campos de la clase que se refieren a los parámetros de eventos del gráfico. Por ahora, colocaremos dos función de manejo de eventos: (1) para la comprobación de eventos en los manejadores de cada control CWndEvents::CheckElementsEvents() y (2) para el seguimiento del cursor del ratón CWndEvents::ChartEventMouseMove().

Entonces, para este momento el contenido del método CWndEvents::ChartEvent() tiene que ser como se muestra en el siguiente 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, mostramos
   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();
  }

Vamos a llenar los métodos CWndEvents::CheckElementsEvents() y CWndEvents::ChartEventMouseMove(), ya que en este momento están vacíos. La comprobación de los eventos de los controles de la interfaz se hace en un ciclo. Los manejadores OnEvent() de todos los controles que se encuentran en la base se llaman de una manera consecutiva. Ya que por ahora nuestro archivo de prueba contiene sólo una ventana, vamos a usar temporalmente el índice cero de los arrays de ventanas en la clase CWndEvents. Pero eso habrá que cambiar cuando pasemos al desarrollo del modo de ventanas múltiples.

//+------------------------------------------------------------------+
//| 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);
  }

Ahora, en la clase CWndEvents, hay que crear el método donde va a realizarse el desplazamiento de los controles por el gráfico. Lo llamaremos MovingWindow(). Primero, en este método va a realizarse el desplazamiento del formulario, luego de todos los controles adjuntos a él.

class CWndEvents : public CWndContainer
  {
private:
   //--- Desplazamiento de la ventana
   void              MovingWindow(void);
  };
//+------------------------------------------------------------------+
//| Desplazamiento de la ventana                                     |
//+------------------------------------------------------------------+
void CWndEvents::MovingWindow(void)
  {
//--- Desplazamiento de la ventana
   int x=m_windows[0].X();
   int y=m_windows[0].Y();
   m_windows[0].Moving(x,y);
//--- Desplazamiento de controles
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].Moving(x,y);
  }

Ahora en el método CWndEvents::ChartEventMouseMove() se le puede añadir el código, tal como se muestra a continuación. No olvide redibujar el gráfico después de cada actualización de las coordenadas, de lo contrario no verá los cambios.

//+------------------------------------------------------------------+
//| Evento CHARTEVENT MOUSE MOVE                                     |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventMouseMove(void)
  {
//--- Salir si no es un evento de desplazamiento del cursor
   if(m_id!=CHARTEVENT_MOUSE_MOVE)
      return;
//--- Desplazamiento de la ventana
   MovingWindow();
//--- Redibujamos el gráfico
   m_chart.Redraw();
  }

El proceso del desarrollo de la librería ha pasado a la fase de implementación, cuando se puede testear el desplazamiento de la ventana sobre el gráfico. Vamos a añadir en el cuerpo del método CWindow::OnEvent() el código para mostrar los datos en la esquina superior izquierda del gráfico, donde van a visualizarse en tiempo real:

  • coordenadas del cursor;
  • coordinada relativa Y;
  • coordenadas del formulario;
  • número de la ventana del gráfico en el que se encuentra el cursor;
  • número de la ventana del gráfico en el que se encuentra el programa MQL;
  • modo del botón izquierdo del ratón.

Después de la prueba, hay que eliminar este código.

::Comment("x: ",x,"\n",
                "y: ",y,"\n",
                "rel_y: ",rel_y,"\n",
                "w.x: ",m_x,"\n",
                "w.y: ",m_y,"\n",
                "subwin: ",subwin,"\n",
                "m_subwin: ",m_subwin,"\n",
                "clamping mode: ",m_clamping_area_mouse);

Por favor, ahora compile todos los archivos del proyecto y cargue el programa en el gráfico. Para comprobar el hecho de que el programa realiza correctamente el seguimiento de la ventana en la que se encuentra el cursor, hay que cargar en el gráfico cualquier indicador que se dibuja fuera de la ventana principal del gráfico.

Casi estoy seguro de que Usted no podrá mover la ventana después de cargar el programa en el gráfico. La cosa es que en el constructor de la clase CWindow, la variable m_movable se inicializa con el valor false, lo que comprende el modo cuando el usuario no puede mover el formulario por el gráfico. Por eso el desarrollador de la aplicación MQL tiene que indicar en el código la necesidad del desplazamiento del formulario.

En la clase CProgram en el método CreateWindow(), añada la línea como se muestra en el código de abajo, compile los archivos e intente probar la aplicación de nuevo.

//+------------------------------------------------------------------+
//| 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);
//--- Coordenadas
   int x=1;
   int y=1;
//--- Propiedades
   m_window.Movable(true);
   m_window.XSize(200);
   m_window.YSize(200);
 //--- Creación del formulario
   if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

Ahora no hay ninguna razón que impida a mover el formulario:

Fig. 1. Prueba de desplazamiento del formulario por el gráfico.

Fig. 1. Prueba de desplazamiento del formulario por el gráfico

A veces sucede que hace falta cambiar el tamaño de la ventana del gráfico, y en este momento se genera el evento del cambio de las propiedades del gráfico CHARTEVENT_CHART_CHANGE. Ahora eso no se monitorea de ninguna manera, por eso puede ocurrir la situación cuando el formulario parcialmente o incluso por completo salga fuera de los límites de la ventana del gráfico. Para evitarlo, hay que comprobar este tipo de eventos también en el manejador de eventos del gráfico CWindow::OnEvent().

Puesto que este evento se genera también durante el scrolling del gráfico, para no ejecutar muchas acciones innecesarias, hay que comprobar si está pulsado el botón izquierdo del ratón en el momento de la llegada de este evento. Si el botón está suelto, obtenemos los tamaños del gráfico y corregimos las coordenadas si ha sido detectada la salida fuera de los límites de la ventana del gráfico. A continuación, se muestra el código a insertar en el método CWindow::OnEvent() tras el procesamiento de otros eventos:

//+------------------------------------------------------------------+
//| Manejador de eventos del gráfico                                 |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Evento del cambio de propiedades del gráfico
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Si el botón está suelto
      if(m_clamping_area_mouse==NOT_PRESSED)
        {
         //--- Obtenemos las dimensiones de la ventana del gráfico
         SetWindowProperties();
         //--- Corrección de coordenadas
         UpdateWindowXY(m_x,m_y);
        }
      return;
     }
  }

El resultado tangible del trabajo descrito anteriormente es la posibilidad de mover el formulario para los controles. Luego, nuestra tarea consiste en insertar la funcionalidad en los botones de minimizar y cerrar la ventana, así como hacer que reaccionen a la posición del cursor, dando a entender al usuario final que no son unas simples imágenes sino los elementos del diseño.

 

Cambio de apariencia de controles de la interfaz al apuntar con el cursor

Antes, cuando hemos considerado la implementación de la clase CElement, que es la clase base para todos los controles, como uno de sus miembros ha sido creado el método CElement::ChangeObjectColor() para cambiar el color del objeto al situar el cursor sobre él. Ha llegado el momento para crear el mecanismo que permita usar eso en nuestro trabajo. Para activar este mecanismo, vamos a necesitar un temporizador. Por defecto, está desactivado en los ajustes de la aplicación MQL, y el desarrollador decide por sí mismo si hace falta activarlo en función de las tareas planteadas.

Para la activación del temporizador, el lenguaje MQL ofrece dos funciones de frecuencia diferente: EventSetTimer() y EventSetMillisecondTimer(). La primera permite establecer el intervalo no menos de un segundo. Para nuestras tareas eso no vale porque un segundo es un intervalo demasiado pequeño para cambiar la apariencia de un control al situar el cursor sobre él. El cambio tiene que realizarse inmediatamente y sin retardos. Por eso, vamos a usar la función EventSetMillisecondTimer() que permite establecer el temporizador con los intervalos medidos en milisegundos. En el manual de referencia de MQL se pone que el intervalo mínimo que se puede establecer con esta función es de 10-16 milisegundos. Eso es suficiente para realizar nuestro plan.

Añadimos una constante con el paso del temporizador necesario para el funcionamiento de la librería en el archivo Defines.mqh:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Paso del temporizador (milisegundos)
#define TIMER_STEP_MSC (16)

Pueden surgir las preguntas bastante lógicas como: “¿No será demasiado frecuente?” y “¿Consumirá el programa demasiados recursos de CPU?”. Contestaremos a esas preguntas cuando vamos a testear nuestra idea.

Vamos a activar el temporizador en la clase CWndEvents, que es en cierto modo el núcleo de la librería que desarrollamos. Para eso, en su constructor y destructor hay que insertar las líneas del código de abajo:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWndEvents::CWndEvents(void)
  {
//--- Activar temporizador
   if(!::MQLInfoInteger(MQL_TESTER))
      ::EventSetMillisecondTimer(TIMER_STEP_MSC);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CWndEvents::~CWndEvents(void)
  {
//--- Eliminar temporizador
   ::EventKillTimer();
  }

En el constructor de la clase CWndEvents, el temporizador va a activarse sólo si el programa se encuentra fuera del Probador de Estrategias. Por ahora nuestra librería no va a trabajar en el Probador de Estrategias, porque actualmente él tiene ciertas limitaciones respecto al trabajo con objetos gráficos. Además, en el Probador el intervalo mínimo para el temporizador es de un segundo, incluso si se usa la función EventSetMillisecondTimer(). El destructor desactiva el temporizador usando la función EventKillTimer().

En la clase de cada control habrá su implementación del método OnEventTimer(), y para eso en la clase CElement ya existe el método virtual con este nombre. Al igual que ha sido implementado el método CWndEvents::CheckElementsEvents(), donde en el ciclo se realizaba el repaso de todos los métodos OnEvent() de cada control, hay que crear el método en el que el programa repase los métodos OnEventTimer() también en el ciclo. Vamos a llamarlo CheckElementsEventsTimer().

Declaración e implementación del método CWndEvents::CheckElementsEventsTimer():

class CWndEvents : public CWndContainer
  {
private:
   //--- Comprobación de los eventos de todos los controles por el temporizador
   void              CheckElementsEventsTimer(void);
  };
//+------------------------------------------------------------------------+
//| Comprobación de los eventos de todos los controles por el temporizador |
//+------------------------------------------------------------------------+
void CWndEvents::CheckElementsEventsTimer(void)
  {
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].OnEventTimer();
  }

Luego, hay que llamar este método en el método CWndEvents::OnTimerEvent() comprobando al principio el tamaño del array de ventanas, de la misma manera que ha sido hecho en el método CWndEvents::ChartEvent().

//+------------------------------------------------------------------+
//| Teemporizador                                                    |
//+------------------------------------------------------------------+
void CWndEvents::OnTimerEvent(void)
  {
//--- Si el array está vacío, mostramos  
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Comprobación de los eventos de todos los controles por el temporizador
   CheckElementsEventsTimer();
//--- Redibujamos el gráfico
   m_chart.Redraw();
  }

Ahora en la clase CProgram, donde el desarrollador de la aplicación MQL va a crear la interfaz gráfica, en el método CProgram::OnTimerEvent(), que está conectado con el archivo principal del programa, simplemente habrá que llamar el método con el mismo nombre desde la clase base, como se muestra a continuación:

//+------------------------------------------------------------------+
//| Temporizador                                                     |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();
  }

De esta manera, las acciones de todos los controles que están en sus métodos OnEventTimer() estarán disponibles en el flujo de eventos del temporizador del programa.

Pero todavía no tenemos todo listo para hacer la prueba. En este momento, en nuestra librería hay sólo un control de la interfaz: formularrio para controles, clase CWindow. Vamos a crear la funcionalidad necesaria en él, que luego insertaremos en la versión local del método OnEventTimer().

El método para el cambio del color de objetos va a llamarse CWindow::ChangeObjectsColor(), abajo se muestra su declaración e implementación en la clase CWindow:

class CWindow: public CElement
  {
private:
   //--- Cambio del color de los objetos del formulario
   void              ChangeObjectsColor(void);
  };
//+------------------------------------------------------------------+
//| Cambio del color del objeto al situar el cursor sobre él         |
//+------------------------------------------------------------------+
void CWindow::ChangeObjectsColor(void)
  {
//--- Cambio de la imagen en los botones
   m_button_rollup.State(m_button_rollup.MouseFocus());
   m_button_unroll.State(m_button_unroll.MouseFocus());
   m_button_close.State(m_button_close.MouseFocus());
//--- Cambio del color en el encabezado
   CElement::ChangeObjectColor(m_caption_bg.Name(),CElement::MouseFocus(),OBJPROP_BGCOLOR,
                               m_caption_bg_color,m_caption_bg_color_hover,m_caption_color_bg_array);
  }

Como se ve desde el código, no hay nada complicado en el método CWindow::ChangeObjectsColor() Los métodos MouseFocus() devuelven el foco del cursor sobre todos los objetos que se comprueba en el método CWindow::CheckMouseFocus() cuando el cursor se mueve por el gráfico. Antes han sido cargadas dos imágenes en los objetos que desempeñan aquí el papel de botones. Se puede conmutarlas estableciendo el estado mediante el método CChartObjectBmpLabel::State(). El método CElement::ChangeObjectColor() trabaja con el color de la parte indicada del objeto.

En este caso, es necesario que se cambie el color del fondo del encabezado cuando el cursor aparezca en el área de la ventana. Pero también se puede cambiar el color del marco del encabezado o el fondo y el marco a la vez, o incluso los colores de otros objetos, llamando consecutivamente el método CElement::ChangeObjectColor() y especificando en el primer parámetro el nombre del objeto para cada uno de ellos.

Nos queda colocar la llamada a este método en el temporizador local:

//+------------------------------------------------------------------+
//| Temporizador                                                     |
//+------------------------------------------------------------------+
void CWindow::OnEventTimer(void)
  {
//--- Cambio del color de los objetos del formulario
   ChangeObjectsColor();
  }

Por favor, compile todos los archivos del proyecto que han sido modificados y cargue el programa para las pruebas en el gráfico. Ahora, al situar el cursor sobre un objeto en el formulario donde se supone la funcionalidad, el objeto va a cambiar su color.

Fig. 2. Prueba de reacción de los tos al situar el cursor sobre ellos.

Fig. 2. Prueba de reacción de los tos al situar el cursor sobre ellos

 

Conclusión

En este artículo, hemos introducido cambios adicionales que permiten desplazar el formulario por el gráfico, y sus controles ahora pueden reaccionar al movimiento del cursor. En el siguiente artículo vamos a continuar el desarrollo de la clase CWindow. Será ampliada con los métodos que permitirán gestionar el formulario mediante los clics en sus controles.

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:

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2127

Archivos adjuntos |
MQL5 para principiantes, protección antivandálica de los objetos gráficos MQL5 para principiantes, protección antivandálica de los objetos gráficos
¿Qué haría si de repente se borraran los paneles gráficos de control, o alguien los modificara? En este artículo enseñamos a evitar las situaciones donde el gráfico se puede quedar con objectos sin dueño. Las controlaremos cuando, tras eliminar la aplicación, los objetos se renombran o se borran programáticamente.
Trading Usando Linux Trading Usando Linux
The article describes how to use indicators to watch the situation on financial markets online.
Evaluación y selección de variables en modelos de aprendizaje de máquinas Evaluación y selección de variables en modelos de aprendizaje de máquinas
Este artículo se centra en aspectos específicos relacionados con la elección, los prerrequisitos y la evaluación de las variables de entrada (predictores) de los modelos de aprendizaje de máquinas. Vamos a plantear nuevos enfoques, y también expondremos las oportunidades que ofrece el análisis predictivo profundo, así como la influencia que tiene en el sobreajuste de los modelos. El resultado general de los modelos depende en gran medida del resultado de esta etapa. Analizaremos dos paquetes que ofrecen enfoques nuevos y originales para seleccionar predictores.
Psicología individual de un trader Psicología individual de un trader
Un retrato del comportamiento de un trader en el mercado financiero. Menú propio del autor extraído del libro "Как играть и выигрывать на бирже" ("Cómo especular en el mercado bursátil y ganar") por A. Elder.