English Русский 中文 Deutsch 日本語 Português
Gráficos en la biblioteca DoEasy (Parte 86): Colección de objetos gráficos - controlando la modificación de propiedades

Gráficos en la biblioteca DoEasy (Parte 86): Colección de objetos gráficos - controlando la modificación de propiedades

MetaTrader 5Ejemplos | 1 diciembre 2021, 08:33
510 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

En el último artículo, logramos implementar el almacenamiento de los objetos gráficos estándar recién creados en la clase de colección de objetos gráficos: monitoreamos la aparición de nuevos objetos en el gráfico, creamos para ellos los objetos de clase correspondientes al tipo de objeto en el gráfico y los añadimos a la lista de colección. Sin embargo, para gestionar de forma completa los objetos gráficos, esto no resultará suficiente. Necesitamos controlar todos los cambios en las propiedades de los objetos gráficos en el gráfico, así como su eliminación o cambio de nombre.

Como las funciones de la serie ObjectGetXXX se usan para leer las propiedades de los objetos gráficos, no podemos comprobar constantemente los valores de cada propiedad de cada objeto gráfico en el temporizador, porque dichas funciones son síncronas, lo cual significa que están esperando que se ejecute el comando, hecho que puede consumir muchos recursos si tenemos una gran cantidad de objetos gráficos.

Aquí nos enfrentamos a un dilema: o bien usamos un temporizador para sondear cada propiedad de cada objeto gráfico con todas las consecuencias derivadas de la sincronización de las funciones de solicitud de propiedades, o bien elegimos usar el modelo de eventos, reaccionando al evento en el manejador OnChartEvent(), que, desafortunadamente, no funciona en el simulador de estrategias (recordemos que el procesamiento de las operaciones del temporizador en el simulador lo organizamos en la biblioteca a través de los manejadores OnTick() y OnCalculate()).

Después de sopesar todos los pros y los contras, decidimos organizar el seguimiento de los cambios en las propiedades de los objetos gráficos en el manejador de eventos del gráfico. Para ello, usaremos el modelo por eventos, que simplifica la escritura del código, pero ofrece restricciones para trabajar en el simulador. No obstante, al menos por ahora, no podemos trabajar con objetos gráficos en el simulador (añadir estos a la ventana del simulador y luego cambiar sus propiedades de alguna forma). En consecuencia, deberíamos trabajar con objetos gráficos solo en los gráficos "en vivo" donde se ejecuta el manejador de eventos.

En esta ocasión, crearemos una versión de prueba que se encargará de procesar los eventos de los objetos gráficos solo para el gráfico actual en el que se está ejecutando el programa. En cuanto entendamos que todo funciona correctamente, crearemos manejadores de eventos completos para cada gráfico abierto, que enviarán eventos al gráfico principal del programa, donde la biblioteca los recopilará y procesará en su colección de objetos gráficos.


Monitoreando la modificación de las propiedades de los objetos gráficos

En el manejador OnChartEvent(), nos interesan los eventos:

  • CHARTEVENT_OBJECT_CREATE — Crear un objeto gráfico (si para el gráfico se ha establecido la propiedad CHART_EVENT_OBJECT_CREATE=true);
  • CHARTEVENT_OBJECT_CHANGE — Cambiar las propiedades del objeto a través de la venta de diálogo de propiedades;
  • CHARTEVENT_OBJECT_DELETE — Eliminar objeto gráfico (si para el gráfico se ha establecido la propiedad CHART_EVENT_OBJECT_DELETE=true)
  • CHARTEVENT_OBJECT_DRAG — Desplazar el objeto gráfico con la ayuda del ratón;

Básicamente, hemos implementado el evento de creación de un objeto gráfico del último artículo sin llamar al manejador OnChartEvent().
Necesitamos el evento de cambio de propiedades de un objeto gráfico a través de su ventana de diálogo de propiedades en el terminal para que el usuario del terminal cliente pueda controlar manualmente el cambio en las propiedades del objeto.
También hemos implementado el evento de eliminación de un objeto gráfico: la biblioteca monitorea el número de objetos gráficos en todos los gráficos del terminal y dispone de banderas de eventos para cada gráfico abierto; cuando el número de objetos en el gráfico disminuye, podemos averiguar la cantidad de objetos eliminados del gráfico y procesar la situación.
Necesitamos un evento de desplazamiento del objeto gráfico para controlar los cambios en la posición del objeto gráfico en general, así como sus puntos de anclaje individuales en particular.

Sobre el evento de desplazamiento, merece la pena señalar que también se activa cuando el objeto se crea manualmente. Cuando clicamos en el gráfico para configurar el objeto, pero aún no hemos soltado el botón, el objeto es creado y la biblioteca lo ve, creando el objeto de la clase correspondiente y añadiéndolo a la colección. Al mismo tiempo, no todos los valores de las propiedades del objeto están configurados correctamente (después de todo, aún no hemos soltado el botón y podemos mover el objeto con el ratón o establecer otros puntos de anclaje si el objeto se extrae de varios puntos). Pero cuando soltamos el botón del ratón (si el objeto ya ha establecido todos sus puntos de anclaje para una construcción exitosa), se crea un evento para mover el objeto gráfico. Rastreando el evento y modificando los valores de propiedad del objeto de clase ya creado según los parámetros completamente establecidos del objeto gráfico creado, establecemos los valores correctos de todas las propiedades del objeto recién creado

Cambiar el nombre de un objeto implica tres eventos al mismo tiempo: la eliminación de un objeto, la creación de un objeto y el cambio de las propiedades del mismo. Podemos monitorear estos tres eventos para comprender si ha sucedido un cambio en el nombre de algún objeto de los existentes, pero seguiremos el camino más simple. El caso es que cuando cambiamos el nombre de un objeto, en cualquier caso, el último evento será el evento CHARTEVENT_OBJECT_CHANGE, que nosotros procesaremos. Y como todos los objetos se seleccionan en el terminal según el nombre y el identificador del gráfico, podemos verificar qué objeto ha desaparecido en la lista de colección entre aquellos que se encuentran en el gráfico. Tenemos que buscar el nombre de un objeto en el gráfico para el que no hay ningún objeto de clase en la colección (1), buscar un objeto en la colección para el que no hay ningún objeto correspondiente al nombre en el gráfico (2) y escribir este nombre en el objeto de clase que se encuentra en (1) en la lista de colección. Suena "aterrador", pero en realidad todo es simple.

