English Русский 中文 Deutsch 日本語 Português
Interfaces gráficas X: Control "Gráfico estándar" (build 4)

Interfaces gráficas X: Control "Gráfico estándar" (build 4)

MetaTrader 5Ejemplos | 21 octubre 2016, 10:56
1 457 0
Anatoli Kazharski
Anatoli Kazharski

Índice


Introducción

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 artículo de la serie se muestra la lista completa de los capítulos con los enlaces. Además, se puede 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.

Vamos a analizar otro control de la la interfaz del que no hemos podido pasar a la hora de desarrollar nuestra librería. Cuando el usuario abre el terminal de trading, aparecen los gráficos de precios, y estaría bien obtener una herramienta que permitiría manejarlos con más comodidad. Antes escribí el artículo Guía práctica de MQL5: supervisar múltiples períodos de tiempo en una sola ventana en el que fue demostrada una de posibles opciones de esta herramienta. En este caso escribiremos la clase que podríamos usar fácilmente en las interfaces gráficas de nuestras aplicaciones MQL. A diferencia de la versión anterior (ver el enlace de arriba), en esta implementación Usted podrá desplazar el contenido de los objetos-gráficos por la horizontal, tal como ya está acostumbrado de hacerlo en la ventana principal del gráfico.

Aparte de eso, continuaremos optimizando el código de la librería para reducir el consumo de los recursos de CPU. Para más información sobre todo eso, lea a continuación.

 

Desarrollo de la clase para la creación del control «Gráfico estándar»

Antes de empezar con el desarrollo de la clase CStandardChart para la creación del control «Gráfico estándar», hay que incluir la clase base CSubChart con propiedades adicionales (véase el código de abajo) en el archivo Object.mqh, como ya ha sido hecho antes para todos los tipo de objetos gráficos que se utilizan durante la creación de los controles de la librería. La clase CChartObjectSubChart de la librería estándar servirá de base para la clase CSubChart

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
//--- Lista de las clases en el archivo para una rápida navegación (Alt+G)
...
class CSubChart;
//+------------------------------------------------------------------+
//| Clase con propiedades adicionales para el objeto Sub Chart         |
//+------------------------------------------------------------------+
class CSubChart : public CChartObjectSubChart
  {
protected:
   int               m_x;
   int               m_y;
   int               m_x2;
   int               m_y2;
   int               m_x_gap;
   int               m_y_gap;
   int               m_x_size;
   int               m_y_size;
   bool              m_mouse_focus;
   //---
public:
                     CSubChart(void);
                    ~CSubChart(void);
   //--- Coordenadas
   int               X(void)                      { return(m_x);           }
   void              X(const int x)               { m_x=x;                 }
   int               Y(void)                      { return(m_y);           }
   void              Y(const int y)               { m_y=y;                 }
   int               X2(void)                     { return(m_x+m_x_size);  }
   int               Y2(void)                     { return(m_y+m_y_size);  }
   //--- Márgenes desde el punto extremo (xy)
   int               XGap(void)                   { return(m_x_gap);       }
   void              XGap(const int x_gap)        { m_x_gap=x_gap;         }
   int               YGap(void)                   { return(m_y_gap);       }
   void              YGap(const int y_gap)        { m_y_gap=y_gap;         }
   //--- Tamaños
   int               XSize(void)                  { return(m_x_size);      }
   void              XSize(const int x_size)      { m_x_size=x_size;       }
   int               YSize(void)                  { return(m_y_size);      }
   void              YSize(const int y_size)      { m_y_size=y_size;       }
   //--- Foco
   bool              MouseFocus(void)             { return(m_mouse_focus); }
   void              MouseFocus(const bool focus) { m_mouse_focus=focus;   }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSubChart::CSubChart(void) : m_x(0),
                             m_y(0),
                             m_x2(0),
                             m_y2(0),
                             m_x_gap(0),
                             m_y_gap(0),
                             m_x_size(0),
                             m_y_size(0),
                             m_mouse_focus(false)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CSubChart::~CSubChart(void)
  {
  }

La clase CChartObjectSubChart incluye el método para la creación del objeto-gráfico, así como los métodos para el cambio de las propiedades del gráfico que se utilizan con más frecuencia. Esta lista contiene los métodos para establecer y obtener las siguientes propiedades: 

  • coordenadas y tamaños
  • símbolo, período de tiempo y escala
  • visualización de la escala de precio y de tiempo
//+------------------------------------------------------------------+
//|                                          ChartObjectSubChart.mqh |
//|                   Copyright 2009-2013, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "ChartObject.mqh"
//+------------------------------------------------------------------+
//| Class CChartObjectSubChart.                                      |
//| Purpose: Class of the "SubChart" object of chart.                |
//|          Derives from class CChartObject.                        |
//+------------------------------------------------------------------+
class CChartObjectSubChart : public CChartObject
  {
public:
                     CChartObjectSubChart(void);
                    ~CChartObjectSubChart(void);
   //--- methods of access to properties of the object
   int               X_Distance(void) const;
   bool              X_Distance(const int X) const;
   int               Y_Distance(void) const;
   bool              Y_Distance(const int Y) const;
   ENUM_BASE_CORNER  Corner(void) const;
   bool              Corner(const ENUM_BASE_CORNER corner) const;
   int               X_Size(void) const;
   bool              X_Size(const int size) const;
   int               Y_Size(void) const;
   bool              Y_Size(const int size) const;
   string            Symbol(void) const;
   bool              Symbol(const string symbol) const;
   int               Period(void) const;
   bool              Period(const int period) const;
   int               Scale(void) const;
   bool              Scale(const int scale) const;
   bool              DateScale(void) const;
   bool              DateScale(const bool scale) const;
   bool              PriceScale(void) const;
   bool              PriceScale(const bool scale) const;
   //--- change of time/price coordinates is blocked
   bool              Time(const datetime time) const { return(false); }
   bool              Price(const double price) const { return(false); }
   //--- method of creating object
   bool              Create(long chart_id,const string name,const int window,
                            const int X,const int Y,const int sizeX,const int sizeY);
   //--- method of identifying the object
   virtual int       Type(void) const { return(OBJ_CHART); }
   //--- methods for working with files
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
  };

Ahora puede crear el archivo StandardChart.mqh con la clase CStandardChart en cuyo contenido base se puede determinar ya mismo los métodos de gestión estándar para todos los controles de la librería (véase el código de abajo). Puesto que aquí va a implementarse el modo de desplazamiento horizontal, vamos a necesitar un icono para el puntero del ratón que va a indicar al usuario que el modo está activado y los datos del objeto-gráfico van a desplazarse al ser movido el puntero del ratón por la horizontal. Para cambiar el icono, incluimos el archivo Pointer.mqh con la clase CPointer que ha sido considerada antes en el artículo Interfaces gráficas VIII: Control “Lista jerárquica” (Capítulo 2). Como imagen para el icono del cursor vamos a usar la copia que se activa durante el desplazamiento horizontal del gráfico principal (flecha negra doble con el contorno blanco). Puede descargar dos versiones de esta imagen (flecha negra y azul) al final del artículo. 

En consecuencia, la enumeración de los punteros para el cursor del ratón (ENUM_MOUSE_POINTER) se completa con el identificador (MP_X_SCROLL): 

//+------------------------------------------------------------------+
//| Enumeración de tipos de punteros                                    |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM     =0,
   MP_X_RESIZE   =1,
   MP_Y_RESIZE   =2,
   MP_XY1_RESIZE =3,
   MP_XY2_RESIZE =4,
   MP_X_SCROLL   =5
  };

