English Русский 中文 Deutsch 日本語 Português
Gráficos en la biblioteca DoEasy (Parte 81): Integrando gráficos en los objetos de la biblioteca

Gráficos en la biblioteca DoEasy (Parte 81): Integrando gráficos en los objetos de la biblioteca

MetaTrader 5Ejemplos | 20 septiembre 2021, 14:20
454 0
Artyom Trishkin
Artyom Trishkin

Contenido

Concepto

Esta vez no vamos a crear nuevas construcciones gráficas ni mejoras. Hoy comenzaremos a integrar las clases de elementos gráficos ya creados en los objetos de la biblioteca. Necesitaremos todo esto para poder seguir trabajando en el desarrollo y la mejora de los elementos gráficos; en el futuro, necesitaremos implementar el desplazamiento de los objetos por el gráfico para controlar que la construcción de los objetos gráficos compuestos sea correcta y fidedigna. Y para ello, ahora deberemos pensar en la integración de estos objetos en los objetos de la biblioteca, de forma que ya podamos crear una clase para gestionar los objetos gráficos y su clase de colección.

La clase de control de objetos gráficos contendrá los métodos necesarios para crear formularios y objetos gráficos posteriores, que retornarán un puntero al objeto gráfico creado para que podamos trabajar con él más tarde. Por ello, en el futuro necesitaremos una colección de clases de elementos gráficos para crear las listas de todos los objetos gráficos construidos relacionados con los diferentes objetos de la biblioteca, de modo que podamos crear los métodos para su interacción mutua, así como con el usuario del programa.

En esta ocasión, integraremos los objetos gráficos solo en uno de los objetos de la biblioteca: en el objeto de barra. Necesitaremos algo de tiempo para depurar el concepto creado. Luego, usando como base el mecanismo creado y depurado, lo añadiremos al resto de los objetos de la biblioteca. Después de ello, volveremos al posterior desarrollo de los objetos gráficos de la biblioteca.

El concepto de hoy será el siguiente:

  • Tenemos un objeto de elemento gráfico con varios métodos de dibujado;
  • tenemos un objeto de barra que aún no sabe nada sobre los objetos gráficos;
  • Necesitamos crear una clase para gestionar los objetos gráficos, que les permita ser creados y retornar el puntero al objeto creado;
  • y también necesitamos hacer de esta clase de control uno de los componentes del objeto de barra.

Estos cuatro pasos nos permitirán obtener cualquier objeto de la biblioteca previamente creado (hoy el objeto de barra servirá como este objeto "experimental") y, con la ayuda del objeto de control (incorporado en él) del objeto gráfico, crear el objeto deseado y obtener un puntero al mismo. Gracias a este, podremos seguir trabajando como con el objeto gráfico habitual de la biblioteca que hemos analizado en artículos anteriores.

Después de depurar el funcionamiento de este concepto, escribiremos para los objetos de la biblioteca todo lo que haremos con el objeto de barra para integrar en este el componente gráfico de la biblioteca. Así, todos los objetos se animarán con una nueva vida "visual", y obtendremos una nueva herramienta para la interacción activa con la biblioteca.


Mejorando las clases de la biblioteca

Cada objeto gráfico creado por alguno de los objetos de la biblioteca debe saber qué objeto lo ha creado. Obviamente, si solo tenemos un objeto que pueda crear objetos gráficos por sí mismo (hoy es un objeto de barra), el objeto gráfico creado no necesitará saber qué objeto se ha encargado de ello. Pero si cada objeto de la biblioteca es capaz de crear objetos gráficos por sí mismo, todos los objetos gráficos creados deberán saber a partir de qué objeto han sido creados, de forma que desde el objeto gráfico puedan referirse al objeto que los ha creado y recibir sus datos del mismo. Esto puede resultar útil para mostrar estos datos en un objeto gráfico, o para establecer relaciones más complejas entre diferentes objetos.

Por supuesto, hoy no podremos hacer todo esto. No obstante, comenzaremos por lo más simple: necesitamos al menos conocer la descripción del tipo de objeto a partir del cual se ha creado el objeto gráfico. Para ello, empezaremos usando el identificador de la colección de objetos (para cada objeto se escribe el identificador de su colección que corresponde a su tipo). Con la ayuda de este identificador, podremos entender a qué tipo de objetos pertenece el objeto de biblioteca a partir del cual se ha creado el objeto gráfico. Obviamente, esto no bastará para indicar con precisión un objeto específico, pero siempre comenzaremos por uno simple, avanzando gradualmente hacia los más complicados.

Además, necesitaremos crear los métodos necesarios para mostrar las descripciones de los objetos del mismo tipo para todos los objetos de la biblioteca anteriormente creados. Estamos hablando de los métodos Print() y PrintShort(), que muestran una descripción breve y completa de las propiedades del objeto. Vamos a hacer estos métodos virtuales, y a declararlos en la clase padre de todos los objetos de la biblioteca CBaseObj. Para que la virtualización funcione, deberemos hacer que los argumentos de estos métodos sean exactamente iguales en todas las clases. Pero, por el momento, tenemos diferentes parámetros para estos métodos en diferentes clases. Deberemos convertirlos al mismo formato y corregir las llamadas al método según los parámetros modificados en los argumentos del método.

En la clase CBaseObj, en el archivo \MQL5\Include\DoEasy\Objects\BaseObj.mqh , declaramos estos dos métodos virtuales con los parámetros ya requeridos:

//--- Return an object type
   virtual int       Type(void)                                const { return this.m_type;                     }
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false)  { return;                        }
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false){ return;                        }
   
//--- Constructor

Los parámetros en los argumentos de los métodos ya han sido seleccionados para que podamos utilizarlos en todos los métodos que hemos escrito previamente en las clases derivadas.

Por ejemplo, en la clase COrder, la clase básica de todo el sistema de órdenes de la biblioteca, ya hemos realizado los siguientes cambios:

//--- Return order/position direction
   string            DirectionDescription(void) const;
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false);
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);
//---
  };
//+------------------------------------------------------------------+

Aquí, hemos añadido otro argumento al método Print() y hemos declarado el método PrintShort().

En la implementación del método, fuera del cuerpo de la clase, también hemos declarado un argumento adicional del método:

//+------------------------------------------------------------------+
//| Send order properties to the journal                             |
//+------------------------------------------------------------------+
void COrder::Print(const bool full_prop=false,const bool dash=false)
  {
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG),": \"",this.StatusDescription(),"\" =============");
   int beg=0, end=ORDER_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_INTEGER prop=(ENUM_ORDER_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=ORDER_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_DOUBLE prop=(ENUM_ORDER_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=ORDER_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_STRING prop=(ENUM_ORDER_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",CMessage::Text(MSG_LIB_PARAMS_LIST_END),": \"",this.StatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

Como ejemplo, aquí tenemos cómo hemos mejorado las llamadas de los métodos con los parámetros añadidos en los argumentos:

//+------------------------------------------------------------------+
//| Display complete collection description to the journal           |
//+------------------------------------------------------------------+
void CMBookSeriesCollection::Print(const bool full_prop=false,const bool dash=false)
  {
   ::Print(CMessage::Text(MSG_MB_COLLECTION_TEXT_MBCOLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CMBookSeries *bookseries=this.m_list.At(i);
      if(bookseries==NULL)
         continue;
      bookseries.Print(false,true);
     }
  }
//+------------------------------------------------------------------+
//| Display the short collection description in the journal          |
//+------------------------------------------------------------------+
void CMBookSeriesCollection::PrintShort(const bool dash=false,const bool symbol=false)
  {
   ::Print(CMessage::Text(MSG_MB_COLLECTION_TEXT_MBCOLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CMBookSeries *bookseries=this.m_list.At(i);
      if(bookseries==NULL)
         continue;
      bookseries.PrintShort(true);
     }
  }
//+------------------------------------------------------------------+

Anteriormente, aquí había un parámetro, y el método se llamaba como bookseries.Print(true); ahora hemos añadido un parámetro más en el método Print() de la clase CMBookSeries antes del que necesitamos, por eso, primero transmitimos false para el parámetro añadido, y ya luego transmitimos true para el parámetro imprescindible: el que antes era una llamada al método.

Cambios similares han afectado a casi todas las clases de los objetos de la biblioteca que hemos escrito anteriormente; ya los hemos introducido en todas las clases que tienen estos métodos y que se heredan del objeto básico de todos los objetos de biblioteca: 

BookSeriesCollection.mqh, ChartObjCollection.mqh, MQLSignalsCollection.mqh, TickSeriesCollection.mqh, TimeSeriesCollection.mqh.

Account.mqh, MarketBookOrd.mqh, MarketBookSnapshot.mqh, MBookSeries.mqh, ChartObj.mqh, ChartWnd.mqh, MQLSignal.mqh, Order.mqh.

Buffer.mqh, BufferArrow.mqh, BufferBars.mqh, BufferCalculate.mqh, BufferCandles.mqh, BufferFilling.mqh, BufferHistogram.mqh, BufferHistogram2.mqh, BufferLine.mqh, BufferSection.mqh, BufferZigZag.mqh, DataInd.mqh, IndicatorDE.mqh.

PendReqClose.mqh, PendReqModify.mqh, PendReqOpen.mqh, PendReqPlace.mqh, PendReqRemove.mqh, PendReqSLTP.mqh, PendRequest.mqh.

Bar.mqh, SeriesDE.mqh, TimeSeriesDE.mqh, DataTick.mqh, TickSeries.mqh.

Symbol.mqh, SymbolBonds.mqh, SymbolCFD.mqh, SymbolCollateral.mqh, SymbolCommodity.mqh, SymbolCommon.mqh, SymbolCrypto.mqh, SymbolCustom.mqh, SymbolExchange.mqh, SymbolFutures.mqh, SymbolFX.mqh, SymbolFXExotic.mqh, SymbolFXMajor.mqh, SymbolFXMinor.mqh, SymbolFXRub.mqh, SymbolIndex.mqh, SymbolIndicative.mqh, SymbolMetall.mqh, SymbolOption.mqh, SymbolStocks.mqh

BaseObj.mqh.

En algunas clases de la biblioteca, la salida de los mensajes mediante la función estándar Print() ha sido reemplazada con la salida de los mensajes mediante el método ToLog() de la clase CMessage, como, por ejemplo, en este método de la clase de colección de eventos:

//+------------------------------------------------------------------+
//| Select only market pending orders from the list                  |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListMarketPendings(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_MARKET_ID)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_ERROR_NOT_MARKET_LIST);
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

Antes, para mostrar los mensajes, se usaba esta línea:

Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_NOT_MARKET_LIST));

Lista de archivos en los que hemos realizado esta correcciones:

EventsCollection.mqh, HistoryCollection.mqh, TimeSeriesCollection.mqh.

Todos los cambios en estas clases se pueden encontrar en los archivos adjuntos al artículo.

Si en el gráfico se encuentra un objeto de formulario ya creado, podremos ocultarlo o mostrarlo indicando las banderas de visualización para él en los marcos temporales especificados. Utilizaremos esta posibilidad para "desplazar" un objeto al primer plano, encima de todos los demás, usando el método BringToTop().
No obstante, tenemos métodos "especificadores" para mostrar/ocultar los objetos gráficos.
Para ello, crearemos dos métodos virtuales en la clase de elemento gráfico CGCnvElement, en el archivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh:

//--- Set the object above all
   void              BringToTop(void)                          { CGBaseObj::SetVisible(false); CGBaseObj::SetVisible(true);            }
//--- (1) Show and (2) hide the element
   virtual void      Show(void)                                { CGBaseObj::SetVisible(true);                                          }
   virtual void      Hide(void)                                { CGBaseObj::SetVisible(false);                                         }

Los métodos simplemente establecen las banderas correspondientes para mostrar el objeto en todos los marcos temporales en el objeto básico de los objetos gráficos de la biblioteca.

La clase de objeto de formulario CForm hereda del objeto de elemento gráfico, y el objeto de formulario puede ser compuesto, constando de varios objetos de elemento gráfico. Por consiguiente, deberemos registrar para él nuestra propia implementación de estos métodos.
Abrimos el archivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh y declaramos dos métodos virtuales en la sección pública:

//+------------------------------------------------------------------+
//| Visual design methods                                            |
//+------------------------------------------------------------------+
//--- (1) Show and (2) hide the form
   virtual void      Show(void);
   virtual void      Hide(void);

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+

Los implementamos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Show the form                                                    |
//+------------------------------------------------------------------+
void CForm::Show(void)
  {
//--- If the object has a shadow, display it
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Show();
//--- Display the main form
   CGCnvElement::Show();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next graphical element
      CGCnvElement *elment=this.m_list_elements.At(i);
      if(elment==NULL)
         continue;
      //--- and display it
      elment.Show();
     }
//--- Update the form
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+
//| Hide the form                                                    |
//+------------------------------------------------------------------+
void CForm::Hide(void)
  {
//--- If the object has a shadow, hide it
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Hide();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next graphical element
      CGCnvElement *elment=this.m_list_elements.At(i);
      if(elment==NULL)
         continue;
      //--- and hide it
      elment.Hide();
     }
//--- Hide the main form and update the object
   CGCnvElement::Hide();
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

Ambos métodos se comentan con detalle en la lista de códigos. Resumiendo: a la hora de ocultar objetos, no existe mucha diferencia en el orden que lo hagamos, pero, al mostrar estos, deberemos restaurar la secuencia completa de disposición de todos los objetos anclados en el formulario principal. Por consiguiente, la visualización se realiza por capas: primero, se muestra el objeto más bajo, la sombra del formulario; luego, se muestra el formulario principal sobre la sombra, y, solo después de eso, se muestran todos los elementos gráficos adjuntos al formulario principal. En esta implementación, su orden de visualización se corresponde con el orden en el que se añaden a la lista de objetos anclados.
Comprobaremos este algoritmo al crear los objetos de formulario complejos (compuestos).

Ahora, podemos comenzar a integrar objetos gráficos en los objetos de la biblioteca.

Clase de control de objetos gráficos

¿Cómo podemos dotar a cada objeto de la biblioteca de la capacidad de crear objetos gráficos para nosotros mismos?

La mayoría de los objetos de la biblioteca se heredan del objeto básico de todos los objetos de la biblioteca CBaseObj. Y si añadimos a este objeto una instancia de clase que podrá crear todos los objetos gráficos posibles (disponibles y planificados para un mayor desarrollo), y proporcionamos acceso al puntero al objeto creado, todos sus herederos serán, por consiguiente, capaces de trabajar con los objetos gráficos.

Como tendremos una gran cantidad de objetos gráficos distintos, necesitaremos una clase que "conozca" cada uno de esos objetos, y que tamién pueda crearlos y controlarlos. Llamaremos a esta clase la clase de control de objetos gráficos.

En la carpeta \MQL5\Include\DoEasy\Objects\Graph\, creamos el nuevo archivo GraphElmControl.mqh de la clase CGraphElmControl. La clase debe heredarse de la clase básica para construir la biblioteca estándar MQL5 CObject. Debemos incluir en el listado de clases tres archivos: el archivo de la clase de matriz dinámica de punteros a las instancias de la clase CObject y sus descendientes, el archivo de funciones de servicio y el archivo de la clase de objeto de formulario:

//+------------------------------------------------------------------+
//|                                              GraphElmControl.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\..\Services\DELib.mqh"
#include "Form.mqh"
//+------------------------------------------------------------------+
//| Class for managing graphical elements                            |
//+------------------------------------------------------------------+
class CGraphElmControl : public CObject
  {
private:
   int               m_type_node;                     // Type of the object the graphics is constructed for
public:
//--- Return itself
   CGraphElmControl *GetObject(void)                  { return &this;               }
//--- Set a type of the object the graphics is constructed for
   void              SetTypeNode(const int type_node) { this.m_type_node=type_node; }
   
//--- Create a form object
   CForm            *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
   CForm            *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
   CForm            *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h);

//--- Constructors
                     CGraphElmControl(){;}
                     CGraphElmControl(int type_node);
  };
//+------------------------------------------------------------------+

La variable m_type_node guardará el tipo de objeto entre cuyos componentes se incluye el objeto de esta clase. Al crear un nuevo objeto (hoy es el objeto de barra), llamaremos en su constructor al método SetTypeNode(), al que transmitiremos el tipo de objeto de barra escrito en su variable m_type (para la barra, este será el identificador de la colección de objetos de barra). Así, el objeto de control de objetos gráficos sabrá qué clase está construyendo sus objetos. Por ahora, solo usaremos el identificador de la colección. En el futuro, pensaremos en cómo transmitir el puntero al objeto a partir del cual se construyen los gráficos.

Vamos a analizar los métodos de la clase.

En el constructor paramétrico de la clase, escribiremos en la variable m_type_node el tipo de objeto transmitido en los argumentos del método:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGraphElmControl::CGraphElmControl(int type_node)
  {
   this.m_type_node=m_type_node;
  }
//+------------------------------------------------------------------+


Método que crea un objeto de formulario en el gráfico indicado, en la subventana indicada:

//+----------------------------------------------------------------------+
//| Create the form object on a specified chart in a specified subwindow |
//+----------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
   CForm *form=new CForm(chart_id,wnd,name,x,y,w,h);
   if(form==NULL)
      return NULL;
   form.SetID(form_id);
   form.SetNumber(0);
   return form;
  }
//+------------------------------------------------------------------+

Transmitimos al método el identificador único del objeto de formulario creado, el identificador del gráfico, el número de la subventana del gráfico, el nombre del objeto de formulario y las coordenadas X e Y, así como la anchura y la altura del formulario, es decir, todo lo necesario para crear el formulario.
A continuación, creamos un nuevo objeto de formulario con los parámetros transmitidos ​​al método, y después de crearlo con éxito, asignamos al objeto el identificador de formulario y el número en el listado de objetos (aquí es cero, ya que este objeto no contiene ningún otro objeto de formulario vinculado, es el objeto de formulario principal). Retornamos el puntero al objeto recién creado.

Método que crea un objeto de formulario en una subventana determinada del gráfico actual:

//+----------------------------------------------------------------------+
//| Create the form object on the current chart in a specified subwindow |
//+----------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
   return this.CreateForm(form_id,::ChartID(),wnd,name,x,y,w,h);
  }
