English Русский 中文 Deutsch 日本語 Português
Gráficos en la biblioteca DoEasy (Parte 94): Objetos gráficos compuestos, desplazamiento y eliminación

Gráficos en la biblioteca DoEasy (Parte 94): Objetos gráficos compuestos, desplazamiento y eliminación

MetaTrader 5Ejemplos | 17 marzo 2022, 09:45
542 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

En el último artículo, comenzamos a crear objetos gráficos compuestos. Para definir un objeto gráfico compuesto, hemos introducido un nuevo tipo de elemento gráfico: el objeto gráfico estándar extendido. Todos los objetos gráficos involucrados en la creación de un objeto gráfico compuesto tendrán este tipo.
Por el momento, no implementaremos ninguna clase para crear ciertos objetos gráficos compuestos: solo crearemos las funcionalidades que posteriormente nos permitirán crear las clases de los objetos gráficos compuestos predefinidos, lo cual, obviamente, no excluye la posibilidad de crear nuestros propios objetos gráficos compuestos tanto programáticamente como "sobre la marcha", directamente en el gráfico.


Vamos a dividir el trabajo sobre esta funcionalidad en partes. Primero, implementaremos todas las herramientas necesarias para crear y administrar los objetos gráficos compuestos, y después crearemos las clases predefinidas de dichos objetos (sin embargo, aquí todo depende de la imaginación y las necesidades de cada uno, y por lo tanto, la creación de tales objetos resulta algo muy individual, así que las clases de objetos gráficos compuestos predefinidos supondrán solo un ejemplo), procediendo finalmente a implementar la funcionalidad que nos permitirá crear de forma manual objetos gráficos compuestos visualmente, en tiempo real y directamente en el gráfico.

En general, hoy vamos a mejorar lo que ya creamos en el último artículo. Vamos a establecer los puntos con las coordenadas de anclaje en los objetos subordinados y a obtener dichas coordenadas. Asimismo, podremos a prueba el desplazamiento de un objeto básico al que se adjuntan los subordinados (y veremos que no todo es tan simple, ya que se requiere el desarrollo de la funcionalidad necesaria para desplazar los puntos de las coordenadas del objeto compuesto a un nivel más complejo que simplemente monitorear un evento del objeto), y también la creación de la funcionalidad necesaria para eliminar un objeto gráfico compuesto.

Al mover los puntos de coordenadas de un objeto gráfico, el evento CHARTEVENT_OBJECT_DRAG aparecerá solo cuando soltemos el botón del ratón. Por consiguiente, si solo monitoreamos este evento, en el momento en que movamos el objeto gráfico básico (hasta que soltemos el botón del ratón), todos los objetos adjuntos permanecerán en sus lugares originales, y solo después de soltar el botón y que aparezca el evento, los objetos adjuntos se moverán a sus puntos de anclaje hacia el objeto subordinado. Esto significa que deberemos monitorear el movimiento del ratón con el botón izquierdo presionado. Y, al mismo tiempo, también deberemos saber que se ha presionado el botón en el objeto gráfico básico, en el área de su punto de anclaje al gráfico (o el central), y mientras movemos el objeto, deberemos calcular de nuevo la ubicación de los puntos de coordenadas y los puntos de anclaje de sus objetos subordinados.
Pero el evento CHARTEVENT_OBJECT_DRAG también debe manejarse al final de dicho movimiento, para fijar las coordenadas finales del objeto básico y recalcular las coordenadas de todos los objetos gráficos subordinados vinculados.

Hoy solo procesaremos el evento CHARTEVENT_OBJECT_DRAG y recalcularemos las coordenadas de los objetos vinculados según la nueva ubicación de las coordenadas del objeto básico. La eliminación de objetos gráficos compuestos se realizará al borrar el básico. Cuando llegue un evento de este tipo, eliminaremos todos los objetos gráficos vinculados. Por ahora, lo haremos más sencillo deshabilitando la capacidad de seleccionar todos los objetos gráficos vinculados a uno básico, con un clic de ratón. Así, para eliminar un objeto gráfico compuesto, será necesario seleccionar el básico y eliminarlo. No podremos seleccionar ninguno de los objetos vinculados al gráfico con el ratón, y esta será la primera forma y la más sencilla de proteger el objeto gráfico compuesto de su destrucción.
Sin embargo, sí que será posible abrir la lista de objetos (Ctrl+B), seleccionar las propiedades de cualquier objeto vinculado y establecer la opción de selección para él, o eliminarlo inmediatamente en la ventana de la lista de objetos gráficos. Por lo tanto, procesaremos en consecuencia la mencionada destrucción (intencional) de los objetos gráficos compuestos: al eliminar cualquiera de los objetos gráficos vinculados al básico, eliminaremos todos los objetos que participan en la construcción del objeto gráfico compuesto. Es decir, haremos que al eliminar cualquier objeto que forme parte de un objeto compuesto, todo este objeto se elimine por completo. Por otra parte, más tarde crearemos la funcionalidad necesaria para separar realmente un objeto vinculado de su objeto básico.


Mejorando las clases de la biblioteca

Como de costumbre, primero escribiremos todos los mensajes nuevos de la biblioteca.
En el archivo \MQL5\Include\DoEasy\Data.mqh introducimos los índices de los nuevos mensajes:

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Failed to get the list of newly added objects
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list
   MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list
   MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART,        // Failed to remove a graphical object from the chart
   MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_DEL_LIST,          // Failed to set a graphical object to the list of removed objects
   MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_RNM_LIST,          // Failed to set a graphical object to the list of renamed objects

...

//--- CLinkedPivotPoint
   MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X,                // Not a single pivot point is set for the object along the X axis
   MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y,                // Not a single pivot point is set for the object along the Y axis
   MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE,             // The object is not attached to the basic graphical object
   MSG_GRAPH_OBJ_EXT_FAILED_CREATE_PP_DATA_OBJ,       // Failed to create a data object for the X and Y pivot points
   MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X,            // Number of base object pivot points for calculating the X coordinate: 
   MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y,            // Number of base object pivot points for calculating the Y coordinate: 
   
  };
//+------------------------------------------------------------------+

y los mensajes de texto que se corresponden con los índices nuevamente añadidos:

//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},
   {"Не удалось удалить графический объект из списка","Failed to delete graphic object from the list"},
   {"Не удалось удалить графический объект с графика","Failed to delete graphic object from the chart"},
   {"Не удалось поместить графический объект в список удалённых объектов","Failed to place graphic object in the list of deleted objects"},
   {"Не удалось поместить графический объект в список переименованных объектов","Failed to place graphic object in the list of renamed objects"},

...

//--- CLinkedPivotPoint
   {"Для объекта не установлено ни одной опорной точки по оси X","The object does not have any pivot points set along the x-axis"},
   {"Для объекта не установлено ни одной опорной точки по оси Y","The object does not have any pivot points set along the y-axis"},
   {"Объект не привязан к базовому графическому объекту","The object is not attached to the base graphical object"},
   {"Не удалось создать объект данных опорной точки X и Y.","Failed to create X and Y reference point data object"},
   {"Количество опорных точек базового объекта для расчёта координаты X: ","Number of reference points of the base object to set the X coordinate: "},
   {"Количество опорных точек базового объекта для расчёта координаты Y: ","Number of reference points of the base object to set the Y coordinate: "},
   
  };
//+---------------------------------------------------------------------+


En todos los archivos de las clases herederas del objeto gráfico estándar abstracto almacenado en \MQL5\Include\DoEasy\Objects\Graph\Standard\, en los métodos que muestran la descripción breve del objeto, realizaremos la siguiente modificación:

//+------------------------------------------------------------------+
//| Display a short description of the object in the journal         |
//+------------------------------------------------------------------+
void CGStdArrowBuyObj::PrintShort(const bool dash=false,const bool symbol=false)
  {
   ::Print
     (
      (dash ? " - " : "")+this.Header(symbol)," \"",CGBaseObj::Name(),"\": ID ",(string)this.GetProperty(GRAPH_OBJ_PROP_ID,0),
      ", ",::TimeToString(CGBaseObj::TimeCreate(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
     );
  }
//+------------------------------------------------------------------+

Como todos los métodos virtuales que muestran el nombre breve del objeto deben tener exactamente el mismo conjunto de parámetros de entrada que el método de la clase principal, en estos métodos teníamos (y aún tenemos) parámetros de entrada sin usar. Uno de ellos es la muestra de un guión antes del texto retornado por el método que ahora hemos implementado. Si transmitimos al método la banderadash con el valor true, se escribirá un guión antes de mostrar el nombre breve del objeto (más adelante en el artículo, veremos un ejemplo de dicha representación). Esto resulta cómodo si necesitamos escribir un encabezado y enumerar los nombres de los objetos debajo de él.
Dichos cambios (absolutamente idénticos a los analizados) ya los hemos realizado en todos los archivos de las clase herederas de la clase del objeto gráfico estándar abstracto. Podemos encontrarlos en los archivos adjuntos al artículo.

Todos los cambios principales que analizaremos hoy están relacionados con el archivo que contiene la clase de objeto gráfico estándar abstracto \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh.

En la clase de datos del punto de pivote del objeto vinculado que se encuentra en el propio archivo, la matriz estaba declarada con un nombre que incluía la indicación de la coordenada: m_property_x[][2]. Así quedó después de experimentar con dos matrices de la misma clase para las coordenadas X e Y. Más tarde, abandonamos esta idea y dejamos incorrecto el nombre de la matriz. Por consiguiente, le hemos cambiado el nombre a m_property[][2].

Hemos añadido a la sección pública de la clase un método para mostrar el nombre del eje cuyas coordenadas se almacenan en la clase, un método para retornar la propiedad y un modificador de la propiedad almacenada en la matriz, así como un método que retorna la descripción del número de puntos de anclaje del objeto básico, según el cual se calcula el punto de coordenadas al que se vincula el objeto gráfico subordinado; este método resulta útil durante la depuración:

//+------------------------------------------------------------------+
//| Class of the dependent object pivot point data                   |
//+------------------------------------------------------------------+
class CPivotPointData
  {
private:
   bool              m_axis_x;
   int               m_property[][2];
public:
//--- (1) Set and (2) return the flag indicating that the pivot point belongs to the X coordinate
   void              SetAxisX(const bool axis_x)         { this.m_axis_x=axis_x;             }
   bool              IsAxisX(void)                 const { return this.m_axis_x;             }
   string            AxisDescription(void)         const { return(this.m_axis_x ? "X" : "Y");}
//--- Return the number of base object pivot points for calculating the coordinate of the dependent one
   int               GetBasePivotsNum(void)  const { return ::ArrayRange(this.m_property,0);  }
//--- Add the new pivot point of the base object for calculating the coordinate of a dependent one
   bool              AddNewBasePivotPoint(const string source,const int pivot_prop,const int pivot_num)
                       {
                        //--- Get the array size 
                        int pivot_index=this.GetBasePivotsNum();
                        //--- if failed to increase the array size, inform of that and return 'false'
                        if(::ArrayResize(this.m_property,pivot_index+1)!=pivot_index+1)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
                           return false;
                          }
                        //--- Return the result of changing the values of a newly added new array dimension
                        return this.ChangeBasePivotPoint(source,pivot_index,pivot_prop,pivot_num);
                       }
//--- Change the specified pivot point of the base object for calculating the coordinate of a dependent one
   bool              ChangeBasePivotPoint(const string source,const int pivot_index,const int pivot_prop,const int pivot_num)
                       {
                        //--- Get the array size. If it is zero, inform of that and return 'false'
                        int n=this.GetBasePivotsNum();
                        if(n==0)
                          {
                           CMessage::ToLog(source,(this.IsAxisX() ? MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X : MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y));
                           return false;
                          }
                        //--- If the specified index goes beyond the array range, inform of that and return 'false'
                        if(pivot_index<0 || pivot_index>n-1)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
                           return false;
                          }
                        //--- Set the values, passed to the method, in the specified array cells by index
                        this.m_property[pivot_index][0]=pivot_prop;
                        this.m_property[pivot_index][1]=pivot_num;
                        return true;
                       }

//--- Return(1) a property and (2) a modifier of the property from the array
   int               GetProperty(const string source,const int index)     const
                       {
                        if(index<0 || index>this.GetBasePivotsNum()-1)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
                           return WRONG_VALUE;
                          }
                        return this.m_property[index][0];   
                       }
   int               GetPropertyModifier(const string source,const int index)  const
                       {
                        if(index<0 || index>this.GetBasePivotsNum()-1)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
                           return WRONG_VALUE;
                          }
                        return this.m_property[index][1];   
                       }

//--- Return the description of the number of pivot points for setting the coordinate
   string            GetBasePivotsNumDescription(void) const
                       {
                        return CMessage::Text(IsAxisX() ? MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X : MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y)+
                               (string)this.GetBasePivotsNum();
                       }

//--- Constructor/destructor
                     CPivotPointData(void){;}
                    ~CPivotPointData(void){;}
  };
//+------------------------------------------------------------------+

Todos los métodos son muy sencillos, y su lógica debería resultar clara tras analizar el código, así que el lector podrá estudiarlos por su cuenta.


En la clase de datos de los puntos de pivote X e Y del objeto compuesto, añadimos los métodos que retornan el resultado de la llamada a los nuevos métodos que acabamos de analizar:

//+------------------------------------------------------------------+
//| Class of data on X and Y pivot points of a composite object      |
//+------------------------------------------------------------------+
class CPivotPointXY : public CObject
  {
private:
   CPivotPointData   m_pivot_point_x;            // X coordinate pivot point
   CPivotPointData   m_pivot_point_y;            // Y coordinate pivot point
public:
//--- Return the pointer to the (1) X and (2) Y coordinate pivot point data object
   CPivotPointData  *GetPivotPointDataX(void)      { return &this.m_pivot_point_x;                    }
   CPivotPointData  *GetPivotPointDataY(void)      { return &this.m_pivot_point_y;                    }
//--- Return the number of base object pivot points for calculating the (1) X and (2) Y coordinate
   int               GetBasePivotsNumX(void) const { return this.m_pivot_point_x.GetBasePivotsNum();  }
   int               GetBasePivotsNumY(void) const { return this.m_pivot_point_y.GetBasePivotsNum();  }
//--- Add the new pivot point of the base object for calculating the X coordinate of a dependent one
   bool              AddNewBasePivotPointX(const int pivot_prop,const int pivot_num)
                       {
                        return this.m_pivot_point_x.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num);
                       }
//--- Add the new pivot point of the base object for calculating the Y coordinate of a dependent one
   bool              AddNewBasePivotPointY(const int pivot_prop,const int pivot_num)
                       {
                        return this.m_pivot_point_y.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num);
                       }
//--- Add new pivot points of the base object for calculating the X and Y coordinates of a dependent one
   bool              AddNewBasePivotPointXY(const int pivot_prop_x,const int pivot_num_x,
                                            const int pivot_prop_y,const int pivot_num_y)
                       {
                        bool res=true;
                        res &=this.m_pivot_point_x.AddNewBasePivotPoint(DFUN,pivot_prop_x,pivot_num_x);
                        res &=this.m_pivot_point_y.AddNewBasePivotPoint(DFUN,pivot_prop_y,pivot_num_y);
                        return res;
                       }