Aparte de eso, hay que incluir los recursos con las imágenes para este tipo de puntero en el archivo Pointer.mqh, y la construcción switch en el método CPointer::SetPointerBmp() hay que ampliarla con otro bloque case

//+------------------------------------------------------------------+
//|                                                      Pointer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
//--- Recursos
...
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp"
//+------------------------------------------------------------------+
//| Establecer las imágenes para el puntero según el tipo del puntero               |
//+------------------------------------------------------------------+
void CPointer::SetPointerBmp(void)
  {
   switch(m_type)
     {
      case MP_X_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs.bmp";
         break;
      case MP_Y_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs.bmp";
         break;
      case MP_XY1_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs.bmp";
         break;
      case MP_XY2_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs.bmp";
         break;
      case MP_X_SCROLL :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp";
         break;
     }
//--- Si ha sido especificado el tipo personalizado (MP_CUSTOM)
   if(m_file_on=="" || m_file_off=="")
      ::Print(__FUNCTION__," > Para el puntero del cursor hay que establecer ambas imágenes");
  }

Aquí hay que mencionar también que el método Moving() ahora puede utilizarse en dos modos que puede ser establecido con el tercer argumento del método. Por defecto, el valor de este argumento es false, lo que significa la posibilidad de mover el control sólo en el caso si el formulario al que está adjuntado se encuentra en el modo de desplazamiento en este momento. 

//+------------------------------------------------------------------+
//|                                                StandardChart.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "Pointer.mqh"
//+------------------------------------------------------------------+
//| Clase para crear el gráfico estándar                          |
//+------------------------------------------------------------------+
class CStandardChart : public CElement
  {
private:
   //--- Puntero al formulario al que está adjuntado el control
   CWindow          *m_wnd;
   //---
public:
   //--- Guarda el puntero del formulario
   void              WindowPointer(CWindow &object)          { m_wnd=::GetPointer(object); }
   //---
public:
   //--- Manejador del evento del gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Desplazamiento del control
   virtual void      Moving(const int x,const int y,const bool moving_mode=false);
   //--- (1) Mostrar, (2) ocultar, (3) resetear, (4) eliminar
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Establecer, (2) resetear las prioridades para el clic izquierdo del ratón
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //---
private:
   //--- Cambiar el ancho por el lado derecho de la ventana
   virtual void      ChangeWidthByRightWindowSide(void);
   //--- Cambiar el alto por el borde inferior de la ventana
   virtual void      ChangeHeightByBottomWindowSide(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CStandardChart::CStandardChart(void)
  {
//--- Guardamos el nombre de la clase del control en la clase base
   CElement::ClassName(CLASS_NAME);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CStandardChart::~CStandardChart(void)
  {
  }

Si el valor del tercer argumento del método Moving() es true, entonces cuando se llama al método, el control será movido de forma forzada, independientemente de que si el formulario se encuentre en el modo de desplazamiento en este momento. En algunas ocasiones, eso reduce considerablemente el consumo de recursos del procesador. 

Para saber si se encuentra el formulario en modo de desplazamiento, en la clase CWindow ha sido incluido el método ClampingAreaMouse() que devuelve el área en la que ha sido apretado el botón izquierdo del ratón: 

//+------------------------------------------------------------------+
//| Clase de creación del formulario para los controles                    |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
public:
   //--- Devuelve el área en la que ha sido apretado el botón izquierdo del ratón
   ENUM_MOUSE_STATE  ClampingAreaMouse(void)                           const { return(m_clamping_area_mouse);          }
  };

El método CWindow::ClampingAreaMouse() está disponible a través del puntero del formulario en cada control adjunto a él. Para que todo funcione tal como ha sido descrito más arriba, en el método Moving() de cada control se inserta el bloque del código como se muestra a continuación (fragmento marcado en amarillo). Como ejemplo, este método se muestra desde la clase CSimpleButton (versión reducida). 

//+------------------------------------------------------------------+
//| Desplazamiento de controles                                            |
//+------------------------------------------------------------------+
void CSimpleButton::Moving(const int x,const int y,const bool moving_mode=false)
  {
//--- Salir si el control está ocultado
   if(!CElement::IsVisible())
      return;
//--- Si la gestión ha sido pasada a la ventana, determinamos su posición
   if(!moving_mode)
      if(m_wnd.ClampingAreaMouse()!=PRESSED_INSIDE_HEADER)
         return;
//--- Si el anclaje está a la derecha
//--- Si el anclaje está a la iizquierda
//--- Si el anclaje está abajo
//--- Si el anclaje está arriba
//--- Actualizando las coordenadas de objetos gráficos
   m_button.X_Distance(m_button.X());
   m_button.Y_Distance(m_button.Y());
  }

Usted puede ver el ejemplo del uso del método Moving() más abajo donde se muestra el código del método CSimpleButton::Show(). En este caso, la actualización de las coordenadas del control tiene que realizarse de forma forzada, por eso en el tercer argumento tenemos el valor true. Los cambios correspondientes han trancendido a todas las clases de la librería donde se utiliza el método Moving().  

//+------------------------------------------------------------------+
//| Muestra el botón                                                |
//+------------------------------------------------------------------+
void CSimpleButton::Show(void)
  {
//--- Salir si el control ya está visible
   if(CElement::IsVisible())
      return;
//--- Hacer que todos los objetos sean visibles
   m_button.Timeframes(OBJ_ALL_PERIODS);
//--- Estado de visibilidad
   CElement::IsVisible(true);
//--- Actualizar la posición de objetos
   Moving(m_wnd.X(),m_wnd.Y(),true);
  }

Más tarde volveremos a la optimización del código de la librería en este artículo, mientras tanto seguiremos analizando el código del control «Gráfico estándar».

Hagamos que haya posibilidad de crear el array de objetos-gráficos colocados en una fila. Por eso es necesario declarar los arrays dinámicos para los objetos que representan los gráfico, así como para algunas propiedades como: (1) identificador del gráfico, (2) símbolo y (3) período de tiempo. Antes de crear el control «Gráfico estándar», hay que usar el método CStandardChart::AddSubChart() en el que tenemos que pasar el símbolo y el período del gráfico. En el principio de este método, antes de añadir el control a los arrays e inicializarlos con valores enviados, se realiza la comprobación del símbolo con el método CStandardChart::CheckSymbol().  

class CStandardChart : public CElement
  {
private:
   //--- Objetos para crear el control
   CSubChart         m_sub_chart[];
   //--- Propiedades de gráficos:
   long              m_sub_chart_id[];
   string            m_sub_chart_symbol[];
   ENUM_TIMEFRAMES   m_sub_chart_tf[];
   //---
public:
   //--- Añade el gráfico con propiedades especificadas antes de la creación
   void              AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf);
   //---
private:
   //--- Comprobación del símbolo
   bool              CheckSymbol(const string symbol);
  };

Primero, en el método CStandardChart::CheckSymbol() se comprueba si figura este símbolo en la ventana «Observación del mercado». Si el símbolo no ha sido encontrado, se realiza el intento de encontrarlo en la lista general de los símbolos. Si el símbolo ha sido encontrado, es obligatorio añadirlo a la ventana «Observación del mercado», de lo contrario no se podrá crear el objeto-gráfico con este símbolo (se creará el objeto-gráfico con el símbolo de la ventana principal del gráfico). 

En caso del éxito, el método CStandardChart::CheckSymbol() devuelve true. Si el símbolo especificado no ha sido encontrado, el método devolverá false y el objeto-gráfico no será añadido (los arrays tendrán el mismo tamaño), en el registro aparecerá un mensaje sobre ello. 

//+------------------------------------------------------------------+
//| Añade el gráfico                                                 |
//+------------------------------------------------------------------+
void CStandardChart::AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf)
  {
//--- Comprobamos si hay este símbolo en el servidor
   if(!CheckSymbol(symbol))
     {
      ::Print(__FUNCTION__," > Символа "+symbol+" нет на сервере!");
      return;
     }
//--- Aumentamos el tamaño del array a un elemento
   int array_size=::ArraySize(m_sub_chart);
   int new_size=array_size+1;
   ::ArrayResize(m_sub_chart,new_size);
   ::ArrayResize(m_sub_chart_id,new_size);
   ::ArrayResize(m_sub_chart_symbol,new_size);
   ::ArrayResize(m_sub_chart_tf,new_size);
//--- Guardamos los valores de los parámetros enviados
   m_sub_chart_symbol[array_size] =symbol;
   m_sub_chart_tf[array_size]     =tf;
  }