//+------------------------------------------------------------------+

Transmitimos al método el identificador único del objeto de formulario creado, el número de la subventana del gráfico, el nombre del objeto de formulario y las coordenadas X e Y, así como la anchura y la altura necesarios para crear el formulario. El método retorna el resultado de la operación de la forma de llamar a este método anteriormente analizada, con una indicación explícita del identificador del gráfico actual.

Método que crea un objeto de formulario en el gráfico actual en la ventana principal del gráfico:

//+----------------------------------------------------------------------+
//| Create the form object on the current chart in the chart main window |
//+----------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h)
  {
   return this.CreateForm(form_id,::ChartID(),0,name,x,y,w,h);
  }
//+------------------------------------------------------------------+

Transmitimos al método el identificador único del objeto de formulario creado, el nombre del objeto de formulario y las coordenadas X e Y, así como la anchura y la altura necesarios para crear el formulario. El método retorna el resultado de la operación de la forma de llamar a este método anteriormente analizada, con una indicación explícita del identificador del gráfico actual, así como el número de la ventana principal del gráfico.

No necesitamos nada más aquí para crear los objetos de formulario: todo el trabajo necesario para crear varias animaciones en el objeto de formulario creado se realizará según el puntero a este objeto retornado por los métodos anteriores.

Ya tenemos todo preparado para comenzar a integrar el trabajo con gráficos en todos los objetos de la biblioteca herederos del objeto básico de todos los objetos de la biblioteca CBaseObj.


