English Русский 中文 Deutsch 日本語 Português
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 91): Eventos de objetos gráficos estándar en el programa. Historia de cambio de nombre del objeto

MetaTrader 5Ejemplos | 2 marzo 2022, 08:33
424 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

Nuestra biblioteca ya sabe cómo detectar los eventos que ocurren con los objetos gráficos estándar. Hoy necesitaremos crear una funcionalidad que nos permita, al recibir un evento en un programa basado en la biblioteca, determinar exactamente qué propiedad es y cuánto se ha cambiado. Haremos esto de forma sencilla: en el evento enviaremos el identificador del evento, el nombre del objeto en el que se ha producido el evento, el identificador del gráfico en el que se encuentra el objeto gráfico cambiado y el nombre de la propiedad cuyo valor se ha modificado. Al eliminar objetos gráficos del gráfico, colocaremos los objetos de clase que describen estos objetos gráficos eliminados en la lista de objetos eliminados; esto nos permitirá comprender de forma programática qué objeto se ha eliminado y, si es necesario, obtener todas sus propiedades o restaurar un objeto accidentalmente eliminado.

Si continuamos con la idea de almacenar los objetos gráficos eliminados y la ampliamos para guardar las propiedades de los objetos modificados, podremos crear una funcionalidad muy atractiva que nos permitirá repetir la historia completa de cambios de un objeto. Es decir, si tenemos una lista con todas las propiedades modificadas sucesivamente en un objeto, siempre podremos reproducir cualquiera de los estados que tenía el objeto antes del actual. Por consiguiente, podremos "grabar" diferentes posiciones relativas al gráfico en el objeto, y luego reproducirlas secuencialmente (o no secuencialmente), lo cual nos permitirá en el futuro crear herramientas de análisis técnico con su propia "memoria", incluidos los objetos gráficos compuestos, que consideraremos en los próximos artículos.

Hoy no vamos a implementar por completo el concepto descrito anteriormente, pero sí que pondremos a prueba su funcionalidad usando como ejemplo el cambio de la propiedad "Nombre del objeto"; para ello, guardaremos su historial de cambios, y ya en artículos posteriores, crearemos gradualmente la capacidad de registrar y reproducir todas las propiedades de un objeto.

Este concepto nació de la necesidad de que el programa supiera qué nombre del objeto gráfico ha cambiado, para registrar sus nombres pasados ​​y actuales. Al comprobar que teníamos casi todo listo para implementar dicha característica, las reflexiones nos llevaron a concluir que sobre esta base poríamos ampliar la funcionalidad de la biblioteca y los objetos gráficos creados sobre ella, así que procedimos a trabajar usando este concepto.


Mejorando las clases de la biblioteca

Como de costumbre, comenzaremos añadiendo los nuevos mensajes de la biblioteca al archivo \MQL5\Include\DoEasy\Data.mqh.

Vamos a añadir 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_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
   
   MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR,           // Indicator for controlling and sending events created
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR,    // Failed to create the indicator for controlling and sending events
   MSG_GRAPH_OBJ_ADDED_EVN_CTRL_INDICATOR,            // Indicator for controlling and sending events successfully added to the chart
   MSG_GRAPH_OBJ_FAILED_ADD_EVN_CTRL_INDICATOR,       // Failed to add the indicator for controlling and sending events
   MSG_GRAPH_OBJ_ALREADY_EXIST_EVN_CTRL_INDICATOR,    // Indicator for controlling and sending events is already present on the chart
   MSG_GRAPH_OBJ_CLOSED_CHARTS,                       // Chart windows closed:
   MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS,            // Objects removed together with charts:
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_OBJ,               // Failed to create the event object for a graphical object
   MSG_GRAPH_OBJ_FAILED_ADD_EVN_OBJ,                  // Failed to add the event object to the list
   MSG_GRAPH_OBJ_GRAPH_OBJ_PROP_CHANGE_HISTORY,       // Property change history: 
   
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE,                // New graphical object created
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE,                // Changed the graphical object property
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME,                // Graphical object renamed
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE,                // Graphical object removed
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART,             // Graphical object removed together with the chart
   
  };
//+------------------------------------------------------------------+

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 place graphic object in the list of deleted objects"},
   {"Не удалось поместить графический объект в список переименованных объектов","Failed to place graphic object in the list of renamed objects"},
   
   {"Создан индикатор контроля и отправки событий","An indicator for monitoring and sending events has been created"},
   {"Не удалось создать индикатор контроля и отправки событий","Failed to create indicator for monitoring and sending events"},
   {"Индикатор контроля и отправки событий успешно добавлен на график ","The indicator for monitoring and sending events has been successfully added to the chart "},
   {"Не удалось добавить индикатор контроля и отправки событий на график ","Failed to add the indicator of monitoring and sending events to the chart "},
   {"Индикатор контроля и отправки событий уже есть на графике","The indicator for monitoring and sending events is already on the chart"},
   {"Закрыто окон графиков: ","Closed chart windows: "},
   {"С ними удалено объектов: ","Objects removed with them: "},
   {"Не удалось создать объект-событие для графического объекта","Failed to create event object for graphic object"},
   {"Не удалось добавить объект-событие в список","Failed to add event object to list"},
   {"История изменения свойства: ","Property change history: "},
   
   {"Создан новый графический объект","New graphic object created"},
   {"Изменено свойство графического объекта","Changed graphic object property"},
   {"Графический объект переименован","Graphic object renamed"},
   {"Удалён графический объект","Graphic object deleted"},
   {"Графический объект удалён вместе с графиком","The graphic object has been removed along with the chart"},
   
  };