//+------------------------------------------------------------------+
//| Comprobación de la `presencia del símbolo                                         |
//+------------------------------------------------------------------+
bool CStandardChart::CheckSymbol(const string symbol)
  {
   bool flag=false;
//--- Comprobamos el símbolo en la ventana "Observación del Mercado"
   int symbols_total=::SymbolsTotal(true);
   for(int i=0; i<symbols_total; i++)
     {
      //--- Si este símbolo está presente, detenemos el cíclo
      if(::SymbolName(i,true)==symbol)
        {
         flag=true;
         break;
        }
     }
//--- Si el símbolo no ha sido encontrado en la ventana «Observación del Mercado», ...
   if(!flag)
     {
      //--- ... intentaremos encontrarlo en la lista general
      symbols_total=::SymbolsTotal(false);
      for(int i=0; i<symbols_total; i++)
        {
        //--- Si este símbolo está presente, ...
         if(::SymbolName(i,false)==symbol)
           {
            //--- ... lo colocamos en la ventana «Observación del mercado» y detenemos el ciclo»
            ::SymbolSelect(symbol,true);
            flag=true;
            break;
           }
        }
     }
//--- Devolver el resultados de búsqueda
   return(flag);
  }

Para la creación del control «Gráfico estándar» hacen falta tres métodos: un método público principal (public) y dos privados (private) uno de los cuales se refiere al icono para el cursor del ratón en el modo de desplazamiento horizontal. Como método adicional, a la clase se añade el método público CStandardChart::SubChartsTotal() para obtener el número de objetos-gráficos. 