Integrando los gráficos en la biblioteca

Por consiguiente, necesitaremos que cada objeto de la biblioteca "vea" las clases de objetos gráficos y pueda crear estos objetos por sí mismo. Para hacer esto, solo necesitaremos declarar una instancia de la clase de control de objetos gráficos como parte de la clase de objeto básico de todos los objetos de la biblioteca. Todos sus herederos estarán inmediatamente dotados de la capacidad de crear gráficos trabajando a través de la instancia de la clase CGraphElmControl que acabamos de analizar.

Abramos el archivo \MQL5\Include\DoEasy\Objects\BaseObj.mqh y le añadimos el archivo de la clase de control de objetos gráficos:

//+------------------------------------------------------------------+
//|                                                      BaseObj.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Services\DELib.mqh"
#include "..\Objects\Graph\GraphElmControl.mqh"
//+------------------------------------------------------------------+

En la sección protegida de la clase CBaseObj, declaramos una instancia del objeto de clase de control de objetos gráficos:

//+------------------------------------------------------------------+
//| Base object class for all library objects                        |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   CGraphElmControl  m_graph_elm;                              // Instance of the class for managing graphical elements
   ENUM_LOG_LEVEL    m_log_level;                              // Logging level
   ENUM_PROGRAM_TYPE m_program;                                // Program type
   bool              m_first_start;                            // First launch flag
   bool              m_use_sound;                              // Flag of playing the sound set for an object
   bool              m_available;                              // Flag of using a descendant object in the program
   int               m_global_error;                           // Global error code
   long              m_chart_id_main;                          // Control program chart ID
   long              m_chart_id;                               // Chart ID
   string            m_name;                                   // Object name
   string            m_folder_name;                            // Name of the folder storing CBaseObj descendant objects 
   string            m_sound_name;                             // Object sound file name
   int               m_type;                                   // Object type (corresponds to the collection IDs)