//+---------------------------------------------------------------------+


En la sección pública de la clase de objeto gráfico estándar abstracto, en el archivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh, escribimos el nuevo método para retornar el puntero a un objeto de propiedades y declaramos el método para mostrar la historia del cambio de nombre de un objeto gráfico en el diario:

//--- Return (1) itself and (2) the properties
   CGStdGraphObj    *GetObject(void)                                       { return &this;      }
   CProperties      *Properties(void)                                      { return this.Prop;  }
//--- 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);
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_STRING property);

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

//--- 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);
//--- Return the object short name
   virtual string    Header(const bool symbol=false);
//--- Display the history of renaming the object to the journal
   void              PrintRenameHistory(void);

//--- Return the description of the (1) (ENUM_OBJECT) graphical object type
   virtual string    TypeDescription(void)                                    const { return CMessage::Text(MSG_GRAPH_STD_OBJ_ANY);          }


Al cambiar el nombre de un objeto, no podemos averiguar su nombre anterior si no ha sido escrito de antemano en ningún lugar. Ya hemos construido el mecanismo para determinar el cambio de nombre de un objeto: para cada objeto físico en el gráfico, almacenamos en la colección de objetos gráficos un objeto de clase de objeto gráfico estándar con una descripción de sus parámetros. En cuanto se define un evento de cambio de nombre de objeto, cambiamos el nombre en el objeto de clase correspondiente. Pero antes de reemplazar el nombre, podemos averiguar el nombre anterior del objeto y almacenarlo para la historia. Y lo mismo podemos hacer con el resto de propiedades.

Después de pensar dónde almacenar el nombre pasado del objeto, hemos decidirlo guardarlo directamente en las propiedades del objeto, porque ahora los objetos de la clase de objetos gráficos se construyen sobre matrices dinámicas multidimensionales, y esto nos permite establecer en cualquier momento el tamaño deseado de matriz en cualquiera de sus dimensiones. Usaremos precisamente esto: solo con cada cambio de nombre posterior del objeto gráfico, aumentaremos la matriz de la propiedad del nombre del objeto e introduciremos su nombre anterior al final de la matriz. Por consiguiente, la celda 0 de la matriz de propiedades del nombre del objeto almacenará su nombre real, mientras que las otras celdas de la matriz guardarán sus nombres anteriores. En este caso, siempre podremos averiguar la historia completa del cambio de nombre de un objeto. Y si hacemos lo mismo con sus otras propiedades, podremos obtener un objeto gráfico interesante que puede registrar y almacenar sus estados. Especificando la celda necesaria desde la que leeremos los parámetros escritos en el objeto, podremos reproducir rápidamente todos sus estados, lo cual puede resultar útil para crear herramientas analíticas con memoria.

Otras propiedades no son el nombre del objeto: lo mejoraremos más adelante, ya que aquí solo revisamos el concepto de almacenamiento de la historia de cambio de las propiedades del objeto. Luego reelaboraremos ligeramente la estructura de la matriz multidimensional que almacena las propiedades del objeto para que puedan almacenarse como una multipropiedad del objeto (por ejemplo, sus niveles), y la historia de cambios en estos niveles. Ahora los niveles se guardan en las mismas celdas de la matriz que la historia de cambio de los valores para la propiedad "Nombre" del objeto. Para evitar colisiones de las propiedades, tendremos que cambiar ligeramente la estructura de la matriz de propiedades multidimensionales, lo cual implementaremos en artículos posteriores.

En la sección pública de la clase, escribimos el método para establecer el nombre anterior del objeto:

//--- Compare CGStdGraphObj objects by a specified property (to sort the list by an object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CGStdGraphObj objects with each other by all properties (to search for equal objects)
   bool              IsEqual(CGStdGraphObj* compared_req) const;
   
//--- Set the object previous name
   bool              SetNamePrev(const string name)
                       {
                        if(!this.Prop.SetSizeRange(GRAPH_OBJ_PROP_NAME,this.Prop.CurrSize(GRAPH_OBJ_PROP_NAME)+1))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_NAME,this.Prop.CurrSize(GRAPH_OBJ_PROP_NAME)-1,name);
                        return true;
                       }
  
//--- Default constructor
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; m_group=WRONG_VALUE; }
//--- Destructor
                    ~CGStdGraphObj()
                       {
                        if(this.Prop!=NULL)
                           delete this.Prop;
                       }
protected:

A continuación, transmitimos al método el nombre pasado del objeto; luego incrementamos en 1 el tamaño de la matriz de propiedades donde se almacena el nombre, añadimos el nombre pasado del objeto gráfico transmitido al método en la nueva celda de la matriz y retornamos true. Si el cambio del tamaño de la matriz o la escritura del nombre no tienen éxito, imprimimos un mensaje en el diario y retornamos false.

Fuera del cuerpo de la clase, escribimos una implementación del método que muestra la historial de cambio de nombre de los objetos:

//+------------------------------------------------------------------+
//| Display the history of renaming the object to the journal        |
//+------------------------------------------------------------------+
void CGStdGraphObj::PrintRenameHistory(void)
  {
   ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_GRAPH_OBJ_PROP_CHANGE_HISTORY),this.GetPropertyDescription(GRAPH_OBJ_PROP_NAME),":");
   int size=this.Properties().CurrSize(GRAPH_OBJ_PROP_NAME);
   ::Print(DFUN,"0: ",this.GetProperty(GRAPH_OBJ_PROP_NAME,0));
   for(int i=size-1;i>0;i--)
      ::Print(DFUN,size-i,": ",this.GetProperty(GRAPH_OBJ_PROP_NAME,i));
  }