//--- Change the specified pivot point of the base object for calculating the X coordinate of a dependent one
   bool              ChangeBasePivotPointX(const int pivot_index,const int pivot_prop,const int pivot_num)
                       {
                        return this.m_pivot_point_x.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num);
                       }
//--- Change the specified pivot point of the base object for calculating the Y coordinate of a dependent one
   bool              ChangeBasePivotPointY(const int pivot_index,const int pivot_prop,const int pivot_num)
                       {
                        return this.m_pivot_point_y.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num);
                       }
//--- Change specified pivot points of the base object for calculating the X and Y coordinates
   bool              ChangeBasePivotPointXY(const int pivot_index,
                                            const int pivot_prop_x,const int pivot_num_x,
                                            const int pivot_prop_y,const int pivot_num_y)
                       {
                        bool res=true;
                        res &=this.m_pivot_point_x.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_x,pivot_num_x);
                        res &=this.m_pivot_point_y.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_y,pivot_num_y);
                        return res;
                       }
//--- Return (1) the property for calculating the X coordinate and (2) the X coordinate property modifier
   int               GetPropertyX(const string source,const int index) const
                       {
                        return this.m_pivot_point_x.GetProperty(source,index);
                       }
   int               GetPropertyModifierX(const string source,const int index) const
                       {
                       return this.m_pivot_point_x.GetPropertyModifier(source,index);
                       }
//--- Return (1) the property for calculating the Y coordinate and (2) the Y coordinate property modifier
   int               GetPropertyY(const string source,const int index) const
                       {
                        return this.m_pivot_point_y.GetProperty(source,index);
                       }
   int               GetPropertyModifierY(const string source,const int index) const
                       {
                       return this.m_pivot_point_y.GetPropertyModifier(source,index);
                       }
//--- Return the description of the number of pivot points for setting the (1) X and (2) Y coordinates
   string            GetBasePivotsNumXDescription(void) const
                       {
                        return this.m_pivot_point_x.GetBasePivotsNumDescription();
                       }
   string            GetBasePivotsNumYDescription(void) const
                       {
                        return this.m_pivot_point_y.GetBasePivotsNumDescription();
                       }
                       
//--- Constructor/destructor
                     CPivotPointXY(void){ this.m_pivot_point_x.SetAxisX(true); this.m_pivot_point_y.SetAxisX(false); }
                    ~CPivotPointXY(void){;}
  };  
//+------------------------------------------------------------------+

Cada uno de estos métodos retorna el resultado de la llamada al método homónimo de la clase correspondiente que almacena los datos sobre las coordenadas en el eje X o Y.
Los nombres de los métodos ahora especifican la coordenada exacta cuyos datos retorna el método, por ejemplo, GetPropertyX o GetPropertyY.

Hemos mejorado considerablemente la clase de los datos vinculados de los puntos de pivote del objeto compuesto, principalmente en cuanto a los nombres de los métodos. Lo único es que al realizar la depuración, los nombres de los métodos que no eran del todo inequívocos, comenzaron a provocar cierta confusión. Por lo tanto, les cambiamos el nombre, para mayor claridad. Por ejemplo, el nombre del método CreateNewLinkedPivotPoint(), que añade un nuevo punto de anclaje del objeto subordinado en las coordenadas X e Y, resultaba confuso, ya que PivotPoint es el punto de anclaje utilizado para establecer la coordenada X o Y del objeto básico para calcular la coordenada a la que se fijará el objeto subordinado. Y el punto de coordenadas en sí se puede calcular partiendo de varios PivotPoint. Por consiguiente, hemos cambiado el nombre del método a CreateNewLinkedCoord(), que indica directamente la adición de un nuevo punto de coordenadas.

Para abreviar el código de los métodos, se utilizaban operadores ternarios. Por ejemplo, el método

   CPivotPointData  *GetBasePivotPointDataX(const int index) const
                       {
                        CPivotPointXY *obj=this.GetLinkedPivotPointXY(index);
                        if(obj==NULL)
                           return NULL;
                        return obj.GetPivotPointDataX();
                       }

ahora tiene el aspecto siguiente:

   CPivotPointData  *GetBasePivotPointDataX(const int index_coord_point) const
                       {
                        CPivotPointXY *obj=this.GetLinkedCoord(index_coord_point);
                        return(obj!=NULL ? obj.GetPivotPointDataX() : NULL);
                       }

que es exactamente lo mismo, pero más corto.

Además, a la sección pública de la clase se añadían métodos que retornaban el resultado de la llamada a los métodos de clase homónimos correspondientes a la coordenada necesaria, lo cual facilitaba finalmente la obtención de los datos requeridos:

//--- Add the new pivot point of the base object for calculating the X coordinate for a specified anchor point of the dependent one
   bool              AddNewBasePivotPointX(const int index_coord_point,const int pivot_prop,const int pivot_num)
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
                        return(obj!=NULL ? obj.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num) : false);
                       }
//--- Add the new pivot point of the base object for calculating the Y coordinate for a specified anchor point of the dependent one
   bool              AddNewBasePivotPointY(const int index_coord_point,const int pivot_prop,const int pivot_num)
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
                        return(obj!=NULL ? obj.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num) : false);
                       }
//--- Add the new pivot points of the base object for calculating the X and Y coordinates for a specified anchor point of the dependent one
   bool              AddNewBasePivotPointXY(const int index_coord_point,
                                            const int pivot_prop_x,const int pivot_num_x,
                                            const int pivot_prop_y,const int pivot_num_y)
                       {
                        CPivotPointData *objx=this.GetBasePivotPointDataX(index_coord_point);
                        if(objx==NULL)
                           return false;
                        CPivotPointData *objy=this.GetBasePivotPointDataY(index_coord_point);
                        if(objy==NULL)
                           return false;
                        bool res=true;
                        res &=objx.AddNewBasePivotPoint(DFUN,pivot_prop_x,pivot_num_x);
                        res &=objy.AddNewBasePivotPoint(DFUN,pivot_prop_y,pivot_num_y);
                        return res;
                       }

//--- Change the specified pivot point of the base object for calculating the X coordinate for a specified anchor point of the dependent one
   bool              ChangeBasePivotPointX(const int index_coord_point,const int pivot_index,const int pivot_prop,const int pivot_num)
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
                        return(obj!=NULL ? obj.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num) : false);
                       }
//--- Change the specified pivot point of the base object for calculating the Y coordinate for a specified anchor point of the dependent one
   bool              ChangeBasePivotPointY(const int index_coord_point,const int pivot_index,const int pivot_prop,const int pivot_num)
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
                        return(obj!=NULL ? obj.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num) : false);
                       }
//--- Change the specified pivot points of the base object for calculating the X and Y coordinates for a specified anchor point
   bool              ChangeBasePivotPointXY(const int index_coord_point,
                                            const int pivot_index,
                                            const int pivot_prop_x,const int pivot_num_x,
                                            const int pivot_prop_y,const int pivot_num_y)
                       {
                        CPivotPointData *objx=this.GetBasePivotPointDataX(index_coord_point);
                        if(objx==NULL)
                           return false;
                        CPivotPointData *objy=this.GetBasePivotPointDataY(index_coord_point);
                        if(objy==NULL)
                           return false;
                        bool res=true;
                        res &=objx.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_x,pivot_num_x);
                        res &=objy.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_y,pivot_num_y);
                        return res;
                       }