Para entender qué propiedad del objeto ha sido cambiada (después de todo, recibimos un evento que contiene solo el nombre del objeto cambiado, pero no se indica la propiedad modificada), hay que comparar todas las propiedades del objeto, es decir, debemos comparar sus valores actuales con los que había antes de obtener el evento de cambio de las propiedades de un objeto. Por consiguiente, necesitamos crear tres matrices más, que se encargarán de guardar las propiedades "anteriores" del objeto.

Abrimos el archivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh de la clase de objeto gráfico abstracto estándar y escribimos en la sección privada las nuevas matrices para guardar las propiedades "anteriores" del objeto:

//+------------------------------------------------------------------+
//| The class of the abstract standard graphical object              |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   long              m_long_prop[GRAPH_OBJ_PROP_INTEGER_TOTAL];         // Integer properties
   double            m_double_prop[GRAPH_OBJ_PROP_DOUBLE_TOTAL];        // Real properties
   string            m_string_prop[GRAPH_OBJ_PROP_STRING_TOTAL];        // String properties
   
   long              m_long_prop_prev[GRAPH_OBJ_PROP_INTEGER_TOTAL];    // Integer properties before change
   double            m_double_prop_prev[GRAPH_OBJ_PROP_DOUBLE_TOTAL];   // Real properties before change
   string            m_string_prop_prev[GRAPH_OBJ_PROP_STRING_TOTAL];   // String properties before change

//--- Return the index of the array the (1) double and (2) string properties are actually located at
   int               IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE property)  const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL;                              }
   int               IndexProp(ENUM_GRAPH_OBJ_PROP_STRING property)  const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_DOUBLE_TOTAL;  }

public:

En la sección pública de la clase, añadimos los métodos para establecer y retornar las propiedades "anteriores" del objeto:

public:
//--- Set object's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                         }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value;       }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value;       }
//--- Return object’s (1) integer, (2) real and (3) string property from the properties array
   long              GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property)        const { return this.m_long_prop[property];                        }
   double            GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];      }
   string            GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];      }
   
//--- Set object's previous (1) integer, (2) real and (3) string properties
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value) { this.m_long_prop_prev[property]=value;                  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value){ this.m_double_prop_prev[this.IndexProp(property)]=value;}
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,string value){ this.m_string_prop_prev[this.IndexProp(property)]=value;}
//--- Return object’s (1) integer, (2) real and (3) string property from the previous properties array
   long              GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property)    const { return this.m_long_prop_prev[property];                   }
   double            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property)     const { return this.m_double_prop_prev[this.IndexProp(property)]; }
   string            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property)     const { return this.m_string_prop_prev[this.IndexProp(property)]; }
   
//--- Return itself
   CGStdGraphObj    *GetObject(void)                                                { return &this;}


Tenemos la propiedad "identificador del objeto" en el objeto de clase que describe el objeto gráfico. La necesitamos para establecer una cierta etiqueta única del objeto, que nos ayudará a identificar este.
Esta propiedad no debe participar en la definición de los eventos del objeto. Por consiguiente, en el método para configurar el identificador del objeto, escribiremos el valor transmitido al método en ambas propiedades a la vez: en la actual (escrita anteriormente) y en la pasada (la añadiremos ahora):

public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+
//--- Object index in the list
   int               Number(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM);                              }
   void              SetNumber(const int number)         { this.SetProperty(GRAPH_OBJ_PROP_NUM,number);                                   }
//--- Object ID
   long              ObjectID(void)                const { return this.GetProperty(GRAPH_OBJ_PROP_ID);                                    }
   void              SetObjectID(const long obj_id)
                       {
                        CGBaseObj::SetObjectID(obj_id);
                        this.SetProperty(GRAPH_OBJ_PROP_ID,obj_id);
                        this.SetPropertyPrev(GRAPH_OBJ_PROP_ID,obj_id);
                       }
//--- Graphical object type


El algoritmo de reacción ante un evento será el siguiente: después de recibir el evento, necesitamos actualizar todos los datos del objeto de clase que describe el objeto gráfico para que todas sus propiedades tengan valores reales. Como no sabemos qué propiedad ha cambiado, actualizamos todas las propiedades del objeto y luego comparamos las propiedades actuales con las que tenía el objeto antes de recibir el evento de cambio. A continuación, compararemos en tres ciclos las tres matrices de propiedades del objeto con las matrices correspondientes de las propiedades anteriores. En el momento de la comparación, y si la propiedad actual (su valor) no es igual al valor anterior, mostraremos temporalmente (todavía estamos comprobando una variante de prueba) un mensaje sobre el cambio de esta propiedad en el diario de registro. Y así, para cada diferencia hallada en los valores comparados en cada matriz de propiedades del objeto. Posteriormente, cuando implementemos ya el control de los cambios de propiedad para cada uno de los gráficos abiertos, lo haremos de una manera ligeramente distinta: "añadiremos" todas las propiedades modificadas en un objeto de evento y obtendremos cada uno de esos objetos en la clase de colección para después indicar al programa de control que han cambiado las propiedades de cada uno de los objetos en cada gráfico abierto.

En la sección pública de la clase, declararemos un método para sobrescribir todas las propiedades del objeto (será necesario cuando se detecte un evento de cambio de propiedad) para revisar todas las propiedades de un objeto gráfico a la vez y añadirlas en las propiedades del objeto de clase.
Método que verifica el cambio de propiedades del objeto; el método comparará todas las propiedades actuales del objeto con su estado anterior.