public:

En la sección pública de la clase, escribimos los métodos para crear el objeto de formulario:

//--- Return an object type
   virtual int       Type(void)                                const { return this.m_type;                     }
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false)  { return;                        }
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false){ return;                        }
//--- Create a form object on a specified chart in a specified subwindow
   CForm            *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,chart_id,wnd,name,x,y,w,h);                }
//--- Create a form object on the current chart in a specified subwindow
   CForm            *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,wnd,name,x,y,w,h);                         }
//--- Create the form object on the current chart in the main window
   CForm            *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,name,x,y,w,h);                             }
   
//--- Constructor

Los métodos retornan el resultado del funcionamiento de los tres métodos homónimos mencionados anteriormente de la clase de control de objetos gráficos.

Ahora, cada uno de los objetos heredados de la clase CBaseObj tiene la capacidad de crear un objeto de formulario llamando a estos métodos.

Hoy comprobaremos el trabajo con los objetos gráficos utilizando la clase de objeto "Barra".
Abrimos el archivo de esta clase \MQL5\Include\DoEasy\Objects\Series\Bar.mqh y añadimos al método de especificiación de parámetros SetProperties() la transmisión del tipo de objeto de barra a la clase de control de objetos gráficos:

//+------------------------------------------------------------------+
//| Set bar object parameters                                        |
//+------------------------------------------------------------------+
void CBar::SetProperties(const MqlRates &rates)
  {
   this.SetProperty(BAR_PROP_SPREAD,rates.spread);
   this.SetProperty(BAR_PROP_VOLUME_TICK,rates.tick_volume);
   this.SetProperty(BAR_PROP_VOLUME_REAL,rates.real_volume);
   this.SetProperty(BAR_PROP_TIME,rates.time);
   this.SetProperty(BAR_PROP_TIME_YEAR,this.TimeYear());
   this.SetProperty(BAR_PROP_TIME_MONTH,this.TimeMonth());
   this.SetProperty(BAR_PROP_TIME_DAY_OF_YEAR,this.TimeDayOfYear());
   this.SetProperty(BAR_PROP_TIME_DAY_OF_WEEK,this.TimeDayOfWeek());
   this.SetProperty(BAR_PROP_TIME_DAY,this.TimeDay());
   this.SetProperty(BAR_PROP_TIME_HOUR,this.TimeHour());
   this.SetProperty(BAR_PROP_TIME_MINUTE,this.TimeMinute());
//---
   this.SetProperty(BAR_PROP_OPEN,rates.open);
   this.SetProperty(BAR_PROP_HIGH,rates.high);
   this.SetProperty(BAR_PROP_LOW,rates.low);
   this.SetProperty(BAR_PROP_CLOSE,rates.close);
   this.SetProperty(BAR_PROP_CANDLE_SIZE,this.CandleSize());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_BODY,this.BodySize());
   this.SetProperty(BAR_PROP_CANDLE_BODY_TOP,this.BodyHigh());
   this.SetProperty(BAR_PROP_CANDLE_BODY_BOTTOM,this.BodyLow());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_UP,this.ShadowUpSize());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_DOWN,this.ShadowDownSize());
//---
   this.SetProperty(BAR_PROP_TYPE,this.BodyType());
//--- Set the object type to the object of the graphical object management class
   this.m_graph_elm.SetTypeNode(this.m_type);
  }
//+------------------------------------------------------------------+

Ya tenemos prácticamente todo listo para la prueba. Pero... Hay un "pero". Cuando comenzamos a trabajar con objetos gráficos, no los conectamos a la biblioteca principal, solo utilizamos las clases de elementos gráficos "tal cual". Ahora tenemos que hacerlo todo bien: todos los objetos de la biblioteca serán accesibles a través de su objeto principal, la clase CEngine, a la que estarán conectados los archivos de colección de todos los objetos. Pero para los objetos gráficos todavía no tenemos una clase para su colección; aún es demasiado pronto para hacerlo por una simple razón: no todos los objetos han sido creados. No obstante, podemos crear una colección de clases preliminar de los objetos gráficos, para que todo se ciña ya a la idea general, y solo entonces, después de crear todos los objetos, volver a ella y terminarla como debería ser.

Usando como base estas consideraciones, vamos a crear ahora una versión preliminar de la clase de colección de objetos gráficos. Para ello, necesitamos especificar el identificador de la lista de colección de objetos gráficos en el archivo \MQL5\Include\DoEasy\Defines.mqh:

//--- Parameters of the chart collection timer
#define COLLECTION_CHARTS_PAUSE        (500)                      // Chart collection timer pause in milliseconds
#define COLLECTION_CHARTS_COUNTER_STEP (16)                       // Chart timer counter increment
#define COLLECTION_CHARTS_COUNTER_ID   (9)                        // Chart timer counter ID
//--- Collection list IDs
#define COLLECTION_HISTORY_ID          (0x777A)                   // Historical collection list ID
#define COLLECTION_MARKET_ID           (0x777B)                   // Market collection list ID
#define COLLECTION_EVENTS_ID           (0x777C)                   // Event collection list ID
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Account collection list ID
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Symbol collection list ID
#define COLLECTION_SERIES_ID           (0x777F)                   // Timeseries collection list ID
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Indicator buffer collection list ID
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Indicator collection list ID
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Indicator data collection list ID
#define COLLECTION_TICKSERIES_ID       (0x7783)                   // Tick series collection list ID
#define COLLECTION_MBOOKSERIES_ID      (0x7784)                   // DOM series collection list ID
#define COLLECTION_MQL5_SIGNALS_ID     (0x7785)                   // MQL5 signals collection list ID
#define COLLECTION_CHARTS_ID           (0x7786)                   // Chart collection list ID
#define COLLECTION_CHART_WND_ID        (0x7787)                   // Chart window list ID
#define COLLECTION_GRAPH_OBJ_ID        (0x7788)                   // Graphical object collection list ID
//--- Pending request type IDs

En la carpeta de la biblioteca \MQL5\Include\DoEasy\Collections\, creamos el nuevo archivo GraphElementsCollection.mqh de la clase CGraphElementsCollection:

//+------------------------------------------------------------------+
//|                                      GraphElementsCollection.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Graph\Form.mqh"
//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
class CGraphElementsCollection : public CBaseObj
  {
private:
   CListObj          m_list_all_graph_obj;      // List of all graphical objects
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
//--- Return the flag indicating the graphical element object in the list of graphical objects
   bool              IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj);
public:
//--- Return itself
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
   //--- Return the full collection list 'as is'
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_all_graph_obj;   }
   //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByGraphCanvElementProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetList(),property,value,mode);  }
   //--- Return the number of new graphical objects
   int               NewObjects(void)   const                                                            { return this.m_delta_graph_obj;       }
   //--- Constructor
                     CGraphElementsCollection();
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false);
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGraphElementsCollection::CGraphElementsCollection()
  {
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
   this.m_list_all_graph_obj.Clear();
   this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID);
  }
//+------------------------------------------------------------------+

La estructura de la clase no se distingue en absoluto de la estructura de las clases de colección de otros objetos de la biblioteca. Además, aquí solo nos interesa el constructor de la clase; todos los demás métodos simplemente se declaran igual que en las otras colecciones de objetos de la biblioteca. Los implementaremos más adelante. Ahora nos importa que esta clase incluya un archivo de la clase de objeto de formulario, a través del cual, los programas creados usando como base esta biblioteca verán los objetos gráficos. Mientras tanto, en el constructor de la clase, se habilitará para el gráfico actual el seguimiento de los eventos de movimiento del ratón y el desplazamiento de la ruleta.

Eso es todo. El resto de esta plantilla de la clase de colección de objetos gráficos no nos importa por ahora: desarrollaremos todo esto más adelante, después de crear todos los objetos gráficos de la biblioteca.

Nos queda por conectar el archivo de la clase de colección de objetos gráficos al archivo del objeto principal de la biblioteca CEngine, ubicado en \MQL5\Include\DoEasy\Engine.mqh:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
#include "Collections\TimeSeriesCollection.mqh"
#include "Collections\BuffersCollection.mqh"
#include "Collections\IndicatorsCollection.mqh"
#include "Collections\TickSeriesCollection.mqh"
#include "Collections\BookSeriesCollection.mqh"
#include "Collections\MQLSignalsCollection.mqh"
#include "Collections\ChartObjCollection.mqh"
#include "Collections\GraphElementsCollection.mqh"
#include "TradingControl.mqh"
//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {

Ahora ya tenemos todo listo para poner a prueba los objetos gráficos integrados en la clase del objeto de barra.


Simulación

Cómo realizaremos la prueba: crearemos una lista de series temporales para el símbolo y el periodo del gráfico actuales. En esta lista, almacenaremos los objetos de barra. Hoy hemos añadido una clase de control de objetos gráficos a estos objetos, lo cual nos permitirá crear nuestro propio objeto de formulario para cada barra.

En consecuencia, haremos lo siguiente: cuando mantengamos presionada la tecla Ctrl en el teclado y movamos el ratón por el gráfico, se creará un objeto de formulario con una sombra para la barra en la que se encuentra el cursor del ratón, en la que además se mostrará un texto describiendo el tipo de barra (alcista/bajista/doji). En cuanto mantengamos presionada la tecla Ctrl, se anulará para el gráfico la posibilidad de desplazarse con el ratón y la ruleta, y el formulario con la descripción de la barra se mostrará de inmediato. Al soltar la tecla Ctrl, la lista de objetos de formulario creados se borrará con la llegada de un nuevo tick, o bien al desplazar el gráfico, simplemente porque para la prueba, resulta opcional el seguimiento de los momentos en que se mantiene/suelta la tecla Ctrl. Sí, el borrado de la lista de objetos creados aquí también se necesita solo (en principio) para "ocultar" algunos de los problemas que surgen al cambiar la escala del gráfico: los objetos de formulario creados previamente comienzan a mostrarse en sus lugares antiguos, es decir, ya no se corresponden con la posición actual de la vela en la escala modificada del gráfico. Y para realizar una prueba rápida, resulta más fácil borrar la lista que recalcular las coordenadas del objeto en el momento que cambia la escala del gráfico.