//--- Return the property for calculating the X coordinate for a specified anchor point
   int               GetPropertyX(const int index_coord_point,const int index) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
                        return(obj!=NULL ? obj.GetProperty(DFUN,index) : WRONG_VALUE);
                       }
//--- Return the modifier of the X coordinate property for a specified anchor point
   int               GetPropertyModifierX(const int index_coord_point,const int index) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
                        return(obj!=NULL ? obj.GetPropertyModifier(DFUN,index) : WRONG_VALUE);
                       }

//--- Return the property for calculating the Y coordinate for a specified anchor point
   int               GetPropertyY(const int index_coord_point,const int index) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
                        return(obj!=NULL ? obj.GetProperty(DFUN,index) : WRONG_VALUE);
                       }
//--- Return the modifier of the Y coordinate property for a specified anchor point
   int               GetPropertyModifierY(const int index_coord_point,const int index) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
                       return(obj!=NULL ? obj.GetPropertyModifier(DFUN,index) : WRONG_VALUE);
                       }

//--- Return the description of the number of base object pivot points for calculating the X coordinate by index
   string            GetBasePivotsNumXDescription(const int index_coord_point) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
                        return(obj!=NULL ? obj.GetBasePivotsNumDescription() : "WRONG_VALUE");
                       }
//--- Return the description of the number of base object pivot points for calculating the Y coordinate by index
   string            GetBasePivotsNumYDescription(const int index_coord_point) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
                        return(obj!=NULL ? obj.GetBasePivotsNumDescription() : "WRONG_VALUE");
                       }

//--- Constructor/destructor
                     CLinkedPivotPoint(void){;}
                    ~CLinkedPivotPoint(void){;}
  };  
//+------------------------------------------------------------------+


En los métodos que retornan la descripción de una propiedad, en la clase del objeto gráfico estándar abstracto, añadimos el índice de la propiedad necesaria:

//--- Return the flag of the object supporting this property
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) { return true;             }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property)  { return true;             }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING property)  { return true;             }

//--- Get description of (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_INTEGER property,const int index=0);
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_DOUBLE property,const int index=0);
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_STRING property,const int index=0);

//--- Return the description of the graphical object anchor point position
   virtual string    AnchorDescription(void)                const { return (string)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,0); }

Esto nos permitirá en el futuro hacer que los métodos muestren no una lista completa con todas las propiedades de un objeto gráfico, sino solo la propiedad necesaria.

Vamos a aclarar este punto. Por ejemplo, una línea de tendencia tiene dos puntos de anclaje en el gráfico. Para especificar el tiempo (coordenada X) o el precio (coordenada Y), el modificador de propiedad (índice en los métodos anteriores) se usa para indicar de qué punto, el izquierdo o el derecho, necesitamos obtener las coordenadas. Por el momento, el método muestra una lista completa de todas las propiedades: escribe el encabezado y, debajo, los valores de ambos puntos de anclaje:

OnChartEvent: Time coordinate: 
 - Pivot point 0: 2022.01.24 20:59
 - Pivot point 1: 2022.01.26 22:00

...

OnChartEvent: Price coordinate: 
 - Pivot point 0: 1.13284
 - Pivot point 1: 1.11846

No obstante, no existe una forma fácil de mostrar algún punto concreto: debemos escribir el nombre de la propiedad y su valor por nosotros mismos. Posteriormente, usando los índices introducidos, implementaremos una forma sencilla de generar el nombre y el valor del punto de anclaje necesario. Mientras tanto, para no corregir muchos errores, asignaremos valores por defecto a los índices: así resultará más fácil introducir cambios después, con solo eliminar el valor predeterminado e introducir la gestión de errores necesaria para mostrar una descripción completa (tal como está ahora), o bien una selectiva para un punto de anclaje.

En la sección pública, añadimos un método que retorna el número de objetos vinculados al objeto básico y corrije los nombres de los métodos:

//--- Return (1) the list of dependent objects, (2) dependent graphical object by index and (3) the number of dependent objects
   CArrayObj        *GetListDependentObj(void)        { return &this.m_list;           }
   CGStdGraphObj    *GetDependentObj(const int index) { return this.m_list.At(index);  }
   int               GetNumDependentObj(void)         { return this.m_list.Total();    }
//--- Return the name of the dependent object by index
   string            NameDependent(const int index);
//--- Add the dependent graphical object to the list
   bool              AddDependentObj(CGStdGraphObj *obj);

//--- Return the object of data on pivot points
   CLinkedPivotPoint*GetLinkedPivotPoint(void)        { return &this.m_linked_pivots;  }
   
//--- Add a new pivot point for calculating X and Y coordinates to the current object
   bool              AddNewLinkedCoord(const int pivot_prop_x,const int pivot_num_x,const int pivot_prop_y,const int pivot_num_y)
                       {
                        //--- If the current object is not bound to the base one, display the appropriate message and return 'false'
                        if(this.BaseObjectID()==0)
                          {
                           CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE);
                           return false;
                          }
                        //--- Return the result of adding a new connected pivot point from the CLinkedPivotPoint class to the current object
                        return this.m_linked_pivots.CreateNewLinkedCoord(pivot_prop_x,pivot_num_x,pivot_prop_y,pivot_num_y);
                       }
//--- Add a new pivot point for calculating X and Y coordinates to the specified object
   bool              AddNewLinkedCoord(CGStdGraphObj *obj,const int pivot_prop_x,const int pivot_num_x,const int pivot_prop_y,const int pivot_num_y)
                       {
                        //--- If the current object is not an extended one, display the appropriate message and return 'false'
                        if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
                          {
                           CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_NOT_EXT_OBJ);
                           return false;
                          }
                        //--- If a zero pointer to the object is passed, return 'false'
                        if(obj==NULL)
                           return false;
                        //--- Return the result of adding a new connected pivot point from the CLinkedPivotPoint class to the specified object
                        return obj.AddNewLinkedCoord(pivot_prop_x,pivot_num_x,pivot_prop_y,pivot_num_y);
                       }


A continuación, renombramos los métodos GetLinkedPivotsNum() y declaramos nuevos métodos privados para establecer las coordenadas para los objetos gráficos subordinados:

//--- Return the number of base object pivot points for calculating the (1) X and (2) Y coordinate in the current object
   int               GetBasePivotsNumX(const int index)           { return this.m_linked_pivots.GetBasePivotsNumX(index);  }
   int               GetBasePivotsNumY(const int index)           { return this.m_linked_pivots.GetBasePivotsNumY(index);  }
//--- Return the number of base object pivot points for calculating the (1) X and (2) Y coordinate in the specified object
   int               GetBasePivotsNumX(CGStdGraphObj *obj,const int index) const { return(obj!=NULL ? obj.GetBasePivotsNumX(index): 0); }
   int               GetBasePivotsNumY(CGStdGraphObj *obj,const int index) const { return(obj!=NULL ? obj.GetBasePivotsNumY(index): 0); }
//--- Return the number of base object pivot points for calculating the coordinates in the (1) current (2) object
   int               GetLinkedCoordsNum(void)               const { return this.m_linked_pivots.GetNumLinkedCoords();      }
   int               GetLinkedPivotsNum(CGStdGraphObj *obj) const { return(obj!=NULL ? obj.GetLinkedCoordsNum() : 0);      }
   
private:
//--- Set the X coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordXFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the Y coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordYFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the (1) integer, (2) real and (3) string property to the specified subordinate property
   void              SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier);
   void              SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier);
   void              SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier);

public:
//--- Default constructor
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; }
//--- Destructor
                    ~CGStdGraphObj()
                       {
                        if(this.Prop!=NULL)
                           delete this.Prop;
                       }