En la sección privada de la clase, declararemos los tres métodos necesarios para obtener todas las propiedades del objeto gráfico y escribirlas en las propiedades del objeto de clase.
Y el método que copia las propiedades actuales en las anteriores, de forma que en la siguiente comprobación, al detectarse un evento, ya se compararán con las propiedades recién cambiadas:

//--- Return the description of the object visibility on timeframes
   string            VisibleOnTimeframeDescription(void);

//--- Re-write all graphical object properties
   void              PropertiesRefresh(void);
//--- Check object property changes
   void              PropertiesCheckChanged(void);
   
private:
//--- Get and save (1) integer, (2) real and (3) string properties
   void              GetAndSaveINT(void);
   void              GetAndSaveDBL(void);
   void              GetAndSaveSTR(void);
//--- Copy the current data to the previous one
   void              PropertiesCopyToPrevData(void);
   
  };
//+------------------------------------------------------------------+

Vamos a simplificar el constructor paramétrico protegido. Antes, en este se recibían todas las propiedades inherentes a todos los objetos gráficos desde el objeto gráfico y se anotaban en el objeto de clase:

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_GROUP group,
                             const long chart_id,const string name)
  {
//--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits
   this.m_type=obj_type;
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetGroup(group);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Save integer properties
   //--- properties inherent in all graphical objects but not present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID]    = CGBaseObj::ChartID();          // Chart ID
   this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM]     = CGBaseObj::SubWindow();        // Chart subwindow index
   this.m_long_prop[GRAPH_OBJ_PROP_TYPE]        = CGBaseObj::TypeGraphObject();  // Graphical object type (ENUM_OBJECT)
   this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.m_long_prop[GRAPH_OBJ_PROP_BELONG]      = CGBaseObj::Belong();           // Graphical object affiliation
   this.m_long_prop[GRAPH_OBJ_PROP_GROUP]       = CGBaseObj::Group();            // Graphical object group
   this.m_long_prop[GRAPH_OBJ_PROP_ID]          = 0;                             // Object ID
   this.m_long_prop[GRAPH_OBJ_PROP_NUM]         = 0;                             // Object index in the list
   //--- Properties inherent in all graphical objects and present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME]  = ::ObjectGetInteger(chart_id,name,OBJPROP_CREATETIME);  // Object creation time
   this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES]  = ::ObjectGetInteger(chart_id,name,OBJPROP_TIMEFRAMES);  // Object visibility on timeframes
   this.m_long_prop[GRAPH_OBJ_PROP_BACK]        = ::ObjectGetInteger(chart_id,name,OBJPROP_BACK);        // Background object
   this.m_long_prop[GRAPH_OBJ_PROP_ZORDER]      = ::ObjectGetInteger(chart_id,name,OBJPROP_ZORDER);      // Priority of a graphical object for receiving the event of clicking on a chart
   this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN]      = ::ObjectGetInteger(chart_id,name,OBJPROP_HIDDEN);      // Disable displaying the name of a graphical object in the terminal object list
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTED]    = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTED);    // Object selection
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE]  = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTABLE);  // Object availability
   this.m_long_prop[GRAPH_OBJ_PROP_TIME]        = ::ObjectGetInteger(chart_id,name,OBJPROP_TIME);        // First point time coordinate
   this.m_long_prop[GRAPH_OBJ_PROP_COLOR]       = ::ObjectGetInteger(chart_id,name,OBJPROP_COLOR);       // Color
   this.m_long_prop[GRAPH_OBJ_PROP_STYLE]       = ::ObjectGetInteger(chart_id,name,OBJPROP_STYLE);       // Style
   this.m_long_prop[GRAPH_OBJ_PROP_WIDTH]       = ::ObjectGetInteger(chart_id,name,OBJPROP_WIDTH);       // Line width
   //--- Properties belonging to different graphical objects
   this.m_long_prop[GRAPH_OBJ_PROP_FILL]                          = 0;  // Object color filling
   this.m_long_prop[GRAPH_OBJ_PROP_READONLY]                      = 0;  // Ability to edit text in the Edit object
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELS]                        = 0;  // Number of levels
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR]                    = 0;  // Level line color
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE]                    = 0;  // Level line style
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH]                    = 0;  // Level line width
   this.m_long_prop[GRAPH_OBJ_PROP_ALIGN]                         = 0;  // Horizontal text alignment in the Edit object (OBJ_EDIT)
   this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE]                      = 0;  // Font size
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT]                      = 0;  // Ray goes to the left
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT]                     = 0;  // Ray goes to the right
   this.m_long_prop[GRAPH_OBJ_PROP_RAY]                           = 0;  // Vertical line goes through all windows of a chart
   this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE]                       = 0;  // Display the full ellipse of the Fibonacci Arc object
   this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE]                     = 0;  // Arrow code for the "Arrow" object
   this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR]                        = 0;  // Position of the binding point of the graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE]                     = 0;  // Distance from the base corner along the X axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE]                     = 0;  // Distance from the base corner along the Y axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION]                     = 0;  // Gann object trend
   this.m_long_prop[GRAPH_OBJ_PROP_DEGREE]                        = 0;  // Elliott wave marking level
   this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES]                     = 0;  // Display lines for Elliott wave marking
   this.m_long_prop[GRAPH_OBJ_PROP_STATE]                         = 0;  // Button state (pressed/released)
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID]            = 0;  // Chart object ID (OBJ_CHART).
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD]              = 0;  // Chart object period<
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE]          = 0;  // Time scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]         = 0;  // Price scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]         = 0;  // Chart object scale
   this.m_long_prop[GRAPH_OBJ_PROP_XSIZE]                         = 0;  // Object width along the X axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_YSIZE]                         = 0;  // Object height along the Y axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET]                       = 0;  // X coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET]                       = 0;  // Y coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR]                       = 0;  // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.m_long_prop[GRAPH_OBJ_PROP_CORNER]                        = 0;  // Chart corner for binding a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE]                   = 0;  // Border type for "Rectangle border"
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]                  = 0;  // Border color for OBJ_EDIT and OBJ_BUTTON
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)]       = ::ObjectGetDouble(chart_id,name,OBJPROP_PRICE);  // Price coordinate
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)]  = 0;                                               // Level value
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)]       = 0;                                               // Scale (property of Gann objects and Fibonacci Arcs objects)
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)]       = 0;                                               // Angle
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)]   = 0;                                               // Deviation of the standard deviation channel
   