class CStandardChart : public CElement
  {
private:
   //--- Objetos para crear el control
   CSubChart         m_sub_chart[];
   CPointer          m_x_scroll;
   //---
public:
   //--- Métodos para la creación del gráfico estándar
   bool              CreateStandardChart(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateSubChart(void);
   bool              CreateXScrollPointer(void);
   //---
public:
   //--- Devuelve el tamaño del array de gráficos
   int               SubChartsTotal(void)              const { return(::ArraySize(m_sub_chart)); }
  };

Veremos el método CStandardChart::CreateSubCharts() para la creación de objetos-gráficos. Aquí al principio se comprueba el número de gráficos añadidos a los arrays antes de la creación del control. Si no ha sido añadido ninguno, el programa saldrá del método mostrando un mensaje con la descripción en el registro.

Si los gráficos han sido añadidos, se realiza el cálculo del ancho para cada objeto. El usuario debe indicar por sí mismo el ancho del control en la clase personalizada de la aplicación MQL. Si hace falta crear más de un gráfico, entonces para saber el ancho para cada objeto, será suficiente dividir el ancho del control por el número de los gráficos.

Luego, se crean los objetos en el ciclo. Además, se toma en cuenta el posicionamiento del control (puntos de anclaje a uno de los lados del formulario). Este tema ha sido analizado al detalle en el artículo anterior, por eso no vamos a tratarlo aquí. 

Después de la creación del gráfico, obtenemos y guardamos en el array el identificador del gráfico creado, establecemos y guardamos sus propiedades. 

//+------------------------------------------------------------------+
//| Crea los gráficos                                                  |
//+------------------------------------------------------------------+
bool CStandardChart::CreateSubCharts(void)
  {
//--- Obtenemos el número de gráficos
   int sub_charts_total=SubChartsTotal();
//--- Si el grupo no tiene ningún gráfico, avisar sobre ello
   if(sub_charts_total<1)
     {
      ::Print(__FUNCTION__," > ¡La llamada a este método debe realizarse "
              "cuando en el grupo hay por lo menos un gráfico! Utilice el método CStandardChart::AddSubChart()");
      return(false);
     }
//--- Calculamos las coordenadas y el tamaño
   int x=m_x;
   int x_size=(sub_charts_total>1)? m_x_size/sub_charts_total : m_x_size;
//--- Creamos el número especificado de gráficos
   for(int i=0; i<sub_charts_total; i++)
     {
      //--- Formación del nombre del objeto
      string name=CElement::ProgramName()+"_sub_chart_"+(string)i+"__"+(string)CElement::Id();
      //--- Cálculo de la coordenada X
      x=(i>0)?(m_anchor_right_window_side)? x-x_size+1 :  x+x_size-1 : x;
      //--- Corrección del ancho del último gráfico
      if(i+1>=sub_charts_total)
         x_size=m_x_size-(x_size*(sub_charts_total-1)-(sub_charts_total-1));
      //--- Establecemos el botón
      if(!m_sub_chart[i].Create(m_chart_id,name,m_subwin,x,m_y,x_size,m_y_size))
         return(false);
      //--- Obtenemos y guardamos el identificador del gráfico creado
      m_sub_chart_id[i]=m_sub_chart[i].GetInteger(OBJPROP_CHART_ID);
      //--- Establecemos las propiedades
      m_sub_chart[i].Symbol(m_sub_chart_symbol[i]);
      m_sub_chart[i].Period(m_sub_chart_tf[i]);
      m_sub_chart[i].Z_Order(m_zorder);
      m_sub_chart[i].Tooltip("\n");
      //--- Guardamos los tamaños
      m_sub_chart[i].XSize(x_size);
      m_sub_chart[i].YSize(m_y_size);
      //--- Márgenes desde el punto extremo
      m_sub_chart[i].XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
      m_sub_chart[i].YGap((m_anchor_bottom_window_side)? m_y : m_y-m_wnd.Y());
      //--- Guardamos el puntero del objeto
      CElement::AddToArray(m_sub_chart[i]);
     }
//---
   return(true);
  }

Ya después de la creación del control «Gráfico estándar», en cualquier momento se puede modificar cualquiera de las propiedades de sus objetos-gráficos usando el puntero que se puede obtener a través del método CStandardChart::GetSubChartPointer(). Si por casualidad se envía un índice incorrecto, será corregido para evitar la salida fuera del rango del array. 

class CStandardChart : public CElement
  {
public:
   //--- Devuelve el puntero al objeto-gráfico según el índice especificado
   CSubChart        *GetSubChartPointer(const uint index);
  };
//+------------------------------------------------------------------+
//| Devuelve el puntero al objeto-gráfico según el índice especificado             |
//+------------------------------------------------------------------+
CSubChart *CStandardChart::GetSubChartPointer(const uint index)
  {
   uint array_size=::ArraySize(m_sub_chart);
//--- Si no hay ningún gráfico, avisar sobre ello
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > ¡La llamada a este método debe realizarse "
              "cuando en el grupo hay por lo menos un gráfico!");
     }
//--- Corrección en caso de salir fuera del diapasón
   uint i=(index>=array_size)? array_size-1 : index;
//--- Devolver el puntero
   return(::GetPointer(m_sub_chart[i]));
  }

El icono para el cursor se crea sólo si los objetos gráficos tienen activado el modo de desplazamiento horizontal de datos. Hay que activarlo antes de crear el control usando el método CStandardChart::XScrollMode().  

class CStandardChart : public CElement
  {
private:
   //--- Modo de desplazamiento horizontal
   bool              m_x_scroll_mode;
   //---
public:
   //--- Modo de desplazamiento horizontal
   void              XScrollMode(const bool mode) { m_x_scroll_mode=mode; }
  };
//+------------------------------------------------------------------+
//| Crea el puntero del cursor del desplazamiento horizontal              |
//+------------------------------------------------------------------+
bool CStandardChart::CreateXScrollPointer(void)
  {
//--- Salir si el desplazamiento horizontal no es necesario
   if(!m_x_scroll_mode)
      return(true);
//--- Establecemos las propiedades
   m_x_scroll.XGap(0);
   m_x_scroll.YGap(-20);
   m_x_scroll.Id(CElement::Id());
   m_x_scroll.Type(MP_X_SCROLL);
//--- Creación del control
   if(!m_x_scroll.CreatePointer(m_chart_id,m_subwin))
      return(false);
//---
   return(true);
  }

Vamos a resumir lo arriba mencionado. Si el modo de desplazamiento horizontal está activado, para su trabajo se utiliza el método CStandardChart::HorizontalScroll() que va a llamarse en el manejador de eventos cuando llega el evento CHARTEVENT_MOUSE_MOVE. Si el botón izquierdo está pulsado, entonces dependiendo de que haya sido pulsado ahora mismo o se trate de las llamadas repetidas al método en el proceso del desplazamiento horizontal, se realiza el cálculo de la distancia pasada del cursor del ratón en píxeles desde el punto del pulsado. Aquí mismo: 

  • Se bloquea el formulario.
  • Se calculan las coordenadas para la imagen del cursor del ratón.
  • Se realiza la visualización de la imagen.

El valor calculado para el desplazamiento dentro de los objetos-gráficos sólo puede ser negativo, ya que el desplazamiento va a realizarse respecto a la última barra: el método ::ChartNavigate() con el valor CHART_END (en el segundo argumento) a partir de la enumeración ENUM_CHART_POSITION. Si el valor del desplazamiento resulta ser positivo, el programa saldrá del método. Si la comprobación ha sido superada, guardamos el valor actual del desplazamiento hasta la siguiente iteración. Luego deshabilitamos el modo «Desplazamiento automático» (CHART_AUTOSCROLL) y «Desplazamiento desde el borde derecho del gráfico» (CHART_SHIFT) para todos los objetos-gráficos y realizamos el desplazamiento de acuerdo con el valor calculado.

Si el botón izquierdo está suelto, el formulario será desbloqueado y la imagen del cursor del ratón que simboliza el proceso del desplazamiento horizontal será ocultada. Después de eso el programa sale del método. 