protected:
//--- Protected parametric constructor
                     CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                                   const ENUM_GRAPH_ELEMENT_TYPE elm_type,
                                   const ENUM_GRAPH_OBJ_BELONG belong,
                                   const ENUM_GRAPH_OBJ_SPECIES species,
                                   const long chart_id, const int pivots,
                                   const string name);
                     
public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+


En el método que añade un objeto gráfico estándar subordinado a la lista de objetos vinculados al objeto básico, añadimos el establecimiento de la propiedad:

//+------------------------------------------------------------------+
//| Add a subordinate standard graphical object to the list          |
//+------------------------------------------------------------------+
bool CGStdGraphObj::AddDependentObj(CGStdGraphObj *obj)
  {
   //--- If the current object is not an extended one, inform of that and return 'false'
   if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
     {
      CMessage::ToLog(MSG_GRAPH_OBJ_NOT_EXT_OBJ);
      return false;
     }
   //--- If failed to add the pointer to the passed object into the list, inform of that and return 'false'
   if(!this.m_list.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_DEP_EXT_OBJ_TO_LIST);
      return false;
     }
   //--- Object added to the list - set its number in the list,
   //--- name and ID of the current object as the base one,
   //--- set the flags of object availability and selection
   //--- and the graphical element type - standard extended graphical object
   obj.SetNumber(this.m_list.Total()-1);
   obj.SetBaseName(this.Name());
   obj.SetBaseObjectID(this.ObjectID());
   obj.SetFlagSelected(false,false);
   obj.SetFlagSelectable(false,false);
   obj.SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED);
   return true;
  }
//+------------------------------------------------------------------+

Después, establecemos la bandera de selección de objetos en false, de forma que el objeto recién añadido no se seleccione, y prohibimos de inmediato la disponibilidad del objeto, estableciendo también la bandera correspondiente como false. Luego establecemos el tipo de objeto como "objeto gráfico estándar extendido". Por lo tanto, no podremos seleccionar estos objetos en el gráfico con el ratón: estarán disponibles en la lista de objetos gráficos estándar extendidos para que puedan ser seleccionados de forma programática según el tipo y el nombre del objeto gráfico básico.

Método que asigna la coordenada X de la propiedad especificada del objeto básico al objeto subordinado indicado:

//+-------------------------------------------------------------------+
//|Set the X coordinate from the specified property of the base object|
//| to the specified subordinate object                               |
//+-------------------------------------------------------------------+
void CGStdGraphObj::SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to)
  {
   int prop=WRONG_VALUE;
   switch(obj.TypeGraphObject())
     {
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
        prop=GRAPH_OBJ_PROP_XDISTANCE; 
        break;
      default:
        prop=GRAPH_OBJ_PROP_TIME;
        break;
     }
   if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL)
     {
      this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to);
     }
   else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL)
     {
      //--- Assigning a real property value to the integer value of the X coordinate is a bad idea unless you know what you are doing
      this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to);
     }
   else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL+GRAPH_OBJ_PROP_STRING_TOTAL)
     {
      //--- Assigning a string property value to the integer value of the X coordinate is a bad idea unless you know what you are doing
      this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to);
     }
  }
//+------------------------------------------------------------------+

Según el tipo de objeto, seleccionamos la propiedad deseada. Podría tratarse de la coordenada temporal o la coordenada en píxeles de la pantalla. A continuación, establecemos en la propiedad de las coordenadas del objeto transmitido al método según el puntero la propiedad cuyos datos se han transmitido a los parámetros de entrada del método, a saber, la propiedad en sí y su modificador, y especificamos el modificador de la propiedad que se ha establecido en el objeto en sí. Como resultado, las coordenadas necesarias del punto de anclaje cuyos parámetros hemos transmitido al método, se establecerán en el objeto gráfico.

Método que asigna la coordenada Y de la propiedad especificada del objeto básico al objeto subordinado indicado:

//+-------------------------------------------------------------------+
//|Set the Y coordinate from the specified property of the base object|
//| to the specified subordinate object                               |
//+-------------------------------------------------------------------+
void CGStdGraphObj::SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to)
  {
   int prop=WRONG_VALUE;
   switch(obj.TypeGraphObject())
     {
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
        prop=GRAPH_OBJ_PROP_YDISTANCE;
        break;
      default:
        prop=GRAPH_OBJ_PROP_PRICE;
        break;
     }
   if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL)
     {
      if(prop==GRAPH_OBJ_PROP_YDISTANCE)
         this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to);
      else
         //--- Assigning an integer property value to the real value of the Y coordinate is allowed only if you know what you are doing
         this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to);
     }
   else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL)
     {
      if(prop==GRAPH_OBJ_PROP_YDISTANCE)
         //--- Assigning a real property value to the integer value of the Y coordinate is a bad idea unless you know what you are doing
         this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to);
      else
         this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to);
     }
   else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL+GRAPH_OBJ_PROP_STRING_TOTAL)
     {
      //--- Assigning a string property value to the integer or real value of the Y coordinate is a bad idea unless you know what you are doing
      if(prop==GRAPH_OBJ_PROP_YDISTANCE)
         this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to);
      else
         this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,(double)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to);
     }
  }
//+------------------------------------------------------------------+

Aquí, casi todo resulta igual que en el método que establece la coordenada X, pero hay una excepción: la coordenada X siempre es un número entero, ya sea el tiempo o el número de píxeles, mientras que la coordenada Y puede ser un número entero (número de píxeles) o real (precio). Por lo tanto, aquí comprobaremos qué propiedad deberíamos establecer eventualmente y, dependiendo de ello, estableceremos el valor o bien en la propiedad entera del objeto, o bien en la propiedad real.

Método que establece una propiedad de número entero para el objeto subordinado especificado:

//+------------------------------------------------------------------+
//| Set the integer property                                         |
//| to the specified dependent object                                |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier)
  {
   if(obj==NULL || obj.BaseObjectID()==0)
      return;
   switch(prop)
     {
      case GRAPH_OBJ_PROP_TIMEFRAMES            :  obj.SetVisibleOnTimeframes((int)value,false);         break;   // Object visibility on timeframes
      case GRAPH_OBJ_PROP_BACK                  :  obj.SetFlagBack(value,false);                         break;   // Background object
      case GRAPH_OBJ_PROP_ZORDER                :  obj.SetZorder(value,false);                           break;   // Priority of a graphical object for receiving the event of clicking on a chart
      case GRAPH_OBJ_PROP_HIDDEN                :  obj.SetFlagHidden(value,false);                       break;   // Disable displaying the name of a graphical object in the terminal object list
      case GRAPH_OBJ_PROP_SELECTED              :  obj.SetFlagSelected(value,false);                     break;   // Object selection
      case GRAPH_OBJ_PROP_SELECTABLE            :  obj.SetFlagSelectable(value,false);                   break;   // Object availability
      case GRAPH_OBJ_PROP_TIME                  :  obj.SetTime(value,modifier);                          break;   // Time coordinate
      case GRAPH_OBJ_PROP_COLOR                 :  obj.SetColor((color)value);                           break;   // Color
      case GRAPH_OBJ_PROP_STYLE                 :  obj.SetStyle((ENUM_LINE_STYLE)value);                 break;   // Style
      case GRAPH_OBJ_PROP_WIDTH                 :  obj.SetWidth((int)value);                             break;   // Line width
      case GRAPH_OBJ_PROP_FILL                  :  obj.SetFlagFill(value);                               break;   // Filling an object with color
      case GRAPH_OBJ_PROP_READONLY              :  obj.SetFlagReadOnly(value);                           break;   // Ability to edit text in the Edit object
      case GRAPH_OBJ_PROP_LEVELS                :  obj.SetLevels((int)value);                            break;   // Number of levels
      case GRAPH_OBJ_PROP_LEVELCOLOR            :  obj.SetLevelColor((color)value,modifier);             break;   // Level line color
      case GRAPH_OBJ_PROP_LEVELSTYLE            :  obj.SetLevelStyle((ENUM_LINE_STYLE)value,modifier);   break;   // Level line style
      case GRAPH_OBJ_PROP_LEVELWIDTH            :  obj.SetLevelWidth((int)value,modifier);               break;   // Level line width
      case GRAPH_OBJ_PROP_ALIGN                 :  obj.SetAlign((ENUM_ALIGN_MODE)value);                 break;   // Horizontal text alignment in the Edit object (OBJ_EDIT)
      case GRAPH_OBJ_PROP_FONTSIZE              :  obj.SetFontSize((int)value);                          break;   // Font size
      case GRAPH_OBJ_PROP_RAY_LEFT              :  obj.SetFlagRayLeft(value);                            break;   // Ray goes to the left
      case GRAPH_OBJ_PROP_RAY_RIGHT             :  obj.SetFlagRayRight(value);                           break;   // Ray goes to the right
      case GRAPH_OBJ_PROP_RAY                   :  obj.SetFlagRay(value);                                break;   // Vertical line goes through all windows of a chart
      case GRAPH_OBJ_PROP_ELLIPSE               :  obj.SetFlagEllipse(value);                            break;   // Display the full ellipse of the Fibonacci Arc object
      case GRAPH_OBJ_PROP_ARROWCODE             :  obj.SetArrowCode((uchar)value);                       break;   // Arrow code for the Arrow object
      case GRAPH_OBJ_PROP_ANCHOR                :  obj.SetAnchor((int)value);                            break;   // Position of the binding point of the graphical object
      case GRAPH_OBJ_PROP_XDISTANCE             :  obj.SetXDistance((int)value);                         break;   // Distance from the base corner along the X axis in pixels
      case GRAPH_OBJ_PROP_YDISTANCE             :  obj.SetYDistance((int)value);                         break;   // Distance from the base corner along the Y axis in pixels
      case GRAPH_OBJ_PROP_DIRECTION             :  obj.SetDirection((ENUM_GANN_DIRECTION)value);         break;   // Gann object trend
      case GRAPH_OBJ_PROP_DEGREE                :  obj.SetDegree((ENUM_ELLIOT_WAVE_DEGREE)value);        break;   // Elliott wave markup level
      case GRAPH_OBJ_PROP_DRAWLINES             :  obj.SetFlagDrawLines(value);                          break;   // Display lines for Elliott wave markup
      case GRAPH_OBJ_PROP_STATE                 :  obj.SetFlagState(value);                              break;   // Button state (pressed/released)
      case GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID    :  obj.SetChartObjChartID(value);                        break;   // Chart object ID (OBJ_CHART)
      case GRAPH_OBJ_PROP_CHART_OBJ_PERIOD      :  obj.SetChartObjPeriod((ENUM_TIMEFRAMES)value);        break;   // Chart object period
      case GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE  :  obj.SetChartObjChartScale((int)value);                break;   // Time scale display flag for the Chart object
      case GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE :  obj.SetFlagChartObjPriceScale(value);                 break;   // Price scale display flag for the Chart object
      case GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE :  obj.SetFlagChartObjDateScale(value);                  break;   // Chart object scale
      case GRAPH_OBJ_PROP_XSIZE                 :  obj.SetXSize((int)value);                             break;   // Object distance along the X axis in pixels
      case GRAPH_OBJ_PROP_YSIZE                 :  obj.SetYSize((int)value);                             break;   // Object height along the Y axis in pixels
      case GRAPH_OBJ_PROP_XOFFSET               :  obj.SetXOffset((int)value);                           break;   // X coordinate of the upper-left corner of the visibility area
      case GRAPH_OBJ_PROP_YOFFSET               :  obj.SetYOffset((int)value);                           break;   // Y coordinate of the upper-left corner of the visibility area
      case GRAPH_OBJ_PROP_BGCOLOR               :  obj.SetBGColor((color)value);                         break;   // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
      case GRAPH_OBJ_PROP_CORNER                :  obj.SetCorner((ENUM_BASE_CORNER)value);               break;   // Chart corner for binding a graphical object
      case GRAPH_OBJ_PROP_BORDER_TYPE           :  obj.SetBorderType((ENUM_BORDER_TYPE)value);           break;   // Border type for "Rectangle border"
      case GRAPH_OBJ_PROP_BORDER_COLOR          :  obj.SetBorderColor((color)value);                     break;   // Border color for the OBJ_EDIT and OBJ_BUTTON objects
      case GRAPH_OBJ_PROP_BASE_ID               :  obj.SetBaseObjectID(value);                           break;   // Base object ID
      case GRAPH_OBJ_PROP_GROUP                 :  obj.SetGroup((int)value);                             break;   // Graphical object group
      case GRAPH_OBJ_PROP_CHANGE_HISTORY        :  obj.SetAllowChangeMemory((bool)value);                break;   // Flag of storing the change history
      case GRAPH_OBJ_PROP_ID                    :  // Object ID
      case GRAPH_OBJ_PROP_TYPE                  :  // Graphical object type (ENUM_OBJECT)
      case GRAPH_OBJ_PROP_ELEMENT_TYPE          :  // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
      case GRAPH_OBJ_PROP_SPECIES               :  // Graphical object species (ENUM_GRAPH_OBJ_SPECIES)
      case GRAPH_OBJ_PROP_BELONG                :  // Graphical object affiliation
      case GRAPH_OBJ_PROP_CHART_ID              :  // Chart ID
      case GRAPH_OBJ_PROP_WND_NUM               :  // Chart subwindow index
      case GRAPH_OBJ_PROP_NUM                   :  // Object index in the list
      case GRAPH_OBJ_PROP_CREATETIME            :  // Object creation time
      default  : break;
     }
  }
//+------------------------------------------------------------------+

Si transmitimos un puntero inválido a un objeto, o el objeto no es un objeto subordinado (no está vinculado al básico), salimos. A continuación, simplemente establecemos para el objeto la propiedad transmitida al método. Algunas propiedades del objeto no se pueden cambiar, por lo que se encuentran al final de la lista de cambios y no se procesan de ninguna forma.

Método que establece una propiedad real en el objeto subordinado especificado:

//+------------------------------------------------------------------+
//|Set a real property to the specified subordinate object           |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier)
  {
   if(obj==NULL || obj.BaseObjectID()==0)
      return;
   switch(prop)
     {
      case GRAPH_OBJ_PROP_PRICE                 : obj.SetPrice(value,modifier);        break;   // Price coordinate
      case GRAPH_OBJ_PROP_LEVELVALUE            : obj.SetLevelValue(value,modifier);   break;   // Level value
      case GRAPH_OBJ_PROP_SCALE                 : obj.SetScale(value);                 break;   // Scale (property of Gann objects and Fibonacci Arcs objects)
      case GRAPH_OBJ_PROP_ANGLE                 : obj.SetAngle(value);                 break;   // Angle
      case GRAPH_OBJ_PROP_DEVIATION             : obj.SetDeviation(value);             break;   // Deviation of the standard deviation channel
      default: break;
     }
  }
//+------------------------------------------------------------------+

Método que establece una propiedad string en el objeto subordinado especificado:

//+------------------------------------------------------------------+
//| Set a string property to the specified subordinate object        |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier)
  {
   if(obj==NULL || obj.BaseObjectID()==0)
      return;
   obj.SetProperty(prop,modifier,value);
   switch(prop)
     {
      case GRAPH_OBJ_PROP_TEXT                  : obj.SetText(value);                  break;   // Object description (the text contained in the object)
      case GRAPH_OBJ_PROP_TOOLTIP               : obj.SetTooltip(value);               break;   // Tooltip text
      case GRAPH_OBJ_PROP_LEVELTEXT             : obj.SetLevelText(value,modifier);    break;   // Level description
      case GRAPH_OBJ_PROP_FONT                  : obj.SetFont(value);                  break;   // Font
      case GRAPH_OBJ_PROP_BMPFILE               : obj.SetBMPFile(value,modifier);      break;   // BMP file name for the "Bitmap Level" object
      case GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL      : obj.SetChartObjSymbol(value);        break;   // Chart object symbol
      case GRAPH_OBJ_PROP_BASE_NAME             : obj.SetBaseName(value);              break;   // Base object name
      case GRAPH_OBJ_PROP_NAME                  : // Object name
      default :  break;
     }
  }
//+------------------------------------------------------------------+

Ambos métodos son idénticos al método que establece una propiedad entera.


Desplazamiento y eliminación de objetos gráficos compuestos

Al desplazar un objeto gráfico compuesto (y solo podemos moverlo desplazando el objeto básico), necesitamos que todos los objetos gráficos subordinados fijados al objeto básico se muevan tras el objeto básico. Como hemos mencionado al principio, esto no se puede conseguir simplemente monitoreando los eventos: los eventos ocurren en el momento en que se suelta el botón del ratón tras desplazar un objeto gráfico. Al mismo tiempo, este adquiere sus propiedades modificadas finales, que deben escribirse en los objetos fijados, para que también se desplacen a las posiciones correspondientes a sus coordenadas de anclaje. Este será el paso final para mover un objeto gráfico compuesto. Mientras movemos el objeto con el ratón y aún no lo hemos soltado, también deberemos monitorear el cambio en la ubicación del objeto gráfico en el propio gráfico, para rastrear sus coordenadas de manera interactiva y mover los objetos dependientes fijados al básico de la forma correspondiente. Pero de esto nos ocuparemos más adelante. Ahora necesitamos recalcular los puntos de ubicación de los objetos dependientes después de mover el objeto básico en un objeto gráfico compuesto.

Para ello, en el método que comprueba los cambios en las propiedades de un objeto, en la misma clase del objeto gráfico abstracto, vamos a escribir el siguiente bloque de código:

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   CGBaseObj::ClearEventsList();
   bool changed=false;
   int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j) && prop!=GRAPH_OBJ_PROP_NAME)
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }
   if(changed)
     {
      for(int i=0;i<this.m_list_events.Total();i++)
        {
         CGBaseEvent *event=this.m_list_events.At(i);
         if(event==NULL)
            continue;
         ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam());
        }
      if(this.AllowChangeHistory())
        {
         int total=HistoryChangesTotal();
         if(this.CreateNewChangeHistoryObj(total<1))
            ::Print
              (
               DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT)," #",(total==0 ? "0-1" : (string)total),
               ": ",this.HistoryChangedObjTimeChangedToString(total-1)
              );
        }
      //--- If subordinate objects are attached to the base one (in a composite graphical object)
      if(this.m_list.Total()>0)
        {
         //--- In the loop by the number of added graphical objects,
         for(int i=0;i<this.m_list.Total();i++)
           {
            //--- get the next graphical object,
            CGStdGraphObj *dep=m_list.At(i);
            if(dep==NULL)
               continue;
            //--- get the data object of its pivot points,
            CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint();
            if(pp==NULL)
               continue;
            //--- get the number of coordinate points the object is attached to
            int num=pp.GetNumLinkedCoords();
            //--- In the loop by the object coordinate points,
            for(int j=0;j<num;j++)
              {
               //--- get the number of coordinate points of the base object for setting the X coordinate
               int numx=pp.GetBasePivotsNumX(j);
               //--- In the loop by each coordinate point for setting the X coordinate,
               for(int nx=0;nx<numx;nx++)
                 {
                  //--- get the property for setting the X coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyX(j,nx);
                  int modifier_from=pp.GetPropertyModifierX(j,nx);
                  this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx);
                 }
               //--- Get the number of coordinate points of the base object for setting the Y coordinate
               int numy=pp.GetBasePivotsNumY(j);
               //--- In the loop by each coordinate point for setting the Y coordinate,
               for(int ny=0;ny<numy;ny++)
                 {
                  //--- get the property for setting the Y coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyY(j,ny);
                  int modifier_from=pp.GetPropertyModifierY(j,ny);
                  this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny);
                 }
              }
           }
         //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes
         ::ChartRedraw(m_chart_id);
        }
      //--- Save the current properties as the previous ones
      this.PropertiesCopyToPrevData();
     }
  }
//+------------------------------------------------------------------+

El asunto aquí es que, si un objeto gráfico tiene algún cambio, buscaremos si este objeto tiene objetos dependientes y, de ser así (la lista no está vacía), iteraremos cada objeto subordinado y estableceremos en él nuevos valores para las coordenadas en las ubicaciones que se registran en este objeto e indican las coordenadas del objeto básico: partiendo de estas coordenadas, obtendremos los valores y los escribiremos en las coordenadas del objeto subordinado. Una vez finalizado el ciclo, actualizaremos el gráfico para mostrar inmediatamente todos los cambios, en lugar de esperar a que llegue un nuevo tick.

Podemos eliminar del gráfico un objeto gráfico compuesto con solo eliminar el objeto básico al que se vinculan todos los subordinados.
Procesaremos este tipo de situaciones (la eliminación de un objeto básico) en la clase de colección de elementos gráficos, en el archivo
\MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

En la sección privada de la clase, declaramos el método que procesa la eliminación de un objeto gráfico extendido estándar:

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

private:
//--- Handle the removal of extended graphical objects
   void              DeleteExtendedObj(CGStdGraphObj *obj);
//--- Create a new graphical object, return the pointer to the chart management object