//--- Save string properties
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_NAME)]        = name;                                            // Object name
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)]        = ::ObjectGetString(chart_id,name,OBJPROP_TEXT);   // Object description (the text contained in the object)
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)]     = ::ObjectGetString(chart_id,name,OBJPROP_TOOLTIP);// Tooltip text
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)]   = "";                                              // Level description
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)]        = "";                                              // Font
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)]     = "";                                              // BMP file name for the "Bitmap Level" object
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]= "";                                          // Chart object symbol 
   
//--- Save basic properties in the parent object
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN);
   this.m_name=this.GetProperty(GRAPH_OBJ_PROP_NAME);

  }
//+-------------------------------------------------------------------+

Ahora, trasladaremos todas estas líneas a métodos aparte, que se llamarán al mismo tiempo desde el método PropertiesRefresh().
Por eso, eliminamos estas líneas; el constructor quedará como sigue:

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_GROUP group,
                             const long chart_id,const string name)
  {
//--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits
   this.m_type=obj_type;
   this.SetName(name);
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetGroup(group);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Save integer properties
   //--- properties inherent in all graphical objects but not present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID]    = CGBaseObj::ChartID();          // Chart ID
   this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM]     = CGBaseObj::SubWindow();        // Chart subwindow index
   this.m_long_prop[GRAPH_OBJ_PROP_TYPE]        = CGBaseObj::TypeGraphObject();  // Graphical object type (ENUM_OBJECT)
   this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.m_long_prop[GRAPH_OBJ_PROP_BELONG]      = CGBaseObj::Belong();           // Graphical object affiliation
   this.m_long_prop[GRAPH_OBJ_PROP_GROUP]       = CGBaseObj::Group();            // Graphical object group
   this.m_long_prop[GRAPH_OBJ_PROP_ID]          = 0;                             // Object ID
   this.m_long_prop[GRAPH_OBJ_PROP_NUM]         = 0;                             // Object index in the list

//--- Save the properties inherent in all graphical objects and present in a graphical object
   this.PropertiesRefresh();

//--- Save basic properties in the parent object
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN);

//--- Save the current properties to the previous ones
   this.PropertiesCopyToPrevData();
   
  }
//+-------------------------------------------------------------------+

Para que el método PropertiesRefresh() funcione correctamente, deberá conocer el nombre del objeto gráfico desde el cual recibe los datos. Antes, el nombre se escribía casi al final, en el bloque para leer parámetros de tipo string. Ahora, leeremos el nombre del objeto gráfico y lo escribiremos en las propiedades del objeto de clase, justo al entrar en el constructor. Al final, después de escribir todas las propiedades en el objeto de la clase, llamaremos al método PropertiesCopyToPrevData(), que escribirá todas las propiedades del objeto ya guardadas en las matrices de propiedades "anteriores", para así controlar sus cambios.

Métodos que obtienen las propiedades de tipo entero, real y string de un objeto gráfico y las almacenan en un objeto de clase:

//+------------------------------------------------------------------+
//| Get and save the integer properties                              |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveINT(void)
  {
   //--- Properties inherent in all graphical objects and present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME); // Object creation time
   this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES); // Object visibility on timeframes
   this.m_long_prop[GRAPH_OBJ_PROP_BACK]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK);       // Background object
   this.m_long_prop[GRAPH_OBJ_PROP_ZORDER]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER);     // Priority of a graphical object for receiving the event of clicking on a chart
   this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN);     // Disable displaying the name of a graphical object in the terminal object list
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTED]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED);   // Object selection
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE); // Object availability
   this.m_long_prop[GRAPH_OBJ_PROP_TIME]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIME);       // First point time coordinate
   this.m_long_prop[GRAPH_OBJ_PROP_COLOR]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR);      // Color
   this.m_long_prop[GRAPH_OBJ_PROP_STYLE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE);      // Style
   this.m_long_prop[GRAPH_OBJ_PROP_WIDTH]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH);      // Line width
   //--- Properties belonging to different graphical objects
   this.m_long_prop[GRAPH_OBJ_PROP_FILL]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL);       // Fill an object with color
   this.m_long_prop[GRAPH_OBJ_PROP_READONLY]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY);   // Ability to edit text in the Edit object
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELS]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS);     // Number of levels
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELCOLOR); // Level line color
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELSTYLE); // Level line style
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELWIDTH); // Level line width
   this.m_long_prop[GRAPH_OBJ_PROP_ALIGN]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN);      // Horizontal text alignment in the Edit object (OBJ_EDIT)
   this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE);   // Font size
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT);   // Ray goes to the left
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT);  // Ray goes to the right
   this.m_long_prop[GRAPH_OBJ_PROP_RAY]         = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY);        // Vertical line goes through all windows of a chart
   this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE);    // Display the full ellipse of the Fibonacci Arc object
   this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE);  // Arrow code for the "Arrow" object
   this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR);     // Position of the binding point of the graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE);  // Distance from the base corner along the X axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE);  // Distance from the base corner along the Y axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION);  // Gann object trend
   this.m_long_prop[GRAPH_OBJ_PROP_DEGREE]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE);     // Elliott wave marking level
   this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES);  // Display lines for Elliott wave marking
   this.m_long_prop[GRAPH_OBJ_PROP_STATE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE);      // Button state (pressed/released)
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID);   // Chart object ID (OBJ_CHART).
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD);     // Chart object period
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE); // Time scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE);// Price scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE);// Chart object scale
   this.m_long_prop[GRAPH_OBJ_PROP_XSIZE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE);      // Object width along the X axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_YSIZE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE);      // Object height along the Y axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET);    // X coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET);    // Y coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR);    // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.m_long_prop[GRAPH_OBJ_PROP_CORNER]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER);     // Chart corner for binding a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE);// Border type for "Rectangle border"
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR);// Border color for OBJ_EDIT and OBJ_BUTTON
  }