//+------------------------------------------------------------------+

Aquí, primero, mostramos en el diario el título con el nuevo nombre del objeto, luego mostramos su nombre actual con una indicación del índice 0,
y después, en un ciclo inverso hasta la celda de la matriz con el índice 1 incluido, mostramos la descripción de la propiedad del objeto "Nombre" inscrita en las celdas de la matriz del nombre del objeto que almacena la historia con sus nuevos nombramientos según el índice de ciclo. Como el nombre actual de un objeto siempre se almacena en la celda cero, y el anterior se almacena en la última, para mostrar correctamente el número secuencial del nuevo nombramiento del objeto, calcularemos este valor sin usar el índice de ciclo.

Como resultado, para un objeto renombrado tres veces, se mostrará la siguiente entrada en el diario:

CGStdGraphObj::PrintRenameHistory: Property change history: Name: "M30 Vertical Line 24264_3":
CGStdGraphObj::PrintRenameHistory: 0: M30 Vertical Line 24264_3
CGStdGraphObj::PrintRenameHistory: 1: M30 Vertical Line 24264_2
CGStdGraphObj::PrintRenameHistory: 2: M30 Vertical Line 24264_1
CGStdGraphObj::PrintRenameHistory: 3: M30 Vertical Line 24264

Como podemos ver, aquí tenemos una imagen clara de la secuencia de cambio de nombre de un objeto gráfico. (Sin embargo, lo más probable es que cambiemos la numeración de esta secuencia para que el número 0 siempre corresponda al nombre original del objeto, y el último número al nombre actual).


Implementaremos el funcionamiento principal para finalizar el monitoreo de eventos de los objetos gráficos en la clase de colección de objetos gráficos.

Abramos el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh y hacemos todos los cambios necesarios.

En esta implementación del procesamiento del evento de adición de objetos, obtenemos el último objeto añadido de la lista del terminal. Funciona, pero solo si hemos añadido un nuevo objeto al gráfico. Si añadimos varios objetos a la vez, seguiremos recibiendo solo un evento en la biblioteca: sobre la adición de un solo objeto de varios al gráfico. Para evitar esta situación, deberemos verificar si se ha añadido un objeto al gráfico y, de ser así, obtener el último objeto agregado. Si añadimos varios objetos al mismo tiempo, deberemos seleccionar cada uno en un ciclo a través de todos los objetos del gráfico y enviar un evento sobre su adición.

Sin embargo, lo haremos de manera un poco diferente: primero crearemos una lista con todos los objetos añadidos, y solo entonces, desde esta lista, enviaremos eventos al gráfico del programa de control. Comprobamos los objetos recién añadidos en los métodos Refresh() de los objetos de la clase de gestión de gráficos CChartObjectsControl que tenemos para cada uno de los gráficos abiertos. En los métodos, creamos los objetos de clase para cada objeto añadido, luego los añadimos a la lista y, además, en la lista, a través de este ciclo, enviamos mensajes sobre la creación de objetos al programa de control. El manejador de eventos de la biblioteca se llamará desde el programa que recibirá el objeto de control de gráficos necesario del que provienen los mensajes, extraerá de la lista los objetos de las clases de objetos gráficos estándar de la lista y los reubicará en la lista de colección.

Después de las mejoras, el método Refresh() de la clase de objeto de control del gráfico tendrá este aspecto:

//+------------------------------------------------------------------+
//| CChartObjectsControl: Check objects on a chart                   |
//+------------------------------------------------------------------+
void CChartObjectsControl::Refresh(void)
  {
//--- Clear the list of newly added objects
   this.m_list_new_graph_obj.Clear();
//--- Calculate the number of new objects on the chart
   this.m_total_objects=::ObjectsTotal(this.ChartID());
   this.m_delta_graph_obj=this.m_total_objects-this.m_last_objects;
   //--- If an object is added to the chart
   if(this.m_delta_graph_obj>0)
     {
      //--- Create the list of added graphical objects
      for(int i=0;i<this.m_delta_graph_obj;i++)
        {
         //--- Get the name of the last added object (if a single new object is added),
         //--- or a name from the terminal object list by index (if several objects have been added)
         string name=(this.m_delta_graph_obj==1 ? this.LastAddedGraphObjName() : ::ObjectName(this.m_chart_id,i));
         //--- Handle only non-programmatically created objects
         if(name==NULL || ::StringFind(name,this.m_name_program)>WRONG_VALUE)
            continue;
         //--- Create the object of the graphical object class corresponding to the added graphical object type
         ENUM_OBJECT type=(ENUM_OBJECT)::ObjectGetInteger(this.ChartID(),name,OBJPROP_TYPE);
         ENUM_OBJECT_DE_TYPE obj_type=ENUM_OBJECT_DE_TYPE(type+OBJECT_DE_TYPE_GSTD_OBJ+1);
         CGStdGraphObj *obj=this.CreateNewGraphObj(type,name);
         //--- If failed to create an object, inform of that and move on to the new iteration
         if(obj==NULL)
           {
            CMessage::ToLog(DFUN,MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ);
            continue;
           }
         //--- Set the object affiliation and add the created object to the list of new objects
         obj.SetBelong(GRAPH_OBJ_BELONG_NO_PROGRAM); 
         //--- If failed to add the object to the list, inform of that, remove the object and move on to the next iteration
         if(!this.m_list_new_graph_obj.Add(obj))
           {
            CMessage::ToLog(DFUN_ERR_LINE,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
            delete obj;
            continue;
           }
        }
      //--- Send events to the control program chart from the created list
      for(int i=0;i<this.m_list_new_graph_obj.Total();i++)
        {
         CGStdGraphObj *obj=this.m_list_new_graph_obj.At(i);
         if(obj==NULL)
            continue;
         //--- Send an event to the control program chart
         ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_CREATE,this.ChartID(),obj.TimeCreate(),obj.Name());
        }
     }
//--- save the index of the last added graphical object and the difference with the last check
   this.m_last_objects=this.m_total_objects;
   this.m_is_graph_obj_event=(bool)this.m_delta_graph_obj;
  }