Lo implementamos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Handle the removal of extended graphical objects                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::DeleteExtendedObj(CGStdGraphObj *obj)
  {
   if(obj==NULL)
      return;
   //--- Save the ID of the graphical object chart and the number of subordinate objects in its list
   long chart_id=obj.ChartID();
   int total=obj.GetNumDependentObj();
   //--- If the list of subordinate objects is not empty (this is the base object)
   if(total>0)
     {
      //--- In the loop, move along all dependent objects and remove them
      for(int n=total-1;n>WRONG_VALUE;n--)
        {
         //--- Get the next graphical object
         CGStdGraphObj *dep=obj.GetDependentObj(n);
         if(dep==NULL)
            continue;
         //--- If failed to remove it from the chart, display the appropriate message in the journal
         if(!::ObjectDelete(dep.ChartID(),dep.Name()))
            CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
        }
      //--- Upon the loop completion, update the chart to display the changes and exit the method
      ::ChartRedraw(chart_id);
      return;
     }
   //--- If this is a subordinate object
   else if(obj.BaseObjectID()>0)
     {
      //--- Get the base object name and its ID
      string base_name=obj.BaseName();
      long base_id=obj.BaseObjectID();
      //--- Get the base object from the graphical object collection list
      CGStdGraphObj *base=GetStdGraphObject(base_name,chart_id);
      if(base==NULL)
         return;
      //--- get the number of dependent objects in its list
      int count=base.GetNumDependentObj();
      //--- In the loop, move along all its dependent objects and remove them
      for(int n=count-1;n>WRONG_VALUE;n--)
        {
         //--- Get the next graphical object
         CGStdGraphObj *dep=base.GetDependentObj(n);
         //--- If failed to get the pointer or the object has already been removed from the chart, move on to the next one
         if(dep==NULL || !this.IsPresentGraphObjOnChart(dep.ChartID(),dep.Name()))
            continue;
         //--- If failed to delete the graphical object from the chart,
         //--- display the appropriate message in the journal and move on to the next one
         if(!::ObjectDelete(dep.ChartID(),dep.Name()))
           {
            CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
            continue;
           }
        }
      //--- Remove the base object from the chart and from the list
      if(!::ObjectDelete(base.ChartID(),base.Name()))
         CMessage::ToLog(DFUN+base.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
     }
   //--- Update the chart for displaying the changes
   ::ChartRedraw(chart_id);
  }
//+------------------------------------------------------------------+

La lógica completa del método se describe detalladamente en los comentarios al código. En resumen: si eliminamos un objeto básico (en su lista hay objetos vinculados), eliminaremos del gráfico todos los objetos vinculados a dicho objeto. Si eliminamos un objeto gráfico dependiente, entonces deberemos averiguar a qué objeto estaba vinculado (para ello, buscaremos el objeto básico del objeto gráfico compuesto), y luego revisaremos la lista de objetos dependientes fijados a él y los eliminaremos todos.

Este método se llama en el método que actualiza la lista con todos los objetos gráficos, en el bloque de procesamiento de la eliminación de objetos gráficos:

//+------------------------------------------------------------------+
//| Update the list of all graphical objects                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::Refresh(void)
  {
   this.RefreshForExtraObjects();
//--- 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(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               continue;
           }
         //--- If the graphical object has been removed
         else if(obj_ctrl.Delta()<0)
           {
            int index=WRONG_VALUE;
            //--- In the loop by the number of removed graphical objects
            for(int j=0;j<-obj_ctrl.Delta();j++)
              {
               // Find an extra object in the list
               CGStdGraphObj *obj=this.FindMissingObj(chart_id,index);
               if(obj!=NULL)
                 {
                  //--- Get the removed object parameters
                  long   lparam=obj.ChartID();
                  string sparam=obj.Name();
                  double dparam=(double)obj.TimeCreate();
                  //--- If this is an extended graphical object
                  if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
                    {                            
                     this.DeleteExtendedObj(obj);
                    }                            
                  //--- Move the graphical object class object to the list of removed objects
                  //--- and send the event to the control program chart
                  if(this.MoveGraphObjToDeletedObjList(index))
                     ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,lparam,dparam,sparam);
                 }
              }
           }
        }
      //--- Increase the loop index
      i++;
     }
  }
//+------------------------------------------------------------------+

Bien, esto es suficiente para procesar la eliminación de un objeto gráfico estándar compuesto.

Pongamos a prueba lo que tenemos.


Simulación

Para las pruebas, tomaremos el asesor del artículo anterior y
lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part94\ con el nuevo nombre TestDoEasyPart94.mq5.

No haremos ningún cambio en el asesor, salvo eliminar la muestra de las entradas en el diario sobre los objetos creados que sirven de base para construir un objeto gráfico compuesto, en el bloque de procesamiento de clics en el gráfico, en el manejador OnChartEvent():

   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      //--- Get the chart click coordinates
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         //--- Get the right point coordinates for a trend line
         datetime time2=iTime(Symbol(),PERIOD_CURRENT,1);
         double price2=iOpen(Symbol(),PERIOD_CURRENT,1);
         
         //--- Create the "Trend line" object
         string name_base="TrendLineExt";
         engine.CreateLineTrend(name_base,0,true,time,price,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID and pass its properties to the journal
         CGStdGraphObj *obj=engine.GraphGetStdGraphObjectExt(name_base,ChartID());
         
         //--- Create the "Left price label" object
         string name_dep="PriceLeft";
         engine.CreatePriceLabelLeft(name_dep,0,false,time,price);
         //--- Get the object from the list of graphical objects by chart name and ID and
         CGStdGraphObj *dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line left point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,0,GRAPH_OBJ_PROP_PRICE,0);
         
         //--- Create the "Right price label" object
         name_dep="PriceRight";
         engine.CreatePriceLabelRight(name_dep,0,false,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID and
         dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line right point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,1,GRAPH_OBJ_PROP_PRICE,1);
        }
     }

El hecho de que aquí creamos los objetos "Left price label" y "Right label" como no extendidos, no debe preocuparnos, porque ahora, en el método AddDependentObj(), todos los objetos vinculados obtienen de forma inequívoca el estado de objeto gráfico extendido.

Compilamos el asesor y lo ejecutamos en el gráfico:


Como podemos ver, el desplazamiento del objeto gráfico compuesto no se ve muy bien: los objetos dependientes se colocan en su lugar solo después de soltar el botón del ratón. Pero esto se puede arreglar, y nos ocuparemos de ello en los siguientes artículos. La eliminación del objeto funciona correctamente: al eliminar el objeto gráfico básico, también se eliminan todos los objetos subordinados. La eliminación intencional de uno de los objetos subordinados da como resultado la eliminación de todo el objeto gráfico compuesto.

¿Qué es lo próximo?

En el próximo artículo, continuaremos trabajando con los objetos gráficos compuestos.

Más abajo, se adjuntan todos los archivos de la versión actual de la biblioteca, así como los archivos del asesor de prueba y el indicador de control de eventos de gráficos 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 89): Creación programática de objetos gráficos estándar. Funcionalidad básica
Gráficos en la biblioteca DoEasy (Parte 90): Eventos de objetos gráficos estándar. Funcionalidad básica
Gráficos en la biblioteca DoEasy (Parte 91): Eventos de objetos gráficos estándar en el programa. Historia de cambio de nombre del objeto
Gráficos en la biblioteca DoEasy (Parte 92): Clase de memoria de objetos gráficos estándar Historia de cambio de propiedades del objeto
Gráficos en la biblioteca DoEasy (Parte 93): Preparando la funcionalidad para crear objetos gráficos compuestos

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

Archivos adjuntos |
MQL5.zip (4202.7 KB)
Cómo desarrollar sistemas basados ​​en medias móviles Cómo desarrollar sistemas basados ​​en medias móviles
En este artículo, aprenderemos cómo desarrollar varios sistemas basados ​​en estrategias que usan medias móviles.
Aprenda por qué y cómo diseñar su sistema de trading algorítmico Aprenda por qué y cómo diseñar su sistema de trading algorítmico
En este artículo, mostraremos los fundamentos de MQL que permitirán a los tráders principiantes diseñar su propio sistema de trading algorítmico (Asesor Experto) mediante el diseño de un sistema de trading algorítmico simple después de mencionar algunas ideas básicas de MQL5
Matemáticas en el trading: Ratios de Sharpe y Sortino Matemáticas en el trading: Ratios de Sharpe y Sortino
El rendimiento es la métrica más obvia usada por los inversores y los tráders principiantes a la hora de analizar la efectividad del comercio. Los tráders profesionales utilizan herramientas más fiables para el análisis de estrategias, como los ratios de Sharpe y Sortino.
Gráficos en la biblioteca DoEasy (Parte 93): Preparando la funcionalidad para crear objetos gráficos compuestos Gráficos en la biblioteca DoEasy (Parte 93): Preparando la funcionalidad para crear objetos gráficos compuestos
En el presente artículo, comenzaremos a desarrollar la funcionalidad necesaria para crear objetos gráficos compuestos. Nuestra biblioteca ofrecerá soporte a la creación de objetos gráficos compuestos complejos en los que dichos objetos podrán tener cualquier jerarquía de relaciones. Vamos a preparar todas las clases necesarias para la posterior implementación de tales objetos.