//+------------------------------------------------------------------+
//| Get and save the real properties                                 |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveDBL(void)
  {
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_PRICE);       // Price coordinate
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)]  = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_LEVELVALUE);  // Level value
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_SCALE);       // Scale (property of Gann objects and Fibonacci Arcs objects)
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_ANGLE);       // Corner
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)]   = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_DEVIATION);   // Deviation of the standard deviation channel
  }
//+------------------------------------------------------------------+
//| Get and save the string properties                               |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveSTR(void)
  {
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)]              = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TEXT);     // Object description (the text contained in the object)
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)]           = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TOOLTIP);  // Tooltip text
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)]         = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_LEVELTEXT);// Level description
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)]              = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_FONT);     // Font
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)]           = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_BMPFILE);  // BMP file name for the "Bitmap Level" object
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]  = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_SYMBOL);   // Chart object symbol 
  }
//+------------------------------------------------------------------+

En estos tres métodos, simplemente se trasladan las líneas eliminadas del constructor de la clase; estas ahora son reemplazadas por una llamada al método que sobrescribe todas las propiedades del objeto gráfico:

//+------------------------------------------------------------------+
//| Overwrite all graphical object properties                        |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesRefresh(void)
  {
   this.GetAndSaveINT();
   this.GetAndSaveDBL();
   this.GetAndSaveSTR();
  }
//+------------------------------------------------------------------+

Aquí, los tres métodos anteriores se llaman por turno.

Método que copia las propiedades actuales del objeto de clase en las anteriores:

//+------------------------------------------------------------------+
//| Copy the current data to the previous one                        |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCopyToPrevData(void)
  {
   ::ArrayCopy(this.m_long_prop_prev,this.m_long_prop);
   ::ArrayCopy(this.m_double_prop_prev,this.m_double_prop);
   ::ArrayCopy(this.m_string_prop_prev,this.m_string_prop);
  }
//+------------------------------------------------------------------+

Aquí, usando la función de copiado de matrices, copiamos por turno las matrices de las propiedades enteras, reales y string en las matrices correspondientes de las propiedades anteriores.

Método que busca cambios en las propiedades del objeto:

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   bool changed=false;
   int beg=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   beg=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   beg=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }
   if(changed)
      PropertiesCopyToPrevData();
  }
//+------------------------------------------------------------------+

Aquí, en tres ciclos por separado para las propiedades enteras, reales y string, obtenemos la siguiente propiedad de la matriz correspondiente y la comparamos con la misma propiedad en la matriz de propiedades anteriores. Si los valores comparados de las propiedades actuales y anteriores no son iguales, esto indicará que la propiedad ha cambiado, por lo que estableceremos la bandera de cambio en las propiedades del objeto y escribiremos un mensaje en el diario de registro.

Este es un método de prueba utilizado solo para comprobar la idea propuesta de búsqueda de cambios en las propiedades del objeto. En artículos posteriores, refinaremos este concepto para alcanzar un estado completamente funcional; asimismo, monitorearemos los cambios en todos los objetos gráficos en todos los gráficos abiertos y enviaremos eventos sobre los cambios en las propiedades del objeto al gráfico del programa de control, que la biblioteca podrá procesar más adelante.


Monitoreando la eliminación de los objetos gráficos

La modificación de las propiedades del objeto gráfico se monitorea en la clase de este objeto, ya que las propiedades del objeto gráfico pertenecen a este objeto, se escriben en la clase de objeto y se pueden verificar en él. No obstante, todos los métodos creados anteriormente para la actualización de las propiedades del objeto y la comprobación de sus cambios se llamarán desde la clase de colección de objetos gráficos. Minetras tanto, la adición y la eliminación de los objetos gráficos en el gráfico, solo podemos monitorearlas en la clase de colección de objetos gráficos; esta clase controla la lista completa de todos los objetos en todos los gráficos abiertos y realiza un seguimiento de ellos en su propia lista de colección.

Ahora, abrimos el archivo de la clase de colección de objetos gráficos \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh y añadimos todas las modificaciones necesarias.

En la clase de control de objetos de gráficos, que también se encuentra en el mismo archivo, declaramos un método privado que establece el permiso para que el gráfico monitoree los eventos del ratón y los objetos gráficos. Lo necesitamos para que los permisos de monitoreo de estos eventos se establezcan para cada uno de los gráficos abiertos.
En los constructores de clase, llamamos a estos métodos para otorgarle los permisos a los gráficos para los cuales se crearán estos objetos de control. Al final del listado de la clase, declaramos el manejador de eventos (analizaremos su implementación en el próximo artículo):

//+------------------------------------------------------------------+
//| Chart object management class                                    |
//+------------------------------------------------------------------+
class CChartObjectsControl : public CObject
  {
private:
   CArrayObj         m_list_new_graph_obj;      // List of added graphical objects
   ENUM_TIMEFRAMES   m_chart_timeframe;         // Chart timeframe
   long              m_chart_id;                // Chart ID
   string            m_chart_symbol;            // Chart symbol
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_last_objects;            // Number of graphical objects during the previous check
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
   
//--- Return the name of the last graphical object added to the chart
   string            LastAddedGraphObjName(void);
//--- Set the permission to track mouse events and graphical objects
   void              SetMouseEvent(void);
   
public:
//--- Return the variable values
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_chart_timeframe;    }
   long              ChartID(void)                             const { return this.m_chart_id;           }
   string            Symbol(void)                              const { return this.m_chart_symbol;       }
   bool              IsEvent(void)                             const { return this.m_is_graph_obj_event; }
   int               TotalObjects(void)                        const { return this.m_total_objects;      }
   int               Delta(void)                               const { return this.m_delta_graph_obj;    }