//+------------------------------------------------------------------+

La lógica del método se describe al completo en los comentarios al código. Nos gustaría llamar la atención del lector sobre un hecho: en el parámetro dparam de la función EventChartCustom(), transmitimos la hora de creación del objeto gráfico. Necesitamos esto para identificar los objetos que se encuentran en un gráfico restaurado previamente eliminado. Es decir, si colocamos los objetos gráficos en un gráfico abierto, luego eliminamos este gráfico y después lo restauramos, los objetos gráficos que estaban en él en el momento de su eliminación se restaurarán junto con el gráfico, y dichos objetos tendrán una hora de creación igual a cero. Posteriormente, podemos usar este valor de tiempo para identificar los objetos gráficos restaurados junto con el gráfico de los símbolos.

En el método que añade al gráfico un indicador de control de eventos de la clase de control de gráfico, antes de añadir el indicador al gráfico, comprobamos que no exista aún el indicador en este gráfico:

//+------------------------------------------------------------------+
//|CChartObjectsControl: Add the event control indicator to the chart|
//+------------------------------------------------------------------+
bool CChartObjectsControl::AddEventControlInd(void)
  {
   if(this.m_handle_ind==INVALID_HANDLE)
      return false;
   ::ResetLastError();
   string shortname="EventSend_From#"+(string)this.ChartID()+"_To#"+(string)this.m_chart_id_main;
   int total=::ChartIndicatorsTotal(this.ChartID(),0);
   for(int i=0;i<total;i++)
      if(::ChartIndicatorName(this.ChartID(),0,i)==shortname)
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_ALREADY_EXIST_EVN_CTRL_INDICATOR);
         return true;
        }
   return ::ChartIndicatorAdd(this.ChartID(),0,this.m_handle_ind);
  }
//+------------------------------------------------------------------+

Aquí, primero creamos un nombre breve para el indicador que usamos para buscar los indicadores en el gráfico; luego, obtenemos en un ciclo el siguiente indicador del gráfico y, si el nombre breve del indicador obtenido coincide con el que estamos buscando, informamos de que dicho indicador ya está en el gráfico y retornamos true. Al final del ciclo , retornamos el resultado de la adición del indicador al gráfico.

En la clase de la colección de objetos gráficos, en su sección privada, declaramos la lista de objetos gráficos remotos, declaramos el método sobrecargado que encuentra un objeto que está en la colección pero falta en el gráfico, y retornamos, además del puntero al objeto encontrado, el índice de este objeto en la lista. Asimismo, declaramos los métodos que trasladan los objetos eliminados a la lista de objetos gráficos eliminados.

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
#resource "\\"+PATH_TO_EVENT_CTRL_IND;          // Indicator for controlling graphical object events packed into the program resources
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
   CArrayObj         m_list_deleted_obj;        // List of removed 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 class 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 presence of the graphical object class in the graphical object collection list
   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);
//--- Check if the chart window is present
   bool              IsPresentChartWindow(const long chart_id);
//--- Handle removing the chart window
   void              RefreshForExtraObjects(void);
//--- Return the first free ID of the graphical (1) object and (2) element on canvas
   long              GetFreeGraphObjID(bool program_object);
   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);
   CGStdGraphObj    *FindMissingObj(const long chart_id,int &index);
//--- Find the graphical object present on a chart but not in the collection
   string            FindExtraObj(const long chart_id);
//--- Remove the graphical object class object from the graphical object collection list: (1) specified object, (2) by chart ID
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   void              DeleteGraphObjectsFromList(const long chart_id);
//--- Move the graphical object class object to the list of removed graphical objects: (1) specified object, (2) by index
   bool              MoveGraphObjToDeletedObjList(CGStdGraphObj *obj);
   bool              MoveGraphObjToDeletedObjList(const int index);
//--- Move all objects by chart ID to the list of removed graphical objects
   void              MoveGraphObjectsToDeletedObjList(const long chart_id);
//--- Remove the object of managing charts from the list
   bool              DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj);
//--- Create a new standard graphical object, return an object name
   bool              CreateNewStdGraphObject(const long chart_id,
                                             const string name,
                                             const ENUM_OBJECT type,
                                             const int subwindow,
                                             const datetime time1,
                                             const double price1,
                                             const datetime time2=0,
                                             const double price2=0,
                                             const datetime time3=0,
                                             const double price3=0,
                                             const datetime time4=0,
                                             const double price4=0,
                                             const datetime time5=0,
                                             const double price5=0);