class CStandardChart : public CElement
  {
private:
   //--- Variables relacionadas con el desplazamiento horizontal del gráfico
   int               m_prev_x;
   int               m_new_x_point;
   int               m_prev_new_x_point;
   //---
private:
   //--- Desplazamiento horizontal
   void              HorizontalScroll(void);
  };
//+------------------------------------------------------------------+
//| Desplazamiento horizontal del gráfico                                 |
//+------------------------------------------------------------------+
void CStandardChart::HorizontalScroll(void)
  {
//--- Salir si el modo de desplazamiento horizontal de gráficos está desactivado
   if(!m_x_scroll_mode)
      return;
//--- Si el botón del ratón está pulsado
   if(m_mouse.LeftButtonState())
     {
      //--- Recordamos las coordenadas actuales X del cursor
      if(m_prev_x==0)
        {
         m_prev_x      =m_mouse.X()+m_prev_new_x_point;
         m_new_x_point =m_prev_new_x_point;
        }
      else
         m_new_x_point=m_prev_x-m_mouse.X();
      //--- Bloquear el formulario
      if(!m_wnd.IsLocked())
        {
         m_wnd.IsLocked(true);
         m_wnd.IdActivatedElement(CElement::Id());
        }
      //--- Actualizar las coordenadas del puntero y hacerlo visible
      int l_x=m_mouse.X()-m_x_scroll.XGap();
      int l_y=m_mouse.Y()-m_x_scroll.YGap();
      m_x_scroll.Moving(l_x,l_y);
      //--- Mostrar el puntero
      m_x_scroll.Show();
      //--- Establecer la bandera de visibilidad
      m_x_scroll.IsVisible(true);
     }
   else
     {
      m_prev_x=0;
      //--- Desbloquear el formulario
      if(m_wnd.IdActivatedElement()==CElement::Id())
        {
         m_wnd.IsLocked(false);
         m_wnd.IdActivatedElement(WRONG_VALUE);
        }
      //--- Ocultar el puntero
      m_x_scroll.Hide();
      //--- Establecer la bandera de visibilidad
      m_x_scroll.IsVisible(false);
      return;
     }
//--- Salir si el valor es positivo
   if(m_new_x_point>0)
      return;
//--- Recordar la posición actual
   m_prev_new_x_point=m_new_x_point;
//--- Aplicar a todos los gráficos
   int symbols_total=SubChartsTotal();
//--- Desactivamos el desplazamiento automático y desplazamiento desde el borde derecho
   for(int i=0; i<symbols_total; i++)
     {
      if(::ChartGetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL))
         ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false);
      if(::ChartGetInteger(m_sub_chart_id[i],CHART_SHIFT))
         ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false);
     }
//--- Resetear el último error
   ::ResetLastError();
//--- Desplazamos los gráficos
   for(int i=0; i<symbols_total; i++)
      if(!::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point))
         ::Print(__FUNCTION__," > error: ",::GetLastError());
  }

Para el reseteo de las variables auxiliares del modo de desplazamiento horizontal de los datos dentro de los objetos-gráficos, se utilizará el método CStandardChart::ZeroHorizontalScrollVariables(). Además, puede que nos sea necesario ir a la última barra mediante programación. Para eso vamos a usar el método público CStandardChart::ResetCharts()

class CStandardChart : public CElement
  {
public:
   //--- Resetear gráficos
   void              ResetCharts(void);
   //---
private:
   //--- Puesta a cero de las variables del desplazamiento horizontal
   void              ZeroHorizontalScrollVariables(void);
  };
//+------------------------------------------------------------------+
//| Resetear gráficos                                                   |
//+------------------------------------------------------------------+
void CStandardChart::ResetCharts(void)
  {
   int sub_charts_total=SubChartsTotal();
   for(int i=0; i<sub_charts_total; i++)
      ::ChartNavigate(m_sub_chart_id[i],CHART_END);
//--- Poner a cero las variables auxiliares para el desplazamiento horizontal de gráficos
   ZeroHorizontalScrollVariables();
  }
//+------------------------------------------------------------------+
//| Puesta a cero de las variables del desplazamiento horizontal                    |
//+------------------------------------------------------------------+
void CStandardChart::ZeroHorizontalScrollVariables(void)
  {
   m_prev_x           =0;
   m_new_x_point      =0;
   m_prev_new_x_point =0;
  }

Es muy probable que pueda surgir la necesidad de supervisar el evento de hacer clic izquierdo en un objeto-gráfico del control «Gráfico estándar». Por eso añadimos nuevo identificador en el archivo Defines.mqh

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
//--- Identificadores de eventos
...
#define ON_CLICK_SUB_CHART         (28) // Clic en el objeto-gráfico

Para determinar el clic en el objeto-gráfico, implementamos el método CStandardChart::OnClickSubChart(). Si el nombre e identificador han sido comprobados con éxito (ver el código a continuación), se genera el mensaje con (1) el identificador ON_CLICK_SUB_CHART del evento, (2) identificador del control, (3) índice del objeto-gráfico y (4) el nombre del símbolo. 