//--- Create a new standard graphical object
   CGStdGraphObj    *CreateNewGraphObj(const ENUM_OBJECT obj_type,const long chart_id, const string name);
//--- Return the list of newly added objects
   CArrayObj        *GetListNewAddedObj(void)                        { return &this.m_list_new_graph_obj;}
//--- Check the chart objects
   void              Refresh(void);
//--- Constructors
                     CChartObjectsControl(void)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=::ChartID();
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.SetMouseEvent();
                       }
                     CChartObjectsControl(const long chart_id)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=chart_id;
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.SetMouseEvent();
                       }
                     
//--- Compare CChartObjectsControl objects by a chart ID (for sorting the list by an object property)
   virtual int       Compare(const CObject *node,const int mode=0) const
                       {
                        const CChartObjectsControl *obj_compared=node;
                        return(this.ChartID()>obj_compared.ChartID() ? 1 : this.ChartID()<obj_compared.ChartID() ? -1 : 0);
                       }

//--- Event handler
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);

  };
//+------------------------------------------------------------------+

Fuera del cuerpo de la clase, escribimos la implementación del método encargado de establecer los permisos para monitorear los eventos del ratón y los objetos gráficos:

//+------------------------------------------------------------------+
//| Set the permission                                               |
//| to track mouse and graphical object events for the chart         |
//+------------------------------------------------------------------+
void CChartObjectsControl::SetMouseEvent(void)
  {
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_CREATE,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_DELETE,true);
  }
//+------------------------------------------------------------------+


Ahora, vamos a ocuparnos de la clase de colección de objetos gráficos.

En la sección privada de la clase, declaramos los nuevos métodos, cuya descripción indica claramente su propósito:

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
class CGraphElementsCollection : public CBaseObj
  {
private:
   CArrayObj         m_list_charts_control;     // List of chart management objects
   CListObj          m_list_all_canv_elm_obj;   // List of all graphical elements on canvas
   CListObj          m_list_all_graph_obj;      // List of all graphical objects
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of 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 presence in the collection list of graphical elements
   bool              IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj);
//--- Return the flag indicating the graphical element object presence in the collection list of graphical objects
   bool              IsPresentGraphObjInList(const long chart_id,const string name);
//--- Return the flag indicating the presence of a graphical object on a chart by name
   bool              IsPresentGraphObjOnChart(const long chart_id,const string name);
//--- Return the pointer to the object of managing objects of the specified chart
   CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id);
//--- Create a new object of managing graphical objects of a specified chart and add it to the list
   CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id);
//--- Update the list of graphical objects by chart ID
   CChartObjectsControl *RefreshByChartID(const long chart_id);
//--- Return the first free ID of the graphical (1) object and (2) element on canvas
   long              GetFreeGraphObjID(void);
   long              GetFreeCanvElmID(void);
//--- Add a graphical object to the collection
   bool              AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control);
//--- Find an object present in the collection but not on a chart
   CGStdGraphObj    *FindMissingObj(const long chart_id);
//--- Find the graphical object present on a chart but not in the collection
   string            FindExtraObj(const long chart_id);
//--- Remove the graphical object from the graphical object collection list
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   
public:

En la sección pública de la clase, declaramos el método que retorna un objeto gráfico según el nombre y el identificador del gráfico y el manejador de eventos del gráfico:

public:
//--- Return itself
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
//--- Return the full collection list of standard graphical objects "as is"
   CArrayObj        *GetListGraphObj(void)                                                               { return &this.m_list_all_graph_obj;   }
//--- Return the full collection list of graphical elements on canvas "as is"
   CArrayObj        *GetListCanvElm(void)                                                                { return &this.m_list_all_canv_elm_obj;}
//--- Return the list of graphical elements by a 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.GetListCanvElm(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
//--- Return the list of graphical objects by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)    { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
//--- Return the number of new graphical objects, (3) the flag of the occurred change in the list of graphical objects
   int               NewObjects(void)   const                                                            { return this.m_delta_graph_obj;       }
   bool              IsEvent(void) const                                                                 { return this.m_is_graph_obj_event;    }
//--- Return a graphical object by chart name and ID
   CGStdGraphObj    *GetStdGraphObject(const string name,const long chart_id);
//--- 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);

//--- Create the list of chart management objects and return the number of charts
   int               CreateChartControlList(void);
//--- Update the list of (1) all graphical objects, (2) on the specified chart, fill in the data on the number of new ones and set the event flag
   void              Refresh(void);
   void              Refresh(const long chart_id);
//--- Event handler
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
  };
//+------------------------------------------------------------------+

En el método que actualiza la lista con todos los objetos gráficos, escribimos un bloque para procesar la eliminación de un objeto gráfico del gráfico:

//+------------------------------------------------------------------+
//| Update the list of all graphical objects                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::Refresh(void)
  {
//--- Declare variables to search for charts
   long chart_id=0;
   int i=0;
//--- In the loop by all open charts in the terminal (no more than 100)
   while(i<CHARTS_MAX)
     {
      //--- Get the chart ID
      chart_id=::ChartNext(chart_id);
      if(chart_id<0)
         break;
      //--- Get the pointer to the object for managing graphical objects
      //--- and update the list of graphical objects by chart ID
      CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id);
      //--- If failed to get the pointer, move on to the next chart
      if(obj_ctrl==NULL)
         continue;
      //--- If the number of objects on the chart changes
      if(obj_ctrl.IsEvent())
        {
         //--- If a graphical object is added to the chart
         if(obj_ctrl.Delta()>0)
           {
            //--- Get the list of added graphical objects and move them to the collection list
            //--- (if failed to move the object to the collection, move on to the next object)
            if(!AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               continue;
           }
         //--- If the graphical object has been removed
         else if(obj_ctrl.Delta()<0)
           {
            // Find an extra object in the list
            CGStdGraphObj *obj=this.FindMissingObj(chart_id);
            if(obj!=NULL)
              {
               //--- Display a short description of a detected object deleted from a chart in the journal
               obj.PrintShort();
               //--- Remove the class object of a removed graphical object from the collection list
               if(!this.DeleteGraphObjFromList(obj))
                  CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
              }
           }
         //--- otherwise
         else
           {
            
           }
        }
      //--- Increase the loop index
      i++;
     }
  }