public:


En la sección pública de la clase, escribimos los métodos que retornan las listas de objetos gráficos remotos según las propiedades seleccionadas, declaramos el método que retorna el puntero a un objeto gráfico remoto, escribimos los métodos que retornan la lista de objetos gráficos remotos y el puntero al último objeto gráfico eliminado, y retornamos el tamaño de la matriz de propiedades del objeto gráfico.
Finalmente, declaramos el método que registra la historia de cambio de nombre de los objetos:

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 existing 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,int index,long value,ENUM_COMPARER_TYPE mode=EQUAL)      { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,index,value,mode);    }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value,ENUM_COMPARER_TYPE mode=EQUAL)     { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,index,value,mode);    }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value,ENUM_COMPARER_TYPE mode=EQUAL)     { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,index,value,mode);    }
//--- Return the list of removed graphical objects by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetListDel(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListDeletedObj(),property,index,value,mode);  }
   CArrayObj        *GetListDel(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByGraphicStdObjectProperty(this.GetListDeletedObj(),property,index,value,mode);  }
   CArrayObj        *GetListDel(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByGraphicStdObjectProperty(this.GetListDeletedObj(),property,index,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 an (1) existing and (2) removed graphical object by chart name and ID
   CGStdGraphObj    *GetStdGraphObject(const string name,const long chart_id);
   CGStdGraphObj    *GetStdDelGraphObject(const string name,const long chart_id);
//--- Return the list of (1) chart management objects and (2) removed graphical objects
   CArrayObj        *GetListChartsControl(void)                                                          { return &this.m_list_charts_control;  }
   CArrayObj        *GetListDeletedObj(void)                                                             { return &this.m_list_deleted_obj;     }
//--- Return (1) the last removed graphical object and (2) the array size of graphical object properties
   CGStdGraphObj    *GetLastDeletedGraphObj(void)                 const { return this.m_list_deleted_obj.At(this.m_list_deleted_obj.Total()-1); }
   int               GetSizeProperty(const string name,const long chart_id,const int prop)
                       {
                        CGStdGraphObj *obj=this.GetStdGraphObject(name,chart_id);
                        return(obj!=NULL ? obj.Properties().CurrSize(prop) : 0);
                       }
   
//--- 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);
//--- Display the history of renaming the object to the journal
   void              PrintRenameHistory(const string name,const long chart_id);

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

private:


En el constructor de clase, eliminamos la lista de objetos gráficos eliminados y le asignamos la bandera de lista clasificada:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGraphElementsCollection::CGraphElementsCollection()
  {
   this.m_type=COLLECTION_GRAPH_OBJ_ID;
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID);
   this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
   this.m_list_all_graph_obj.Clear();
   this.m_list_charts_control.Sort();
   this.m_list_charts_control.Clear();
   this.m_total_objects=0;
   this.m_is_graph_obj_event=false;
   this.m_list_deleted_obj.Clear();
   this.m_list_deleted_obj.Sort();
  }
//+------------------------------------------------------------------+


En el método Refresh() de la clase de colección de objetos gráficos, al procesar el evento de eliminación de objetos gráficos del gráfico, encontraremos solo un objeto eliminado. Ahora necesitaremos encontrar cada objeto remoto en un ciclo por el número de objetos remotos y enviar el evento al gráfico del programa de control. Esto se hace en el manejador mejorado de 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();
                  //--- 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++;
     }
  }
//+------------------------------------------------------------------+


En el método que procesa la eliminación de la ventana del gráfico, previamente eliminamos los objetos de las clases que describen los objetos gráficos eliminados junto con la ventana del gráfico. Ahora los trasladaremos a la lista de objetos gráficos eliminados, desde la cual luego podremos obtener todas las propiedades de cada objeto eliminado junto con el gráfico:

//+------------------------------------------------------------------+
//| Handle removing the chart window                                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::RefreshForExtraObjects(void)
  {
   for(int i=this.m_list_charts_control.Total()-1;i>WRONG_VALUE;i--)
     {
      CChartObjectsControl *obj_ctrl=this.m_list_charts_control.At(i);
      if(obj_ctrl==NULL)
         continue;
      if(!this.IsPresentChartWindow(obj_ctrl.ChartID()))
        {
         long chart_id=obj_ctrl.ChartID();
         string chart_symb=obj_ctrl.Symbol();
         int total_ctrl=this.m_list_charts_control.Total();
         this.DeleteGraphObjCtrlObjFromList(obj_ctrl);
         int total_obj=this.m_list_all_graph_obj.Total();
         this.MoveGraphObjectsToDeletedObjList(chart_id);
         int del_ctrl=total_ctrl-this.m_list_charts_control.Total();
         int del_obj=total_obj-this.m_list_all_graph_obj.Total();
         ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DEL_CHART,chart_id,del_obj,chart_symb);
        }
     }
  }
//+------------------------------------------------------------------+


Método que encuentra un objeto presente en la colección pero ausente en el gráfico y retorna el puntero al objeto y al índice de un objeto en la lista:

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

Este método es una sobrecarga del método del mismo nombre que escribimos anteriormente. Pero, a diferencia de su "socio", el método retorna, además del puntero al objeto encontrado, el índice del objeto encontrado, necesario cuando se requiere acceso a un objeto hallado según su índice en la lista.


Método que traslada a la lista de objetos gráficos eliminados todos los objetos según el ID del gráfico:

//+------------------------------------------------------------------+
//| Move all objects (by chart ID)                                   |
//| to the list of removed graphical objects                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::MoveGraphObjectsToDeletedObjList(const long chart_id)
  {
   CArrayObj *list=GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
   if(list==NULL)
      return;
   for(int i=list.Total()-1;i>WRONG_VALUE;i--)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      this.MoveGraphObjToDeletedObjList(obj);
     }
  }
//+------------------------------------------------------------------+

El método traslada todos los objetos con el identificador de gráfico indicado a la lista de objetos eliminados.
Aquí, obtenemos una lista de objetos con el ID de gráfico especificado; en un ciclo sobre por lista resultante, obtenemos el siguiente objeto y lo movemos a la lista de objetos gráficos eliminados con el método que analizaremos a continuación.


Método que traslada a la lista de objetos gráficos eliminados un objeto de clase de objeto gráfico según el índice:

//+------------------------------------------------------------------+
//| Move the class object of the graphical object by index           |
//| to the list of removed graphical objects                         |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::MoveGraphObjToDeletedObjList(const int index)
  {
   CGStdGraphObj *obj=this.m_list_all_graph_obj.Detach(index);
   if(obj==NULL)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
      return false;
     }
   if(!this.m_list_deleted_obj.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_DEL_LIST);
      delete obj;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

El método traslada a la lista de objetos eliminados el objeto cuyo índice ha sido transmitido al método desde la lista de colecciones.
Aquí, extraemos un objeto de la lista según el índice especificado. Si no hemos podido recuperar el objeto, lo comunicamos y retornamos false.
Además, si el objeto no se ha podido colocar en la lista de objetos eliminados, lo comunicamos, eliminamos el objeto y retornamos false.
Como resultado, retornaremos true.

Método que traslada el objeto especificado de la clase de objeto gráfico a la lista de objetos gráficos eliminados:

//+------------------------------------------------------------------+
//| Move the specified graphical object class object                 |
//| to the list of removed graphical objects                         |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::MoveGraphObjToDeletedObjList(CGStdGraphObj *obj)
  {
   this.m_list_all_graph_obj.Sort();
   int index=this.m_list_all_graph_obj.Search(obj);
   return this.MoveGraphObjToDeletedObjList(index);
  }
//+------------------------------------------------------------------+

El método traslada el objeto especificado por el puntero a la lista de objetos gráficos remotos.
Aquí, asignamos a la lista de colección la bandera de lista clasificada y obtenemos el índice del objeto en la lista usando el método Search().
Usando el método encargado de trasladar un objeto según su índice en la lista que hemos analizado anteriormente, trasladamos el objeto a la lista de objetos gráficos eliminados.

Método que retorna un objeto gráfico remoto según el nombre e ID del gráfico:

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

El nombre del objeto y el ID del gráfico son transmitidos al método. A continuación, obtenemos una lista de objetos según el ID de gráfico; de la lista resultante, obtenemos una lista de objetos según su nombre (solo debe haber uno de esos objetos). Si obtenemos una lista y su tamaño es superior a cero, retornaremos el puntero al único objeto de la lista ubicado en el índice 0. De lo contrario, retornaremos NULL: no ha sido posible obtener el objeto.

A continuación, eliminamos del manejador de eventos el bloque de código responsable de procesar los eventos de los objetos gráficos; lo trasladaremos al manejador de eventos del asesor:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj=NULL;
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   if(id==CHARTEVENT_OBJECT_CHANGE  || id==CHARTEVENT_OBJECT_DRAG    || id==CHARTEVENT_OBJECT_CLICK   ||
      idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG   || idx==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Calculate the chart ID
      //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID
      //--- If the event ID corresponds to a user event, the chart ID is received from lparam
      //--- Otherwise, the chart ID is assigned to -1
      long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE);
      long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param);
      //--- Get the object, whose properties were changed or which was relocated,
      //--- from the collection list by its name set in sparam
      obj=this.GetStdGraphObject(sparam,chart_id);
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      if(obj==NULL)
        {
         //--- Let's search the list for the object that is not on the chart
         obj=this.FindMissingObj(chart_id);
         //--- If failed to find the object here as well, exit
         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(chart_id);
         //--- Send an event with the old name of an object to the control program chart and
         //--- set a new name for the collection list object, which does not correspond to any graphical object on the chart
         ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj.ChartID(),0,obj.Name());
         obj.SetName(name_new);
        }
      //--- Update the properties of the obtained object
      //--- and check their change
      obj.PropertiesRefresh();
      obj.PropertiesCheckChanged();
     }
//--- Handle standard graphical object events
   if(idx>GRAPH_OBJ_EVENT_NO_EVENT && idx<GRAPH_OBJ_EVENTS_NEXT_CODE)
     {
      //--- Depending on the event type, display an appropriate message in the journal
      switch(idx)
        {
         case GRAPH_OBJ_EVENT_CREATE   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE));
           obj=this.GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
              obj.PrintShort();
           break;
         case GRAPH_OBJ_EVENT_CHANGE   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE));
           obj=this.GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
              obj.PrintShort();
           break;
         case GRAPH_OBJ_EVENT_RENAME   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME));
           obj=this.GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
              obj.PrintShort();
           break;
         case GRAPH_OBJ_EVENT_DELETE   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE));
           break;
         case GRAPH_OBJ_EVENT_DEL_CHART:
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART),": ChartID: ",lparam,", ChartSymbol: ",sparam);
           break;
         default:
           break;
        }
     }
  }