class CStandardChart : public CElement
  {
private:
  //--- Procesamiento del clic en el gráfico
   bool              OnClickSubChart(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Procesamiento del clic en el botón                                      |
//+------------------------------------------------------------------+
bool CStandardChart::OnClickSubChart(const string clicked_object)
  {
//--- Salimos si el clic ha sido hecho fuera del elemento del menú
   if(::StringFind(clicked_object,CElement::ProgramName()+"_sub_chart_",0)<0)
      return(false);
//--- Obtenemos el identificador e índice desde el nombre del objeto
   int id=CElement::IdFromObjectName(clicked_object);
//--- Salir si el identificador no coincide
   if(id!=CElement::Id())
      return(false);
//--- Obtenemos el índice
   int group_index=CElement::IndexFromObjectName(clicked_object);
//--- Enviar la señal sobre ello
   ::EventChartCustom(m_chart_id,ON_CLICK_SUB_CHART,CElement::Id(),group_index,m_sub_chart_symbol[group_index]);
   return(true);
  }

Supongamos que necesita otro modo de navegación por los objetos-gráficos, tal como ha sido implementado para el gráfico principal a través de los medios del terminal. Si pulsa la tecla «Space» o «Enter» en el terminal de trading MetaTrader, en la esquina inferior izquierda del gráfico aparecerá el campo de edición (véase la captura de pantalla a continuación). Es una especie de la línea de comandos en la que se puede introducir una fecha para ir rápidamente a ella en el gráfico. Además, se puede usar esta línea de comandos para cambiar el símbolo y el período de tiempo del gráfico. 

 Fig. 1. Línea de comandos del gráfico en la esquina inferior izquierda.

Fig. 1. Línea de comandos del gráfico en la esquina inferior izquierda.

Por cierto, en la última actualización de ahora del terminal de trading (build 1455) los desarrolladores han añadido nueva posibilidad de manejar la línea de comandos:

6. MQL5: Añadida la propiedad CHART_QUICK_NAVIGATION para activar/desactivar las barras de navegación rápida en el gráfico. Para cambiar y obtener el estado de una propiedad, use las funciones ChartSetInteger y ChartGetInteger.

La barra se llama pulsando las teclas Enter o Space. Con la ayuda de ésta, es posible desplazarse rápidamente hasta la fecha establecida en el gráfico, alternar el símbolo y el marco temporal. Si su programa MQL5 procesa la pulsación de las teclas Enter o Space, desactive la propiedad CHART_QUICK_NAVIGATION, para que el terminal no intercepte estos eventos. En este sentido, seguirá existiendo la posibilidad de llamar la barra de navegación con un doble clic del ratón.

… 

Dentro de los márgenes de la la interfaz gráfica, se puede hacer todo eso aún más fácil y cómodo. En la librería Easy And Fast ya existe la clase «Calendario» (clase CCalendar). Se puede implementar la navegación por el gráfico principal y objetos-gráficos seleccionando simplemente una fecha en el calendario. Vamos a simplificar todos eso hasta la llamada a un solo método con un argumento. El valor de este argumento será la fecha hasta la cual hay que desplazar el gráfico. Llamaremos este método CStandardChart::SubChartNavigate(). El código de su versión actual se muestra a continuación.

Al principio del método, desactivamos el modo «Desplazamiento automático del gráfico» y «Desplazamiento desde el borde derecho del gráfico» para el gráfico principal. Luego, si la fecha enviada al método es mayor que la fecha actual, simplemente vamos a la última barra y salimos del método. Si la fecha es menor, hay que calcular a qué número de barras se realiza el desplazamiento a la izquierda. Primero el cálculo se hace para el gráfico actual:

  • Obtenemos el número total de barras disponibles para el símbolo y período de tiempo actuales a partir del inicio del día actual hasta la fecha especificada.
  • Obtenemos el número de barras visibles en el gráfico.
  • Obtenemos el número de barras a partir del día actual + dos barras como margen adicional.
  • Calculamos el número de barras para el desplazamiento desde la última barra.

Después de eso se realiza el desplazamiento del gráfico y todo se repite para los objetos-gráficos. 

class CStandardChart : public CElement
  {
public:
   //--- Ir a la fecha especificada
   void              SubChartNavigate(const datetime date);
  };
//+------------------------------------------------------------------+
//| Ir a la fecha especificada                                         |
//+------------------------------------------------------------------+
void CStandardChart::SubChartNavigate(const datetime date)
  {
//--- (1) Fecha actual en el gráfico y (2) la fecha recién seleccionada en el calendario
   datetime current_date  =::StringToTime(::TimeToString(::TimeCurrent(),TIME_DATE));
   datetime selected_date =date;
//--- Desactivamos el desplazamiento automático y desplazamiento desde el borde derecho
   ::ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,false);
   ::ChartSetInteger(m_chart_id,CHART_SHIFT,false);
//--- Si la fecha seleccionada en el calendario es mayor que la fecha actual
   if(selected_date>=current_date)
     {
      //--- Ir a la fecha actual en todos los gráficos
      ::ChartNavigate(m_chart_id,CHART_END);
      ResetCharts();
      return;
     }
//--- Obtenemos el númemro de barras a partir de la fecha especificada
   int  bars_total    =::Bars(::Symbol(),::Period(),selected_date,current_date);
   int  visible_bars  =(int)::ChartGetInteger(m_chart_id,CHART_VISIBLE_BARS);
   long seconds_today =::TimeCurrent()-current_date;
   int  bars_today    =int(seconds_today/::PeriodSeconds())+2;
//--- Establecemos el margen desde el borde derecho de todos los gráficos
   m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today);
   ::ChartNavigate(m_chart_id,CHART_END,m_new_x_point);
//---
   int sub_charts_total=SubChartsTotal();
   for(int i=0; i<sub_charts_total; i++)
     {
      //--- Desactivamos el desplazamiento automático y desplazamiento desde el borde derecho
      ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false);
      ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false);
      //--- Obtenemos el número de barras a partir de la fecha especificada
      bars_total   =::Bars(m_sub_chart[i].Symbol(),(ENUM_TIMEFRAMES)m_sub_chart[i].Period(),selected_date,current_date);
      visible_bars =(int)::ChartGetInteger(m_sub_chart_id[i],CHART_VISIBLE_BARS);
      bars_today   =int(seconds_today/::PeriodSeconds((ENUM_TIMEFRAMES)m_sub_chart[i].Period()))+2;
      //--- Margen desde el borde derecho del gráfico
      m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today);
      ::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point);
     }
  }

Hemos concluido el desarrollo de la clase CStandardChart para la creación del control «Gráfico estándar». Ahora escribiremos la aplicación para ver cómo funciona eso. 


Aplicación para la prueba del control

Se puede coger el EA del artículo anterior para la prueba. Eliminemos de ahí todos los controles, salvo el menú principal, la barra de estado y las pestañas. Hagamos que cada pestaña tenga su grupo de objetos-gráficos. Cada grupo va a contener una determinada divisa, por eso cada pestaña tendrá su descripción:

  • La primera pestaña – EUR (Euro — euro).
  • La segunda pestaña – GBP (Great Britain Pound – libra esterlina).
  • La tercera pestaña – AUD (Australian Dollar – dólar australiano).
  • La cuarta pestaña – CAD (Canadian Dollar – dólar canadiense).
  • La quinta pestaña – JPY (Japanese Yen – yen japonés).

Los objetos-gráficos van a ubicarse estrictamente en el área de trabajo de las pestañas, y cambiar automáticamente su tamaño al cambiar el tamaño del formulario. El lado derecho del área de trabajo de las pestañas siempre va a tener el margen de 173 píxeles desde el borde derecho del formulario. Vamos a llenar este área con los controles para establecer las siguientes propiedades:

  • Mostrar la escala de tiempo (Date time).
  • Mostrar la escala de precio (Price scale).
  • Cambio del marco temporal del gráfico (Timeframes).
  • Navegación por los datos del gráfico mediante el calendario.