Bien. Para la prueba, tomaremos al asesor del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part81\
con el nuevo nombre TestDoEasyPart81.mq5.

En la zona global, en lugar incluir los archivos

#include <Arrays\ArrayObj.mqh>
#include <DoEasy\Services\Select.mqh>
#include <DoEasy\Objects\Graph\Form.mqh>

incluiremos el archivo del objeto principal de la biblioteca y declararemos su instancia:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart81.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define        FORMS_TOTAL (4)   // Number of created forms
#define        START_X     (4)   // Initial X coordinate of the shape
#define        START_Y     (4)   // Initial Y coordinate of the shape
//--- input parameters
sinput   bool              InpMovable     =  true;          // Movable forms flag
sinput   ENUM_INPUT_YES_NO InpUseColorBG  =  INPUT_YES;     // Use chart background color to calculate shadow color
sinput   color             InpColorForm3  =  clrCadetBlue;  // Third form shadow color (if not background color) 
//--- global variables
CEngine        engine;
CArrayObj      list_forms;  
color          array_clr[];

//+------------------------------------------------------------------+

A continuación, eliminamos el código que crea los objetos de formulario del manejador OnInit() del asesor; solo necesitamos indicar el símbolo que se usará en la biblioteca y crear una serie temporal para el símbolo y el periodo actuales. Como resultado, el manejador tendrá el aspecto siguiente:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'246,244,244';     // Original ≈pale gray
   array_clr[1]=C'249,251,250';     // Final ≈pale gray-green
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Vamos a elimanar las funciones FigureType() y FigureProcessing() del asesor; no las necesitamos para esta prueba, y además ocupan casi todo el volumen del código del asesor.

En su lugar, escribiremos tres funciones.

Una función que retornará una bandera para indicar la existencia de un formulario con el nombre especificado:

//+-------------------------------------------------------------------------------+
//| Return the flag that indicates the existence of a form with a specified name  |
//+-------------------------------------------------------------------------------+
bool IsPresentForm(const string name)
  {
   //--- In the loop by the list of form objects,
   for(int i=0;i<list_forms.Total();i++)
     {
      //--- get the next form object
      CForm *form=list_forms.At(i);
      if(form==NULL)
         continue;
      //--- form the desired object name as "Program name_" + the form name passed to the function
      string nm=MQLInfoString(MQL_PROGRAM_NAME)+"_"+name;
      //--- If the current form object has such a name, return 'true'
      if(form.NameObj()==nm)
         return true;
     }
   //--- Upon the loop completion, return 'false'
   return false;
  }
//+------------------------------------------------------------------+

Una función que ocultará todos los formularios salvo el formulario con el nombre indicado:

//+------------------------------------------------------------------+
//| Hide all forms except the one with the specified name            |
//+------------------------------------------------------------------+
void HideFormAllExceptOne(const string name)
  {
   //--- In the loop by the list of form objects,
   for(int i=0;i<list_forms.Total();i++)
     {
      //--- get the next form object
      CForm *form=list_forms.At(i);
      if(form==NULL)
         continue;
      //--- form the desired object name as "Program name_" + the form name passed to the function
      string nm=MQLInfoString(MQL_PROGRAM_NAME)+"_"+name;
      //--- If the current form object has such a name, display it,
      if(form.NameObj()==nm)
         form.Show();
      //--- otherwise - hide
      else
         form.Hide();
     }
  }
//+------------------------------------------------------------------+

Y una función que retornará la bandera de pulsación continua de la tecla "Ctrl" en el teclado:

//+------------------------------------------------------------------+
//| Return the flag of holding Ctrl                                  |
//+------------------------------------------------------------------+
bool IsCtrlKeyPressed(void)
  {
   return((TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)&0x80)!=0);
  }
//+------------------------------------------------------------------+

Todas las funciones son bastante simples y, en nuestra opinión, no necesitarán de explicaciones.