//+------------------------------------------------------------------+

En el propio manejador, corregiremos la lógica de envío de mensajes sobre el cambio de nombre de un objeto gráfico: si el objeto tiene un nombre nuevo, entonces, en este caso, enviaremos un evento al gráfico del programa de control:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj=NULL;
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   if(id==CHARTEVENT_OBJECT_CHANGE  || id==CHARTEVENT_OBJECT_DRAG    || id==CHARTEVENT_OBJECT_CLICK   ||
      idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG   || idx==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Calculate the chart ID
      //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID
      //--- If the event ID corresponds to a user event, the chart ID is received from lparam
      //--- Otherwise, the chart ID is assigned to -1
      long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE);
      long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param);
      //--- Get the object, whose properties were changed or which was relocated,
      //--- from the collection list by its name set in sparam
      obj=this.GetStdGraphObject(sparam,chart_id);
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      if(obj==NULL)
        {
         //--- Let's search the list for the object that is not on the chart
         obj=this.FindMissingObj(chart_id);
         //--- If failed to find the object here as well, exit
         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(chart_id);
         //--- set a new name for the collection list object, which does not correspond to any graphical object on the chart,
         //--- and send an event with the new name of the object to the control program chart
         if(obj.SetNamePrev(obj.Name()) && obj.SetName(name_new))
            ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj.ChartID(),obj.TimeCreate(),obj.Name());
        }
      //--- Update the properties of the obtained object
      //--- and check their change
      obj.PropertiesRefresh();
      obj.PropertiesCheckChanged();
     }
  }
//+------------------------------------------------------------------+


Método que muestra en el diario la historia de cambio de nombre:

//+------------------------------------------------------------------+
//| Display the history of renaming the object to the journal        |
//+------------------------------------------------------------------+
void CGraphElementsCollection::PrintRenameHistory(const string name,const long chart_id)
  {
   CGStdGraphObj *obj=this.GetStdGraphObject(name,chart_id);
   if(obj==NULL)
      return;
   obj.PrintRenameHistory();
  }
//+------------------------------------------------------------------+

Transmitimos al método el nombre del objeto gráfico y el identificador del gráfico en el que se encuentra.
Obtenemos el puntero a un objeto según el nombre y el ID del gráfico y llamamos a su método encargado de mostrar en el diario la historia de cambios de nombre de los objetos usando el método PrintRenameHistory() de la clase de objeto gráfico estándar abstracto que analizamos anteriormente.

Para trabajar cómodamente con el programa, necesitaremos añadir algunos métodos a la clase principal de la biblioteca CEngine.

Para ello, abriremos el archivo \MQL5\Include\DoEasy\Engine.mqh y añadiremos un método que retornará la lista de objetos gráficos eliminados, un método que retornará la cantidad de objetos gráficos eliminados y un método que retornará el tamaño de la matriz de la propiedad especificada:

//--- 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 (1) collection of graphical objects and (2) the list of removed objects
   CGraphElementsCollection *GetGraphicObjCollection(void)              { return &this.m_graph_objects;                       }
   CArrayObj           *GetListDeletedObj(void)                         { return this.m_graph_objects.GetListDeletedObj();    }
//--- Return (1) the number of removed graphical objects and (2) the size of the property array
   int                  TotalDeletedGraphObjects(void)                  { return this.GetListDeletedObj().Total();            }
   int                  GraphGetSizeProperty(const string name,const long chart_id,const int prop)
                          {
                           return this.m_graph_objects.GetSizeProperty(name,chart_id,prop);
                          }
   
//--- Fill in the array with IDs of the charts opened in the terminal

Estos métodos retornarán el resultado devuelto por los métodos de la clase de colección de objetos gráficos con el mismo nombre, que, consideramos, no necesitan descripción.


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\Part91\ con el nuevo nombre TestDoEasyPart91.mq5.

Todo lo que debemos hacer es añadir al manejador OnChartEvent() del asesor el bloque de código para procesar los eventos de objetos gráficos que eliminamos del 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();
           }
         //--- Re-draw the chart
         ChartRedraw();
        }
     }
   */
   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         long array[];
         engine.GraphGetArrayChartsID(array);
         for(int i=0;i<ArraySize(array);i++)
            engine.CreateLineVertical(array[i],"LineVertical",0,time);
        }
     }
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);