como ejemplo será suficiente mostrar el código de creación sólo de un control «Gráfico estándar» (CStandardChart). Recordamos que por defecto el modo de desplazamiento horizontal está desactivado, por eso si el desplazamiento es necesario, se puede usar el método CStandardChart::XScrollMode(). Para añadir los gráficos al grupo, se usa el método CStandardChart::AddSubChart().

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                     |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
protected:
   //--- Gráfico estándar
   CStandardChart    m_sub_chart1;
   //---
protected:
   //--- Gráfico estándar
   bool              CreateSubChart1(const int x_gap,const int y_gap);
  };
//+------------------------------------------------------------------+
//| Crea el gráfico estándar 1                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateSubChart1(const int x_gap,const int y_gap)
  {
//--- Guardamos el puntero a la ventana
   m_sub_chart1.WindowPointer(m_window);
//--- Adjuntar a la primera pestaña
   m_tabs.AddToElementsArray(0,m_sub_chart1);
//--- Coordenadas
   int x=m_window.X()+x_gap;
   int y=m_window.Y()+y_gap;
//--- Establecemos las propiedades antes de la creación
   m_sub_chart1.XSize(600);
   m_sub_chart1.YSize(200);
   m_sub_chart1.AutoXResizeMode(true);
   m_sub_chart1.AutoYResizeMode(true);
   m_sub_chart1.AutoXResizeRightOffset(175);
   m_sub_chart1.AutoYResizeBottomOffset(25);
   m_sub_chart1.XScrollMode(true);
//--- Añadimos los gráficos
   m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1);
   m_sub_chart1.AddSubChart("EURGBP",PERIOD_D1);
   m_sub_chart1.AddSubChart("EURAUD",PERIOD_D1);
//--- Creamos el control
   if(!m_sub_chart1.CreateStandardChart(m_chart_id,m_subwin,x,y))
      return(false);
//--- Añadimos el objeto al array común de los grupos de objetos
   CWndContainer::AddToElementsArray(0,m_sub_chart1);
   return(true);
  }

En la captura de pantalla de abajo se muestra el resultado obtenido. En este ejemplo, Usted puede desplazar los datos en los objetos-gráficos por la horizontal igual que eso se hace en el gráfico principal. Aparte de eso, la navegación por los objetos-gráficos funciona a través del calendario, inclusive durante el avance/retroceso rápido de fechas

 Fig. 2. Prueba del control «Gráfico estándar».

Fig. 2. Prueba del control «Gráfico estándar».


Puede descargar esta aplicación de prueba al final del artículo para estudiarla más detalladamente. 

 

Optimización del temporizador y del manejador de eventos del motor de la librería

Con este propósito, las pruebas de la librería Easy And Fast fueron realizadas por mí sólo en el sistema operativo Windows 7 x64. Después de pasar a Windows 10 x64, he notado que el consumo de recursos se aumenta considerablemente. Incluso en el estado de reposo, cuando no se efectúa la interacción con la la interfaz gráfica, los procesos de la librería consumían hasta 10% de los recursos de CPU. En las siguientes capturas de pantalla se muestra el consumo de recursos del procesador hasta y después del inicio de la aplicación MQL en el gráfico.

Fig. 3. Consumo de recursos de CPU hasta el inicio de la aplicación MQL en el gráfico.

Fig. 3. Consumo de recursos de CPU hasta el inicio de la aplicación MQL en el gráfico.


Fig. 4. Consumo de recursos de CPU después del inicio de la aplicación MQL en el gráfico.

Fig. 4. Consumo de recursos de CPU después del inicio de la aplicación MQL en el gráfico.


Resulta que el problema se encuentra en el motor de la librería donde la actualización del gráfico se realiza cada 16 ms (véase el código de abajo):

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

Si a eso le añadimos el desplazamiento del cursor del ratón en el área del gráfico y la interacción activa con la interfaz gráfica de la aplicación MQL, el consumo crecerá aun más. El objetivo consiste en limitar el trabajo del temporizador del motor de la librería y evitar el redibujo del gráfico cuando llega el evento del desplazamiento del cursor del ratón. ¿Cómo se puede hacerlo?

Eliminemos la línea que se encarga del redibujo del gráfico (marcado en rojo) en el método CWndEvents::ChartEventMouseMove(): 

//+------------------------------------------------------------------+
//| 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();
//--- Establecer el estado del gráfico
   SetChartState();
//--- Redibujamos el gráfico
   m_chart.Redraw();
  }

En cuanto al temporizador del motor de la librería, en el momento actual su función se limita a cambiar el color de controles al situar el cursor sobre ellos y realizar el avance/retroceso rápido en diferentes controles (listas, tablas, calendario, etc.). Por eso, no es necesario que funcione permanentemente. Para ahorrar los recursos, hagamos que el temporizador se active cuando el cursor del ratón empiece a mover, y en cuanto el movimiento se detenga, su trabajo va a bloquearse dentro de una pequeña pausa. 

Para implementar lo planteado, hay que introducir algunas adiciones en la clase CMouse. Vamos a añadir el contador de llamadas del temporizador de sistema y el método CMouse::GapBetweenCalls() que va a devolver la diferencia entre las llamadas al evento del desplazamiento del cursor del ratón. 

class CMouse
  {
private:
  //--- Contador de llamadas
   ulong             m_call_counter;
   //---
public:   
   //--- Devuelve  (1) el valor del contador guardado durante la última llamada y (2) la diferencia entre las llamadas al manejador del evento del desplazamiento del cursor del ratón
   ulong             CallCounter(void)     const { return(m_call_counter);                  }
   ulong             GapBetweenCalls(void) const { return(::GetTickCount()-m_call_counter); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMouse::CMouse(void) : m_call_counter(::GetTickCount())
  {
  }

Aquí la lógica es muy simple. En cuanto el cursor del ratón empiece a mover, guardamos el valor actual del temporizador de sistema en el manejador de eventos de la clase CMouse

//+------------------------------------------------------------------+
//| Procesamiento de eventos del desplazamiento del cursor del ratón                       |
//+------------------------------------------------------------------+
void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del desplazamiento del cursor
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
       //--- Coordenadas y el estado del botón izquierdo del ratón
      m_x                 =(int)lparam;
      m_y                 =(int)dparam;
      m_left_button_state =(bool)int(sparam);
      //--- Guardamos el valor del contador de llamadas
      m_call_counter=::GetTickCount();
      //--- Obtenemos la posición del cursor
      if(!::ChartXYToTimePrice(0,m_x,m_y,m_subwin,m_time,m_level))
         return;
      //--- Obtenemos la coordenada relativa Y
      if(m_subwin>0)
         m_y=m_y-m_chart.SubwindowY(m_subwin);
     }
  }