//+------------------------------------------------------------------+

En este método, en un ciclo por el número total de objetos de control del gráfico, comprobamos la presencia de un evento en cada objeto de control y, si el evento existe, verificamos la magnitud en la que ha cambiado el número de objetos en el gráfico que está controlado por el objeto de control del gráfico. Ya escribimos el procesamiento de la adición de un objeto en el último artículo; hoy implementaremos el procesamiento del valor negativo del cambio en el número de objetos en el gráfico.
Todo es simple aquí: buscamos en la lista de colección un objeto para el que no hay un objeto gráfico en el gráfico y lo eliminamos de la lista de colección.

Método que busca un objeto que está presente en la colección, pero no lo está en el gráfico:

//+------------------------------------------------------------------+
//|Find an object present in the collection but not on a chart       |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   if(list==NULL)
      return NULL;
   for(int i=0;i<list.Total();i++)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name()))
         return obj;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

Aquí, obtenemos una lista con todos los objetos cuyo identificador de gráfico es igual al indicado en los parámetros del método.
En un ciclo por la lista resultante, obtenemos otro objeto de la clase del objeto gráfico estándar y, si no hay ningún objeto con este nombre en el gráfico, retornamos el puntero a este objeto.
Al final del ciclo, retornamos NULL.

Método que busca un objeto que está presente en el gráfico, pero ausente en la colección:

//+------------------------------------------------------------------+
//|Find an object present on a chart but not in the collection       |
//+------------------------------------------------------------------+
string CGraphElementsCollection::FindExtraObj(const long chart_id)
  {
   int total=::ObjectsTotal(chart_id);
   for(int i=0;i<total;i++)
     {
      string name=::ObjectName(chart_id,i);
      if(!this.IsPresentGraphObjInList(chart_id,name))
         return name;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

Aquí, en un ciclo a través de todos los objetos en la lista del terminal, obtenemos el nombre del siguiente objeto y, si no hay un objeto con el mismo nombre e identificador de gráfico en la lista de colección, retornamos el nombre del objeto gráfico. Al final del ciclo, retornamos NULL.

Método que retorna la bandera de presencia de un objeto de clase de objeto gráfico en la lista de colección de objetos gráficos:

//+-----------------------------------------------------------------------------------+
//| Return the flag indicating the presence of the graphical object class object      |
//| in the graphical object collection list                                           |
//+-----------------------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentGraphObjInList(const long chart_id,const string name)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL);
   return(list==NULL || list.Total()==0 ? false : true);
  }
//+------------------------------------------------------------------+

Obtenemos la lista de objetos con el identificador de gráfico especificado. De la lista resultante, obtenemos el puntero al objeto cuyo nombre coincide con el necesario. Si la lista no se ha podido recuperar o está vacía, retornamos false, el objeto no ha sido encontrado; de lo contrario, retornamos true.

Método que retorna la bandera de presencia de un objeto gráfico en el gráfico según el nombre:

//+----------------------------------------------------------------------------------+
//| Return the flag indicating the presence of a graphical object on a chart by name |
//+----------------------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentGraphObjOnChart(const long chart_id,const string name)
  {
   int total=::ObjectsTotal(chart_id);
   for(int i=0;i<total;i++)
      if(::ObjectName(chart_id,i)==name)
         return true;
   return false;
  }
//+-------------------------------------------------------------------+

En un ciclo por el número total de objetos gráficos en el gráfico indicado según el identificador, obtenemos el nombre del siguiente objeto y, si el nombre coincide con el buscado, retornamos true. Al final del ciclo, retornamos false.

Método que elimina un objeto gráfico de la lista de colección de objetos gráficos:

//+---------------------------------------------------------------------+
//|Remove the graphical object from the graphical object collection list|
//+---------------------------------------------------------------------+
bool CGraphElementsCollection::DeleteGraphObjFromList(CGStdGraphObj *obj)
  {
   this.m_list_all_graph_obj.Sort();
   int index=this.m_list_all_graph_obj.Search(obj);
   return(index==WRONG_VALUE ? false : this.m_list_all_graph_obj.Delete(index));
  }
//+------------------------------------------------------------------+

Al método se le transmite el puntero al objeto que debe eliminarse de la lista.
Asignamos a la lista la bandera de lista clasificada
(la búsqueda se realiza solo en listas clasificadas),
y obtenemos el índice del objeto usando el método Search()
de la biblioteca estándar.
Si no se encuentra el índice del objeto, retornamos false
;
de lo contrario, retornamos el resultado de la eliminación del objeto de la lista usando el método Delete()
de la Biblioteca Estándar.

Método que retorna el puntero a un objeto gráfico según el nombre e identificador del gráfico:

//+------------------------------------------------------------------+
//| Return a graphical object by chart name and ID                   |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const string name,const long chart_id)
  {
   CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,chart_id);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Obtenemos la lista de objetos cuyo identificador de gráfico es igual al transmitido al método. De la lista resultante, obtenemos la lista que contiene el objeto cuyo nombre coincide con el buscado (solo debe haber uno de esos objetos). Si la lista se ha obtenido con éxito y no está vacía, retornamos el puntero al primer (y único) objeto de la lista. De lo contrario, retornamos NULL.

Manejador de eventos.

Hoy crearemos una versión de prueba del manejador de eventos, que procesará los eventos de los objetos gráficos solo en el gráfico actual. El manejador procesará los eventos de cambio o desplazamiento de objetos gráficos. Este método bastará para determinar los eventos mencionados y, al mismo tiempo, para resolver el problema del rellenado incompleto de las propiedades de los objetos construidos con más de un clic del ratón. Ya hemos discutido anteriormente que el primer clic en el gráfico genera el evento de creación de un objeto, y que la biblioteca crea inmediatamente el objeto correspondiente.

En este caso, no todas las propiedades se registran correctamente en él, ya que el objeto se construye con unos pocos clics del ratón. Y ahora, una vez completada la construcción del objeto, se crea un evento de desplazamiento, al que reaccionaremos sobrescribiendo además las propiedades del objeto (queremos monitorear este evento durante el desplazamiento real, pero al crear un objeto con varios puntos de anclaje, también obtenemos un evento de desplazamiento que hará que se actualicen las propiedades del objeto y se sobrescriban con los datos correctos).

La lógica completa se describe en los comentarios al código del método. Solo analizaremos el método de autoaprendizaje:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj=NULL;
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG)
     {
      //--- If the object, whose properties were changed or which was relocated,
      //--- is successfully received from the collection list by its name set in sparam
      obj=this.GetStdGraphObject(sparam,::ChartID());
      if(obj!=NULL)
        {
         //--- Update the properties of the obtained object
         //--- and check their change
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      else
        {
         //--- Let's search the list for the object that is not on the chart
         obj=this.FindMissingObj(::ChartID());
         if(obj==NULL)
            return;
         //--- Get the name of the renamed graphical object on the chart, which is not in the collection list
         string name_new=this.FindExtraObj(::ChartID());
         //--- Set a new name for the collection list object, which does not correspond to any graphical object on the chart,
         //--- update the chart properties and check their change
         obj.SetName(name_new);
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
     }
  }
//+------------------------------------------------------------------+


Queda un pequeño detalle: necesitamos acceder a la colección de objetos gráficos de los programas creados a partir de la biblioteca.
Para ello, en el objeto principal de la biblioteca, crearemos en \MQL5\Include\DoEasy\Engine.mqh un método que retorna el puntero a la clase de colección de objetos gráficos de la biblioteca:

//--- Launch the new pause countdown
   void                 Pause(const ulong pause_msc,const datetime time_start=0)
                          {
                           this.PauseSetWaitingMSC(pause_msc);
                           this.PauseSetTimeBegin(time_start*1000);
                           while(!this.PauseIsCompleted() && !::IsStopped()){}
                          }

//--- Return the graphical object collection
   CGraphElementsCollection *GetGraphicObjCollection(void)              { return &this.m_graph_objects; }

//--- Constructor/destructor
                        CEngine();
                       ~CEngine();

private:


Simulación

Para la simulación, vamos a tomar el asesor del artículo anterior y a guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part86\ con el nuevo nombre TestDoEasyPart86.mq5.

Eliminamos del manejador OnInit() las líneas en las que se establecen los permisos de monitoreo de los eventos del ratón:

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

Ahora, los permisos para monitorear los eventos del ratón y los eventos de los objetos gráficos se establecen en la clase de control de gráficos, y se establecen estos permisos para todos los gráficos abiertos.

Al final del manejador OnChartEvent(), escribimos la llamada al manejador de eventos de la clase de colección de objetos gráficos:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If working in the tester, exit
   if(MQLInfoInteger(MQL_TESTER))
      return;
//--- 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, allow scrolling a chart with the mouse and show the context menu
         list_forms.Clear();
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,true);
         ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,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 and showing the context menu
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,false);
         ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,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,114,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();
        }
     }
   
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);
   
  }