Eliminamos del manejador OnChartEvent() el procesamiento de la pulsación de las teclas del teclado y los clics sobre los objetos; no lo necesitaremos hoy. Simplemente añadiremos el procesamiento del movimiento del ratón:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If the mouse is moved
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      CForm *form=NULL;
      datetime time=0;
      double price=0;
      int wnd=0;
      
      //--- If Ctrl is not pressed,
      if(!IsCtrlKeyPressed())
        {
         //--- clear the list of created form objects and allow scrolling a chart with the mouse
         list_forms.Clear();
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,true);
         return;
        }
      
      //--- If X and Y chart coordinates are successfully converted into time and price,
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- get the bar index the cursor is hovered over
         int index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         if(index==WRONG_VALUE)
            return;
         
         //--- Get the bar index by index
         CBar *bar=engine.SeriesGetBar(Symbol(),Period(),index);
         if(bar==NULL)
            return;
         
         //--- Convert the coordinates of a chart from the time/price representation of the bar object to the X and Y coordinates
         int x=(int)lparam,y=(int)dparam;
         if(!ChartTimePriceToXY(ChartID(),0,bar.Time(),(bar.Open()+bar.Close())/2.0,x,y))
            return;
         
         //--- Disable moving a chart with the mouse
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,false);
         
         //--- Create the form object name and hide all objects except one having such a name
         string name="FormBar_"+(string)index;
         HideFormAllExceptOne(name);
         
         //--- If the form object with such a name does not exist yet,
         if(!IsPresentForm(name))
           {
            //--- create a new form object
            form=bar.CreateForm(index,name,x,y,76,16);
            if(form==NULL)
               return;
            
            //--- Set activity and unmoveability flags for the form
            form.SetActive(true);
            form.SetMovable(false);
            //--- Set the opacity of 200
            form.SetOpacity(200);
            //--- The form background color is set as the first color from the color array
            form.SetColorBackground(array_clr[0]);
            //--- Form outlining frame color
            form.SetColorFrame(C'47,70,59');
            //--- Draw the shadow drawing flag
            form.SetShadow(true);
            //--- Calculate the shadow color as the chart background color converted to the monochrome one
            color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
            //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units
            //--- Otherwise, use the color specified in the settings for drawing the shadow
            color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3);
            //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
            //--- Set the shadow opacity to 200, while the blur radius is equal to 4
            form.DrawShadow(2,2,clr,200,3);
            //--- Fill the form background with a vertical gradient
            form.Erase(array_clr,form.Opacity());
            //--- Draw an outlining rectangle at the edges of the form
            form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
            //--- If failed to add the form object to the list, remove the form and exit the handler
            if(!list_forms.Add(form))
              {
               delete form;
               return;
              }
            //--- Capture the form appearance
            form.Done();
           }
         //--- If the form object exists,
         if(form!=NULL)
           {
            //--- draw a text with the bar type description on it and show the form. The description corresponds to the mouse cursor position
            form.TextOnBG(0,bar.BodyTypeDescription(),form.Width()/2,form.Height()/2-1,FRAME_ANCHOR_CENTER,C'7,28,21');
            form.Show();
           }
         //--- Redraw the chart
         ChartRedraw();
        }
     }
  }
//+------------------------------------------------------------------+

El código del manejador ha sido comentado al completo directamente en el listado. Esperamos que el lector comprenda todo. En cualquier caso, siempre podrá plantear cualquier duda en los comentarios al artículo.

Compilamos el asesor y lo ejecutamos en el gráfico. Pulsamos y mantenemos la tecla Ctrl y realizamos movimientos con el ratón por el gráfico. Para cada barra se creará un objeto de formulario en el que se mostrará la descripción del tipo de barra (bajista/alcista/doji). Al soltar la tecla Ctrl, se eliminarán todos los objetos creados.



¿Qué es lo próximo?

En el próximo artículo, continuaremos integrando los objetos gráficos en los objetos de la biblioteca.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo del asesor de prueba para MQL5. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

*Artículos de esta serie:

Gráficos en la biblioteca DoEasy (Parte 73): Objeto de formulario del elemento gráfico
Gráficos en la biblioteca DoEasy (Parte 74): Elemento gráfico básico sobre la clase CCanvas
Gráficos en la biblioteca DoEasy (Parte 75): Métodos de trabajo con primitivas y texto en el elemento gráfico básico
Gráficos en la biblioteca DoEasy (Parte 76): Objeto de formulario y temas de color predeterminados
Gráficos en la biblioteca DoEasy (Parte 77): Clase de objeto Sombra
Gráficos en la biblioteca DoEasy (Parte 78): Fundamentos de animación en la biblioteca. Cortando las imágenes
Gráficos en la biblioteca DoEasy (Parte 79): Clase de objeto "Fotograma de animación" y sus objetos herederos
Gráficos en la biblioteca DoEasy (Parte 80): Clase de objeto "Fotograma de animación geométrica"

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

Archivos adjuntos |
MQL5.zip (4087.48 KB)
Cómo ser un mejor programador (parte 04): Aprendiendo a desarrollar más rápido Cómo ser un mejor programador (parte 04): Aprendiendo a desarrollar más rápido
Todo desarrollador quiere poder escribir código más rápido, y eso no es algo tan difícil de conseguir: debemos saber que codificar de forma más rápida y eficaz no es un tipo de habilidad especial con la que nazcan solo unas pocas personas. Es una habilidad que todos los codificadores pueden aprender, independientemente de la experiencia acumulada con el teclado.
Cómo ser un mejor programador (parte 03): 5 cosas que evitar para convertirse en un programador exitoso de MQL5 Cómo ser un mejor programador (parte 03): 5 cosas que evitar para convertirse en un programador exitoso de MQL5
Este es un artículo de lectura obligada para todo aquel que quiera mejorar su carrera como programador. Esta serie de artículos tiene como objetivo hacer de usted el mejor programador posible, sin importar la experiencia que tenga. Las ideas debatidas funcionan tanto para principiantes como para profesionales de la programación en MQL5.
Combinatoria y teoría de la probabilidad en el trading (Parte II): Fractal universal Combinatoria y teoría de la probabilidad en el trading (Parte II): Fractal universal
En el presente artículo, continuaremos estudiando los fractales, prestando especial atención a la generalización de todo el material. En concreto, intentaremos hacer el material más compacto y comprensible, para poder usarlo de forma práctica en el trading.
Gráficos en la biblioteca DoEasy (Parte 80): Clase de objeto "Fotograma de animación geométrica" Gráficos en la biblioteca DoEasy (Parte 80): Clase de objeto "Fotograma de animación geométrica"
En este artículo, optimizaremos el código de las clases de los artículos anteriores y crearemos una clase de objeto de fotograma de animación geométrica que nos permitará dibujar polígonos regulares con un número determinado de vértices.