Hay que poner una condición en el temporizador del motor de la librería (clase CWndEvents): si el cursor del ratón se encuentra en estado de reposo más de 500 ms, hay que redibujar el gráfico. El botón izquierdo debe estar suelto para no enfrentarse a que el avance/retroceso rápido de los controles trabaje sólo durante 500 ms. 

//+------------------------------------------------------------------+
//| Temporizador                                                           |
//+------------------------------------------------------------------+
void CWndEvents::OnTimerEvent(void)
  {
//--- Salir si el cursor está en estado de reposo (la diferencia entre las llamadas es >500 ms) y el botón izquierdo está suelto
   if(m_mouse.GapBetweenCalls()>500 && !m_mouse.LeftButtonState())
      return;
//--- 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();
  }

El problema está resuelto. La exclusión del redibujo según el evento del desplazamiento del cursor no ha afectado la calidad del desplazamiento del formulario con los controles, ya que el intervalo de 16 ms para el redibujo en el temporizador es más que suficiente para ello. Hemos solucionado el problema de una manera simple pero no única. Ya volveremos a la optimización del código de la librería en los siguientes artículos, porque existen otros métodos y opciones que ayudarán a reducir el consumo de los recursos de CPU de una manera aun más eficaz.


Optimización del control «Lista jerárquica» y «Explorador de archivos»

Se descubrió que cuando había una gran cantidad de elementos en la lista jerárquica (CTreeView), así como en el explorador de archivos (CFileNavigator) que utiliza este tipo de listas, la inicialización era muy prolongada. Para resolver este problema, al añadir el control a los arrays, en la función ::ArrayResize() se indica el tamaño de reserva para el array como el tercer parámetro. 

La cita de la Ayuda para la función ::ArrayResize():

En caso de la distribución frecuente de la memoria se recomienda utilizar el tercer parámetro que establece una reserva para disminuir la cantidad de distribución física de la memoria. Las siguientes llamadas a la función ArrayResize no llevan a la redistribución física de la memoria, simplemente se cambia el tamaño de la primera dimensión del array dentro de los límites de la memoria reservada. Hay que recordar que el tercer parámetro va a utilizarse sólo cuando va a tener lugar la distribución física de la memoria...

A efectos de comparación, abajo se muestran los resultados de las pruebas con diferentes valores del tamaño reservado de los arrays en la lista jerárquica. El número de archivos para la prueba es más de 15 000.

 Fig. 5. Resultados de las pruebas de formación de los arrays con el valor de reserva del tamaño.

Fig. 5. Resultados de las pruebas de formación de los arrays con el valor de reserva del tamaño.


Vamos a establecer el tamaño para los arrays de la lista jerárquica igual a 10 000. Los cambios correspondientes han sido introducidos en las clases CTreeView y CFileNavigator.

 

Nuevos iconos para las carpetas y archivos en el explorador de archivos

Han sido añadidos nuevos iconos para las carpetas y archivos del explorador de archivos (clase CFileNavigator), similares a los que están establecidos en el explorador de archivos del sistema operativo Windows 10. Su diseño conciso conviene mejor para las interfaces gráficas de nuestra librería, pero si es necesario, Usted tiene la posibilidad de usar sus versiones.

 Fig. 6. Nuevos iconos para las carpetas y archivos en el explorador de archivos.

Fig. 6. Nuevos iconos para las carpetas y archivos en el explorador de archivos. 

 

Estas imágenes están disponibles para la descarga al final del artículo.

 

Conclusión

En esta fase del desarrollo de la librería para la creación de las interfaces gráficas, su esquema general tiene el siguiente aspecto.

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

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


Seguiremos desarrollando la librería Easy And Fast en los siguientes artículos de la serie sobre las interfaces gráficas. La librería será ampliada con los controles adicionales que pueden ser necesarios en las aplicaciones MQL desarrolladas. Los controles existentes van a desarrollarse y completarse con nuevas posibilidades. 

Puede descargar la cuarta (build 4) versión de la librería  Easy And Fast  al final del artículo.Si está interesado, puede contribuir al desarrollo más rápido de este proyecto si va a proponer su versiones de la solución de algunas tareas, dejando los comentarios para los artículos o enviando mensajes privados. 

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

Archivos adjuntos |
LifeHack para tráders: Optimización "silenciosa" o Trazando la distribución de trades LifeHack para tráders: Optimización "silenciosa" o Trazando la distribución de trades
Análisis de la historia comercial y la construcción de los gráficos HTML de distribuciónde de los resultados comerciales dependiendo de la hora de entrada en la posición. Los gráficos se representan en tres segmentos, por horas, días y meses.
Interfaces gráficas X: Actualizaciones para la librería Easy And Fast (build 3) Interfaces gráficas X: Actualizaciones para la librería Easy And Fast (build 3)
En este artículo se muestra la siguiente versión de la librería Easy And Fast (versión 3). Hemos corregido algunos fallos, así como hemos añadido nuevas posibilidades. Para más información, lea a continuación.
Principios de programación en MQL5: Archivos Principios de programación en MQL5: Archivos
Artículo y estudio práctico sobre el trabajo con archivos en MQL5. Lea el artículo, ejecute las sencillas tareas, y al final usted conseguirá no solo conocimientos teóricos, sino también habilidades prácticas para trabajar con archivos en MQL5.
Distribuciones Estadísticas en MQL5: tomamos lo mejor de R y lo hacemos más rápido Distribuciones Estadísticas en MQL5: tomamos lo mejor de R y lo hacemos más rápido
Vamos a analizar las funciones para trabajar con las principales distribuciones estadísticas implementadas en el lenguaje R. Se trata de las distribuciones de Cauchy, Weibull, normal, log-normal, logística, exponencial, uniforme, la distribución gamma, la distribución beta central y no central, la distribución chi-cuadrado, la distribución F de Fisher, la distribución t de Student, así como las distribuciones binomial discreta y binomial negativa, la geométrica, la hipergeométrica y la distribución de Poisson. Además, también existen funciones de cálculo de los momentos teóricos de las distribuciones, que permiten valorar el grado de correspondencia entre la distribución real y la modelada.