//--- Handle standard graphical object events
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   CGStdGraphObj *obj=NULL;
   if(idx>GRAPH_OBJ_EVENT_NO_EVENT && idx<GRAPH_OBJ_EVENTS_NEXT_CODE)
     {
      CChartObjectsControl *chart_ctrl=NULL;
      int end=0;
      string evn="";
      //--- Depending on the event type, display an appropriate message in the journal
      switch(idx)
        {
         //--- Graphical object creation event
         case GRAPH_OBJ_EVENT_CREATE   :
           //--- Display the message about creating a new graphical object
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE),":");
           //--- Get the pointer to the object by chart name and ID passed to sparam and lparam, respectively
           //--- and display the short description of a newly created object to the journal
           obj=engine.GetGraphicObjCollection().GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              obj.PrintShort();
             }
           break;
         //--- Event of changing the graphical object property
         case GRAPH_OBJ_EVENT_CHANGE   :
           //--- Display the message about changing the graphical object property
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE),":");
           //--- Get the pointer to the object by chart name and ID passed to sparam and lparam, respectively
           obj=engine.GetGraphicObjCollection().GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              //--- Display a short description of the changed object in the journal
              obj.PrintShort();
              //--- calculate the code of the changed property passed to dparam and get the property description
              if(dparam<GRAPH_OBJ_PROP_INTEGER_TOTAL)
                 evn=obj.GetPropertyDescription((ENUM_GRAPH_OBJ_PROP_INTEGER)dparam);
              else if(dparam<GRAPH_OBJ_PROP_DOUBLE_TOTAL)
                 evn=obj.GetPropertyDescription((ENUM_GRAPH_OBJ_PROP_DOUBLE)dparam);
              else
                 evn=obj.GetPropertyDescription((ENUM_GRAPH_OBJ_PROP_STRING)dparam);
              //--- Display the description of the graphical object's changed property in the journal
              Print(DFUN,evn);
             }
           break;
         //--- Graphical object renaming event
         case GRAPH_OBJ_EVENT_RENAME   :
           //--- Display the message about renaming the graphical object
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME));
           //--- Get the pointer to the object by chart name and ID passed to sparam and lparam, respectively
           obj=engine.GetGraphicObjCollection().GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              //--- Display the previous and new object name, as well as its entire renaming history, in the journal
              Print(DFUN,obj.GetProperty(GRAPH_OBJ_PROP_NAME,obj.Properties().CurrSize(GRAPH_OBJ_PROP_NAME)-1)," >>> ",obj.GetProperty(GRAPH_OBJ_PROP_NAME,0));
              obj.PrintRenameHistory();
             }
           break;
         //--- Graphical object deletion event
         case GRAPH_OBJ_EVENT_DELETE   :
           //--- Display the message about removing the graphical object
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE),":");
           //--- Get the pointer to the removed object by chart name and ID passed to sparam and lparam, respectively
           //--- and display a short description of the removed object in the journal
           obj=engine.GetGraphicObjCollection().GetStdDelGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              obj.PrintShort();
             }
           break;
         //--- Event of removing the graphical object together with the chart window
         case GRAPH_OBJ_EVENT_DEL_CHART:
           //--- Display the message about removing graphical objects together with the chart window, whose ID and symbol are passed to lparam and sparam
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART),": #",lparam,", ",sparam,":");
           //--- Calculate the end value for the loop by the list of removed graphical objects
           end=engine.TotalDeletedGraphObjects()-(int)dparam;
           if(end<0)
              end=0;
           //--- In the loop from the end of the removed graphical objects list up to the value calculated in the 'end' variable,
           for(int i=engine.TotalDeletedGraphObjects()-1;i>=end;i--)
             {
              //--- get the next removed graphical object from the list
              obj=engine.GetListDeletedObj().At(i);
              if(obj==NULL)
                 continue;
              //--- and display its brief description in the journal
              obj.PrintShort();
             }
           break;
         //---
         default:
           break;
        }
     }
  }
//+------------------------------------------------------------------+

La lógica en el bloque del código añadido se ha descrito con gran detalle, por lo que no tiene sentido insistir en ella.
En cualquier caso, el lector podrá exponer cualquier duda en los comentarios al artículo.

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


Como podemos ver, el cambio de nombre de un objeto se almacena en su "memoria".

La eliminación por paquetes de los objetos gráficos también se procesa correctamente:


Nos gustaría señalar que después de eliminar la ventana del gráfico y restaurarla posteriormente, no todos los objetos gráficos eliminados junto con el gráfico se restauran correctamente en la lista de colección de objetos gráficos. Aún no sabemos por qué se omiten los objetos, pero definitivamente encontraremos este error y lo solucionaremos.

¿Qué es lo próximo?

En el próximo artículo, continuaremos trabajando en los eventos de los objetos gráficos y comenzaremos a crear la funcionalidad necesaria para guardar en las propiedades del objeto la historia de cambio de sus propiedades.

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 86): Colección de objetos gráficos - controlando la modificación de propiedades
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 88): Colección de objetos gráficos - matriz dinámica bidimensional para almacenar propiedades de objetos que cambian dinámicamente
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

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

Archivos adjuntos |
MQL5.zip (4181.64 KB)
Valoración visual de los resultados de optimización Valoración visual de los resultados de optimización
La conversación en este artículo se centrará en cómo crear gráficos para todas las pasadas de optimización y elegir el criterio personalizado óptimo. Y también sobre cómo, teniendo un conocimiento mínimo de MQL5 y un gran ánimo de trabajar, usando los artículos del sitio y los comentarios en el foro, podremos escribir lo que queramos.
Matrices y vectores en MQL5 Matrices y vectores en MQL5
La matriz y el vector de tipos de datos especiales nos permiten escribir un código próximo a la notación matemática. Esto elimina la necesidad de crear ciclos anidados y recordar la indexación correcta de las matrices que participan en los cálculos, aumentando la fiabilidad y la velocidad del desarrollo de programas complejos.
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 92): Clase de memoria de objetos gráficos estándar Historia de cambio de propiedades del objeto
En este artículo, crearemos la clase de memoria del objeto gráfico estándar, que permitirá al objeto guardar sus estados al modificarse sus propiedades, lo que a su vez le permitirá volver a los estados anteriores del objeto gráfico en cualquier momento.
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 90): Eventos de objetos gráficos estándar. Funcionalidad básica
En este artículo, crearemos la funcionalidad básica para el seguimiento de eventos de objetos gráficos estándar. Empezaremos con el evento de doble clic sobre un objeto gráfico.