//+------------------------------------------------------------------+

Esto es todo lo que debíamos mejorar. Compilamos el asesor y lo ejecutamos en el gráfico. Al crear o eliminar un objeto, o cambiar las propiedades del mismo, las entradas sobre los eventos correspondientes se mostrarán en el diario del terminal de cliente.


Por ahora, solo son entradas en el diario, pero...

¿Qué es lo próximo?

En el próximo artículo, crearemos los manejadores de eventos de los objetos para cada gráfico abierto y enviaremos estos eventos al gráfico del programa de control para que este programa pueda procesarlos por completo.

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 83): Clase de objeto gráfico abstracto estándar
Gráficos en la biblioteca DoEasy (Parte 84): Clases herederas del objeto gráfico abstracto estándar
Gráficos en la biblioteca DoEasy (Parte 85): Colección de objetos gráficos - añadiendo los objetos nuevamente creados

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

Archivos adjuntos |
MQL5.zip (4153.33 KB)
Use los canales y chats grupales de MQL5.community Use los canales y chats grupales de MQL5.community
En el sitio web MQL5.com podrá encontrar tráders de todo el mundo: estos publican artículos, códigos gratuitos y productos en el Mercado, ejecutan trabajos en freelance y codifican señales comerciales. Podrá relacionarse con ellos en el foro, los chats de tráders y los canales de MetaTrader.
Cómo ser un mejor programador (parte 07): Apuntes para convertirse en un desarrollador freelance exitoso Cómo ser un mejor programador (parte 07): Apuntes para convertirse en un desarrollador freelance exitoso
¿Desea convertirse en un desarrollador freelance de éxito en MQL5? Si la respuesta es sí, este artículo es justo para usted.
Gráficos en la biblioteca DoEasy (Parte 87): Colección de objetos gráficos - control de la modificación de propiedades en todos los gráficos abiertos Gráficos en la biblioteca DoEasy (Parte 87): Colección de objetos gráficos - control de la modificación de propiedades en todos los gráficos abiertos
En este artículo, continuaremos trabajando en el monitoreo de los eventos de los objetos gráficos estándar y crearemos una funcionalidad que nos permitirá controlar los cambios en las propiedades de los objetos gráficos colocados en cualquier gráfico abierto en el terminal.
Programamos una red neuronal profunda desde cero usando el lenguaje MQL Programamos una red neuronal profunda desde cero usando el lenguaje MQL
El objetivo de este artículo es enseñar al lector cómo crear una red neuronal profunda desde cero utilizando el lenguaje MQL4/5.