English Русский 中文 Deutsch 日本語 Português
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

MetaTrader 5Ejemplos | 14 febrero 2022, 10:25
1 094 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

Al trabajar con los objetos gráficos que nos ofrece el terminal de cliente, a veces nos encontramos con tareas que implican la definición de algunos valores de las propiedades de estos objetos mediante programación. El programa puede monitorear el valor de cualquier línea del objeto gráfico y ejecutar el algoritmo incorporado en él, cuando, por ejemplo, es cruzado por el precio. Una línea puede desplazarse cuando el usuario mueve el objeto gráfico. Por consiguiente, el programa debe reaccionar a ello. Pero el programa debe saber que el valor de la línea que se está monitoreando ha cambiado, o bien debe sondear constantemente los valores de las propiedades del objeto gráfico, lo cual supone una tarea con gran gasto de recursos, como bien informa la ayuda del usuario.

Resulta mucho más conveniente disponer de una función que nos avise sobre los cambios en cualquier objeto gráfico, y el terminal tiene esta función: se trata del manejador de eventos OnChartEvent(). Vamos a añadir a su arsenal algunos eventos que nos informarán en nuestra biblioteca sobre lo que ha ocurrido exactamente con el objeto gráfico, mientras que el programa que trabaja bajo el control de la biblioteca sabrá exactamente lo que ha pasado con el objeto gráfico y sabrá por sus propiedades qué propiedad ha sido cambiada.

Vamos a dividir el trabajo de creación de la funcionalidad descrita en dos pasos: primero crearemos los eventos generales que pueden suceder a los objetos gráficos, y luego añadiremos la funcionalidad que especifica lo que ha ocurrido exactamente con el objeto y permite averiguarlo rápidamente de forma programática. En principio, ya estamos listos para crear tal funcionalidad; solo necesitamos mejorar un poco las clases de la biblioteca y crear un lugar en el manejador de eventos en el que se procesarán los eventos que ocurran a los objetos gráficos.

Los eventos básicos de los objetos gráficos se definen de la forma siguiente:

  • Creación de un nuevo objeto gráfico,
  • Cambio en las propiedades de un objeto gráfico,
  • Cambio de nombre de un objeto gráfico,
  • Eliminación de un objeto gráfico,
  • Eliminación de un objeto gráfico junto con la ventana gráfica.

Estos son los eventos que implementaremos hoy. Estos serán enviados al manejador OnChartEvent(). En el próximo artículo, crearemos un manejador para cada uno de estos eventos de manera que podamos saber exactamente qué propiedades del objeto gráfico han sido modificadas.

Debemos tener en cuenta que el cambio en el nombre de un objeto gráfico supone a su vez un cambio en las propiedades del objeto gráfico, es decir, el cambio de la propiedad "Nombre". No obstante, hemos decidido convertirlo en un evento aparte para simplificar el procesamiento, ya que el cambio de nombre desencadena varios eventos seguidos: la eliminación de un objeto gráfico, la creación de uno nuevo y el cambio de sus propiedades. Todos los estados mencionados ya los hemos procesado en la biblioteca, y esta calcula correctamente el evento de nuevo nombramiento. Por lo tanto, lo enviaremos para el posterior procesamiento programático del evento.

Mientras que la eliminación de un objeto gráfico nos permite averiguar exactamente qué objeto se ha eliminado e informar sobre su nombre, al eliminar una ventana de gráfico con objetos gráficos en ella, en cambio, solo podremos conocer el número de objetos gráficos en el gráfico eliminado en este momento. Por el momento, estos dos eventos solo nos comunicarán que los objetos gráficos han sido eliminados. En el futuro, tal vez consideremos la utilidad de recordar las propiedades de los objetos eliminados. Por ahora, no vemos necesidad urgente de recordar las propiedades de los objetos eliminados.


Mejorando las clases de la biblioteca

Para monitorear la selección de un objeto gráfico, deberemos añadir el procesamiento de eventos de clic en el objeto gráfico. Clicando dos veces en un objeto gráfico no seleccionado, se elegirá para su edición, y clicando dos veces en el objeto seleccionado, este se deseleccionará. Para procesar un evento de este tipo en el gráfico actual, basta con añadir al manejador de eventos un nuevo ID de evento; sin embargo, para procesar un evento de este tipo en los demás gráficos, deberemos mejorar el indicador de control de eventos que hemos establecido automáticamente para cada ventana de gráfico recién abierta.

Vamos a abrir el archivo de este indicador \MQL5\IndicadoresDoEasy\EventControl.mq5 y añadir el nuevo ID del evento que será enviado al gráfico del programa de control:

//+------------------------------------------------------------------+
//|                                                 EventControl.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0
//--- input parameters
input long     InpChartSRC = 0;
input long     InpChartDST = 0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator shortname
   IndicatorSetString(INDICATOR_SHORTNAME,"EventSend_From#"+(string)InpChartSRC+"_To#"+(string)InpChartDST);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   return rates_total;
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK)
     {
      EventChartCustom(InpChartDST,(ushort)id,InpChartSRC,dparam,sparam);
     }
  }
//+------------------------------------------------------------------+


En el archivo \MQL5\Include\DoEasy\Data.mqh, escribimos 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_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_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_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 textos de los mensajes 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"},
   
   {"Создан индикатор контроля и отправки событий","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 "},
   {"Закрыто окон графиков: ","Closed chart windows: "},
   {"С ними удалено объектов: ","Objects removed with them: "},
   {"Не удалось создать объект-событие для графического объекта","Failed to create event object for graphic object"},
   {"Не удалось добавить объект-событие в список","Failed to add event object to list"},
   
   {"Создан новый графический объект","New graphic object created"},
   {"Изменено свойство графического объекта","Changed graphic object property"},
   {"Графический объект переименован","Graphic object renamed"},
   {"Графический объект удалён","Graphic object deleted"},
   {"Графический объект удалён вместе с графиком","The graphic object has been removed along with the chart"},
   
  };
//+---------------------------------------------------------------------+


Introducimos las correciones en el archivo \MQL5\Include\DoEasy\Defines.mqh.

De la lista de posibles eventos de objetos gráficos, eliminamos el evento de desplazamiento del objeto, ya que este cambio de propiedad está ya presente en la enumeración de eventos de objetos gráficos:

//+------------------------------------------------------------------+
//| List of possible graphical object events                         |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_EVENT
  {
   GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,// No event
   GRAPH_OBJ_EVENT_CREATE,                            // "Creating a new graphical object" event
   GRAPH_OBJ_EVENT_CHANGE,                            // "Changing graphical object properties" event
   GRAPH_OBJ_EVENT_MOVE,                              // "Moving graphical object" event
   GRAPH_OBJ_EVENT_RENAME,                            // "Renaming graphical object" event
   GRAPH_OBJ_EVENT_DELETE,                            // "Removing graphical object" event
  };

Y añadimos una nueva constante a esta lista para el evento de eliminación de un objeto gráfico junto con el gráfico:

//+------------------------------------------------------------------+
//| Data for handling graphical elements                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of possible graphical object events                         |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_EVENT
  {
   GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,// No event
   GRAPH_OBJ_EVENT_CREATE,                            // "Creating a new graphical object" event
   GRAPH_OBJ_EVENT_CHANGE,                            // "Changing graphical object properties" event
   GRAPH_OBJ_EVENT_RENAME,                            // "Renaming graphical object" event
   GRAPH_OBJ_EVENT_DELETE,                            // "Removing graphical object" event
   GRAPH_OBJ_EVENT_DEL_CHART,                         // "Removing a graphical object together with the chart window" event
  };
#define GRAPH_OBJ_EVENTS_NEXT_CODE  (GRAPH_OBJ_EVENT_DEL_CHART+1) // The code of the next event after the last graphical object event code
//+------------------------------------------------------------------+

El siguiente código de evento después de los códigos de evento de los objetos gráficos se calculará ahora a partir del último valor añadido a la enumeración.


Al crear cualquier objeto gráfico estándar, leeremos en su constructor los datos del objeto físico del gráfico y los escribiremos en las propiedades del objeto de la clase. Algunas de las propiedades de los objetos gráficos las hemos escrito como comunes para todos los objetos gráficos: cada objeto las posee, y están escritas en la clase básica de todos los objetos gráficos de la biblioteca.

El algoritmo es el siguiente: primero leemos los parámetros de un objeto gráfico usando las funciones estándar ObjectGetXXX (Integer, Double y String) y luego, si los datos se han obtenido con éxito, estos parámetros se escribirán primero en la propiedad del objeto de clase del objeto gráfico básico CGBaseObj, y luego en las propiedades de su descendiente. Para los objetos gráficos estándar es la clase CGStdGraphObj.

Aquí es donde nos encontramos con un conflicto. Disponemos de métodos para establecer los parámetros del objeto gráfico. Estos métodos deben asignar el valor de la propiedad que se les transmite tanto al propio objeto gráfico como a la propiedad correspondiente del objeto de clase, si asignamos con éxito un parámetro a un objeto gráfico usando las funciones ObjectSetXXX (Integer, Double y String). Sin embargo, a veces solo necesitamos establecer en una propiedad de la clase un valor ya conocido del parámetro de un objeto gráfico. Y para ello, no necesitamos volver a leer ese valor ni asignarlo a un objeto gráfico, sino simplemente asignar su valor a una variable de la clase. Nuestros métodos Set del objeto gráfico básico de la biblioteca primero establecen el valor en los parámetros del objeto gráfico, y solo entonces los escriben en las variables de la clase. Para ahorrarnos la tarea innecesaria de establecer de nuevo una propiedad ya conocida del objeto gráfico en el objeto, y solo registrarla en la variable de la clase, vamos a añadir a tales métodos la variable bool only_prop, que indicará la necesidad de establecer un valor ya sea solo en los valores de las variables, o bien tanto en los parámetros del objeto gráfico, como en las propiedades del objeto de la clase. Si esta variable de entrada es true, los parámetros se escribirán solo en las variables de clase; de lo contrario, el valor se establecerá primero en el objeto gráfico y luego en las variables de la clase.

En la clase objeto básico de todos los objetos gráficos de la biblioteca \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, vamos a añadir esta variable a dichos métodos y a cambiar la lógica de los mismos:

//--- Set the "Background object" flag
   bool              SetFlagBack(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_BACK,flag)) || only_prop)
                          {
                           this.m_back=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }

Aquí, si la variable tiene el valor false y al objeto gráfico se le ha asignado la propiedad, o si la variable tiene el valor true,
añadimos el valor transmitido al método en la variable de clase .

Todos los métodos de esta clase se han mejorado de la forma siguiente:

//--- Set the "Background object" flag
   bool              SetFlagBack(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_BACK,flag)) || only_prop)
                          {
                           this.m_back=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set the "Object selection" flag
   bool              SetFlagSelected(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTED,flag)) || only_prop)
                          {
                           this.m_selected=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set the "Object selection" flag
   bool              SetFlagSelectable(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop)
                          {
                           this.m_selectable=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set the "Disable displaying the name of a graphical object in the terminal object list" flag
   bool              SetFlagHidden(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop)
                          {
                           this.m_hidden=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set the priority of a graphical object for receiving the event of clicking on a chart 
   bool              SetZorder(const long value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop)
                          {
                           this.m_zorder=value;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set object visibility on all timeframes
   bool              SetVisible(const bool flag,const bool only_prop)   
                       {
                        long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop)
                          {
                           this.m_visible=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set visibility flags on timeframes specified as flags
   bool              SetVisibleOnTimeframes(const int flags,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,flags)) || only_prop)
                          {
                           this.m_timeframes_visible=flags;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Add the visibility flag on a specified timeframe
   bool              SetVisibleOnTimeframe(const ENUM_TIMEFRAMES timeframe,const bool only_prop)
                       {
                        int flags=this.m_timeframes_visible;
                        switch(timeframe)
                          {
                           case PERIOD_M1    : flags |= OBJ_PERIOD_M1;  break;
                           case PERIOD_M2    : flags |= OBJ_PERIOD_M2;  break;
                           case PERIOD_M3    : flags |= OBJ_PERIOD_M3;  break;
                           case PERIOD_M4    : flags |= OBJ_PERIOD_M4;  break;
                           case PERIOD_M5    : flags |= OBJ_PERIOD_M5;  break;
                           case PERIOD_M6    : flags |= OBJ_PERIOD_M6;  break;
                           case PERIOD_M10   : flags |= OBJ_PERIOD_M10; break;
                           case PERIOD_M12   : flags |= OBJ_PERIOD_M12; break;
                           case PERIOD_M15   : flags |= OBJ_PERIOD_M15; break;
                           case PERIOD_M20   : flags |= OBJ_PERIOD_M20; break;
                           case PERIOD_M30   : flags |= OBJ_PERIOD_M30; break;
                           case PERIOD_H1    : flags |= OBJ_PERIOD_H1;  break;
                           case PERIOD_H2    : flags |= OBJ_PERIOD_H2;  break;
                           case PERIOD_H3    : flags |= OBJ_PERIOD_H3;  break;
                           case PERIOD_H4    : flags |= OBJ_PERIOD_H4;  break;
                           case PERIOD_H6    : flags |= OBJ_PERIOD_H6;  break;
                           case PERIOD_H8    : flags |= OBJ_PERIOD_H8;  break;
                           case PERIOD_H12   : flags |= OBJ_PERIOD_H12; break;
                           case PERIOD_D1    : flags |= OBJ_PERIOD_D1;  break;
                           case PERIOD_W1    : flags |= OBJ_PERIOD_W1;  break;
                           case PERIOD_MN1   : flags |= OBJ_PERIOD_MN1; break;
                           default           : return true;
                          }
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,flags)) || only_prop)
                          {
                           this.m_timeframes_visible=flags;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }

Ahora podemos elegir cómo escribir la propiedad: o bien asignamos una nueva propiedad al objeto gráfico y a la variable de clase (para que se corresponda con el nuevo valor establecido), o bien asignamos el valor ya conocido del parámetro del objeto gráfico a la variable de clase (como se hace en el constructor de la clase al crear un nuevo objeto gráfico). Esto nos permitirá evitar referencias innecesarias a funciones bastante lentas de trabajo con objetos gráficos, debido a su naturaleza sincrónica.

En el fichero \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, hemos corregido la llamada a los métodos mejorados:

//--- Return (1) the element ID, (2) element index in the list, (3) flag of the form shadow presence and (4) the chart background color
   int               ID(void)                            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ID);                   }
   int               Number(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                  }
   bool              IsShadow(void)                      const { return this.m_shadow;                                                 }
   color             ChartColorBackground(void)          const { return this.m_chart_color_bg;                                         }
//--- Set the object above all
   void              BringToTop(void)                          { CGBaseObj::SetVisible(false,false); CGBaseObj::SetVisible(true,false);}
//--- (1) Show and (2) hide the element
   virtual void      Show(void)                                { CGBaseObj::SetVisible(true,false);                                    }
   virtual void      Hide(void)                                { CGBaseObj::SetVisible(false,false);                                   }
   
//+------------------------------------------------------------------+
//| The methods of receiving raster data                             |
//+------------------------------------------------------------------+

En el archivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, corregimos igualmente los dos métodos:

//+------------------------------------------------------------------+
//| Create the shadow object                                         |
//+------------------------------------------------------------------+
void CForm::CreateShadowObj(const color colour,const uchar opacity)
  {
//--- If the shadow flag is disabled or the shadow object already exists, exit
   if(!this.m_shadow || this.m_shadow_obj!=NULL)
      return;

//--- Calculate the shadow object coordinates according to the offset from the top and left
   int x=this.CoordX()-OUTER_AREA_SIZE;
   int y=this.CoordY()-OUTER_AREA_SIZE;
//--- Calculate the width and height in accordance with the top, bottom, left and right offsets
   int w=this.Width()+OUTER_AREA_SIZE*2;
   int h=this.Height()+OUTER_AREA_SIZE*2;
//--- Create a new shadow object and set the pointer to it in the variable
   this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h);
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ));
      return;
     }
//--- Set the properties for the created shadow object
   this.m_shadow_obj.SetID(this.ID());
   this.m_shadow_obj.SetNumber(-1);
   this.m_shadow_obj.SetOpacityShadow(opacity);
   this.m_shadow_obj.SetColorShadow(colour);
   this.m_shadow_obj.SetMovable(true);
   this.m_shadow_obj.SetActive(false);
   this.m_shadow_obj.SetVisible(false,false);
//--- Move the form object to the foreground
   this.BringToTop();
  }
//+------------------------------------------------------------------+
//| Draw the shadow                                                  |
//+------------------------------------------------------------------+
void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=4)
  {
//--- If the shadow flag is disabled, exit
   if(!this.m_shadow)
      return;
//--- If there is no shadow object, create it
   if(this.m_shadow_obj==NULL)
      this.CreateShadowObj(colour,opacity);
//--- If the shadow object exists, draw the shadow on it,
//--- set the shadow object visibility flag and
//--- move the form object to the foreground
   if(this.m_shadow_obj!=NULL)
     {
      this.m_shadow_obj.DrawShadow(shift_x,shift_y,blur);
      this.m_shadow_obj.SetVisible(true,false);
      this.BringToTop();
     }
  }
//+------------------------------------------------------------------+


En la clase de objeto gráfico estándar, hacemos que todos los métodos para establecer las propiedades de los objetos gráficos tengan un valor de retorno de tipo booleano (actualmente los métodos no retornan ningún valor y, por lo tanto, son de tipo void). Esto es necesario para asegurarnos de que los métodos correspondientes de la clase padre que también tienen el tipo de retorno bool han sido reasignados en la clase hija, y para que al escribir el código no haya dos métodos en una pista (con el tipo bool del objeto básico, y con el tipo void de su descendiente), lo cual podría provocar confusiones.

Vamos a analizar el método para establecer el número de subventana:

//--- Chart subwindow index
   int               SubWindow(void)               const { return (int)this.GetProperty(GRAPH_OBJ_PROP_WND_NUM,0);                        }
   bool              SetSubWindow(void)
                       {
                        if(!CGBaseObj::SetSubwindow(CGBaseObj::ChartID(),CGBaseObj::Name()))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow());
                        return true;
                       }

Como el número de subventana se busca según el identificador del gráfico y el nombre del objeto, entonces transferiremos al método de la clase básica para establecer el número de subventana el identificador del gráfico ya almacenado en el objeto básico y el nombre del objeto gráfico. Si no se ha conseguido establecer un valor en el objeto básico, retornaremos false; en caso contrario, estableceremos en la propiedad del objeto el valor que se acaba de escribir en el objeto básico, y retornaremos true.

Y en el ejemplo del método para establecer la visibilidad de un objeto en todos los marcos temporales:

//--- Object visibility on timeframes
   bool              Visible(void)                 const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0);                    }
   bool              SetFlagVisible(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetVisible(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,flag);
                        return true;
                       }

Aquí, transmitimos el nuevo parámetro al método que hemos añadido antes para especificar en qué valores escribiremos: solo en la propiedad del objeto, o en los parámetros del objeto gráfico y en las propiedades de la clase. A continuación, establecemos estas propiedades en el objeto básico, y si no ha sido posible, retornaremos false.
De lo contrario, escribiremos el valor en la propiedad del objeto de la clase y retornaremos true.

Todos los demás métodos han sido mejorados de forma idéntica a los anteriores:

//--- Background object
   bool              Back(void)                    const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0);                          }
   bool              SetFlagBack(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagBack(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BACK,0,flag);
                        return true;
                       }
//--- Priority of a graphical object for receiving the event of clicking on a chart
   long              Zorder(void)                  const { return this.GetProperty(GRAPH_OBJ_PROP_ZORDER,0);                              }
   bool              SetZorder(const long value,const bool only_prop)
                       {
                        if(!CGBaseObj::SetZorder(value,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,value);
                        return true;
                       }
//--- Disable displaying the name of a graphical object in the terminal object list
   bool              Hidden(void)                  const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0);                        }
   bool              SetFlagHidden(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagHidden(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,flag);
                        return true;
                       }
//--- Object selection
   bool              Selected(void)                const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,0);                      }
   bool              SetFlagSelected(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagSelected(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_SELECTED,0,flag);
                        return true;
                       }
//--- Object availability
   bool              Selectable(void)              const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,0);                    }
   bool              SetFlagSelectable(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagSelectable(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_SELECTABLE,0,flag);
                        return true;
                       }
//--- Time coordinate
   datetime          Time(const int modifier)      const { return (datetime)this.GetProperty(GRAPH_OBJ_PROP_TIME,modifier);               }
   bool              SetTime(const datetime time,const int modifier)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_TIME,modifier,time))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_TIME,modifier,time);
                        return true;
                       }
//--- Color
   color             Color(void)                   const { return (color)this.GetProperty(GRAPH_OBJ_PROP_COLOR,0);                        }
   bool              SetColor(const color colour)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_COLOR,colour))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_COLOR,0,colour);
                        return true;
                       }
//--- Style
   ENUM_LINE_STYLE   Style(void)                   const { return (ENUM_LINE_STYLE)this.GetProperty(GRAPH_OBJ_PROP_STYLE,0);              }
   bool              SetStyle(const ENUM_LINE_STYLE style)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_STYLE,style))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_STYLE,0,style);
                        return true;
                       }
//--- Line width
   int               Width(void)                   const { return (int)this.GetProperty(GRAPH_OBJ_PROP_WIDTH,0);                          }
   bool              SetWidth(const int width)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_WIDTH,width))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_WIDTH,0,width);
                        return true;
                       }
//--- Object color filling
   bool              Fill(void)                    const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_FILL,0);                          }
   bool              SetFlagFill(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_FILL,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_FILL,0,flag);
                        return true;
                       }
//--- Ability to edit text in the Edit object
   bool              ReadOnly(void)                const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_READONLY,0);                      }
   bool              SetFlagReadOnly(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_READONLY,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_READONLY,0,flag);
                        return true;
                       }
//--- Number of levels
   int               Levels(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_LEVELS,0);                         }
   bool              SetLevels(const int levels)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELS,levels))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELS,0,levels);
                        return true;
                       }
//--- Line level color
   color             LevelColor(const int modifier)   const { return (color)this.GetProperty(GRAPH_OBJ_PROP_LEVELCOLOR,modifier);         }
   bool              SetLevelColor(const color colour,const int modifier)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELCOLOR,modifier,colour))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELCOLOR,modifier,colour);
                        return true;
                       }
//--- Level line style
   ENUM_LINE_STYLE   LevelStyle(const int modifier)const { return (ENUM_LINE_STYLE)this.GetProperty(GRAPH_OBJ_PROP_LEVELSTYLE,modifier);  }
   bool              SetLevelStyle(const ENUM_LINE_STYLE style,const int modifier)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELSTYLE,modifier,style))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELSTYLE,modifier,style);
                        return true;
                       }
///--- Level line width
   int               LevelWidth(const int modifier)const { return (int)this.GetProperty(GRAPH_OBJ_PROP_LEVELWIDTH,modifier);              }
   bool              SetLevelWidth(const int width,const int modifier)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELWIDTH,modifier,width))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELWIDTH,modifier,width);
                        return true;
                       }
//--- Horizontal text alignment in the Edit object (OBJ_EDIT)
   ENUM_ALIGN_MODE   Align(void)                   const { return (ENUM_ALIGN_MODE)this.GetProperty(GRAPH_OBJ_PROP_ALIGN,0);              }
   bool              SetAlign(const ENUM_ALIGN_MODE align)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ALIGN,align))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ALIGN,0,align);
                        return true;
                       }
//--- Font size
   int               FontSize(void)                const { return (int)this.GetProperty(GRAPH_OBJ_PROP_FONTSIZE,0);                       }
   bool              SetFontSize(const int size)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_FONTSIZE,size))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_FONTSIZE,0,size);
                        return true;
                       }
//--- Ray goes to the left
   bool              RayLeft(void)                 const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_RAY_LEFT,0);                      }
   bool              SetFlagRayLeft(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_RAY_LEFT,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_RAY_LEFT,0,flag);
                        return true;
                       }
//--- Ray goes to the right
   bool              RayRight(void)                const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,0);                     }
   bool              SetFlagRayRight(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_RAY_RIGHT,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,0,flag);
                        return true;
                       }
//--- Vertical line goes through all windows of a chart
   bool              Ray(void)                     const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_RAY,0);                           }
   bool              SetFlagRay(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_RAY,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_RAY,0,flag);
                        return true;
                       }
//--- Display the full ellipse of the Fibonacci Arc object
   bool              Ellipse(void)                 const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_ELLIPSE,0);                       }
   bool              SetFlagEllipse(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ELLIPSE,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ELLIPSE,0,flag);
                        return true;
                       }
//--- Arrow code for the "Arrow" object
   uchar             ArrowCode(void)               const { return (uchar)this.GetProperty(GRAPH_OBJ_PROP_ARROWCODE,0);                    }
   bool              SetArrowCode(const uchar code)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ARROWCODE,code))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ARROWCODE,0,code);
                        return true;
                       }
//--- Position of the graphical object anchor point
   int               Anchor(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,0);                         }
   bool              SetAnchor(const int anchor)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ANCHOR,anchor))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ANCHOR,0,anchor);
                        return true;
                       }
//--- Distance from the base corner along the X axis in pixels
   int               XDistance(void)               const { return (int)this.GetProperty(GRAPH_OBJ_PROP_XDISTANCE,0);                      }
   bool              SetXDistance(const int distance)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_XDISTANCE,distance))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_XDISTANCE,0,distance);
                        return true;
                       }
//--- Distance from the base corner along the Y axis in pixels
   int               YDistance(void)               const { return (int)this.GetProperty(GRAPH_OBJ_PROP_YDISTANCE,0);                      }
   bool              SetYDistance(const int distance)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_YDISTANCE,distance))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_YDISTANCE,0,distance);
                        return true;
                       }
//--- Gann object trend
   ENUM_GANN_DIRECTION Direction(void)             const { return (ENUM_GANN_DIRECTION)this.GetProperty(GRAPH_OBJ_PROP_DIRECTION,0);      }
   bool              SetDirection(const ENUM_GANN_DIRECTION direction)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DIRECTION,direction))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_DIRECTION,0,direction);
                        return true;
                       }
//--- Elliott wave marking level
   ENUM_ELLIOT_WAVE_DEGREE Degree(void)            const { return (ENUM_ELLIOT_WAVE_DEGREE)this.GetProperty(GRAPH_OBJ_PROP_DEGREE,0);     }
   bool              SetDegree(const ENUM_ELLIOT_WAVE_DEGREE degree)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DEGREE,degree))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_DEGREE,0,degree);
                        return true;
                       }
//--- Display lines for Elliott wave marking
   bool              DrawLines(void)               const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_DRAWLINES,0);                     }
   bool              SetFlagDrawLines(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DRAWLINES,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_DRAWLINES,0,flag);
                        return true;
                       }
//--- Button state (pressed/released)
   bool              State(void)                   const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_STATE,0);                         }
   bool              SetFlagState(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_STATE,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_STATE,0,flag);
                        return true;
                       }
//--- Chart object ID (OBJ_CHART)
   long              ChartObjChartID(void)         const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,0);                  }
   bool              SetChartObjChartID(const long chart_id)
                       {
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,0,chart_id);
                        return true;
                       }
//--- Chart object period
   ENUM_TIMEFRAMES   ChartObjPeriod(void)          const { return (ENUM_TIMEFRAMES)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,0);   }
   bool              SetChartObjPeriod(const ENUM_TIMEFRAMES timeframe)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_PERIOD,timeframe))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,0,timeframe);
                        return true;
                       }
//--- Time scale display flag for the Chart object
   bool              ChartObjDateScale(void)       const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,0);          }
   bool              SetFlagChartObjDateScale(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DATE_SCALE,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,0,flag);
                        return true;
                       }
//--- Price scale display flag for the Chart object
   bool              ChartObjPriceScale(void)      const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,0);         }
   bool              SetFlagPriceScale(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_PRICE_SCALE,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,0,flag);
                        return true;
                       }
//--- Chart object scale
   int               ChartObjChartScale(void)      const { return (int)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,0);          }
   bool              SetChartObjChartScale(const int scale)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_CHART_SCALE,scale))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,0,scale);
                        return true;
                       }
//--- Object width along the X axis in pixels
   int               XSize(void)                   const { return (int)this.GetProperty(GRAPH_OBJ_PROP_XSIZE,0);                          }
   bool              SetXSize(const int size)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_XSIZE,size))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_XSIZE,0,size);
                        return true;
                       }
//--- Object height along the Y axis in pixels
   int               YSize(void)                   const { return (int)this.GetProperty(GRAPH_OBJ_PROP_YSIZE,0);                          }
   bool              SetYSize(const int size)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_YSIZE,size))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_YSIZE,0,size);
                        return true;
                       }
//--- X coordinate of the upper-left corner of the visibility area
   int               XOffset(void)                 const { return (int)this.GetProperty(GRAPH_OBJ_PROP_XOFFSET,0);                        }
   bool              SetXOffset(const int offset)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_XOFFSET,offset))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_XOFFSET,0,offset);
                        return true;
                       }
//--- Y coordinate of the upper-left corner of the visibility area
   int               YOffset(void)                 const { return (int)this.GetProperty(GRAPH_OBJ_PROP_YOFFSET,0);                        }
   bool              SetYOffset(const int offset)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_YOFFSET,offset))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_YOFFSET,0,offset);
                        return true;
                       }
//--- Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   color             BGColor(void)                 const { return (color)this.GetProperty(GRAPH_OBJ_PROP_BGCOLOR,0);                      }
   bool              SetBGColor(const color colour)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BGCOLOR,colour))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BGCOLOR,0,colour);
                        return true;
                       }
//--- Chart corner for attaching a graphical object
   ENUM_BASE_CORNER  Corner(void)                  const { return (ENUM_BASE_CORNER)this.GetProperty(GRAPH_OBJ_PROP_CORNER,0);            }
   bool              SetCorner(const ENUM_BASE_CORNER corner)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_CORNER,corner))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CORNER,0,corner);
                        return true;
                       }
//--- Border type for the Rectangle label object
   ENUM_BORDER_TYPE  BorderType(void)              const { return (ENUM_BORDER_TYPE)this.GetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,0);       }
   bool              SetBorderType(const ENUM_BORDER_TYPE type)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BORDER_TYPE,type))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,0,type);
                        return true;
                       }
//--- Border color for OBJ_EDIT and OBJ_BUTTON
   color             BorderColor(void)             const { return (color)this.GetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,0);                 }
   bool              SetBorderColor(const color colour)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BORDER_COLOR,colour))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,0,colour);
                        return true;
                       }

//--- Price coordinate
   double            Price(const int modifier)     const { return this.GetProperty(GRAPH_OBJ_PROP_PRICE,modifier);                        }
   bool              SetPrice(const double price,const int modifier)
                       {
                        if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_PRICE,modifier,price))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_PRICE,modifier,price);
                        return true;
                       }
//--- Level value
   double            LevelValue(const int modifier)const { return this.GetProperty(GRAPH_OBJ_PROP_LEVELVALUE,modifier);                   }
   bool              SetLevelValue(const double value,const int modifier)
                       {
                        if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELVALUE,modifier,value))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELVALUE,modifier,value);
                        return true;
                       }
//--- Scale
   double            Scale(void)                   const { return this.GetProperty(GRAPH_OBJ_PROP_SCALE,0);                               }
   bool              SetScale(const double scale)
                       {
                        if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SCALE,scale))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_SCALE,0,scale);
                        return true;
                       }
//--- Angle
   double            Angle(void)                   const { return this.GetProperty(GRAPH_OBJ_PROP_ANGLE,0);                               }
   bool              SetAngle(const double angle)
                       {
                        if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ANGLE,angle))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ANGLE,0,angle);
                        return true;
                       }
//--- Deviation of the standard deviation channel
   double            Deviation(void)               const { return this.GetProperty(GRAPH_OBJ_PROP_DEVIATION,0);                           }
   bool              SetDeviation(const double deviation)
                       {
                        if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DEVIATION,deviation))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_DEVIATION,0,deviation);
                        return true;
                       }

//--- Object name
   string            Name(void)                    const { return this.GetProperty(GRAPH_OBJ_PROP_NAME,0);                                }
   bool              SetName(const string name)
                       {
                        if(CGBaseObj::Name()==name)
                           return true;
                        if(CGBaseObj::Name()=="")
                          {
                           CGBaseObj::SetName(name);
                           this.SetProperty(GRAPH_OBJ_PROP_NAME,0,name);
                           return true;
                          }
                        else
                          {
                           if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_NAME,name))
                              return false;
                           CGBaseObj::SetName(name);
                           this.SetProperty(GRAPH_OBJ_PROP_NAME,0,name);
                           return true;
                          }
                       }
//--- Object description (text contained in the object)
   string            Text(void)                    const { return this.GetProperty(GRAPH_OBJ_PROP_TEXT,0);                                }
   bool              SetText(const string text)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_TEXT,text))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_TEXT,0,text);
                        return true;
                       }
//--- Tooltip text
   string            Tooltip(void)                 const { return this.GetProperty(GRAPH_OBJ_PROP_TOOLTIP,0);                             }
   bool              SetTooltip(const string tooltip)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_TOOLTIP,tooltip))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_TOOLTIP,0,tooltip);
                        return true;
                       }
//--- Level description
   string            LevelText(const int modifier) const { return this.GetProperty(GRAPH_OBJ_PROP_LEVELTEXT,modifier);                    }
   bool              SetLevelText(const string text,const int modifier)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELTEXT,modifier,text))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELTEXT,modifier,text);
                        return true;
                       }
//--- Font
   string            Font(void)                    const { return this.GetProperty(GRAPH_OBJ_PROP_FONT,0);                                }
   bool              SetFont(const string font)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_FONT,font))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_FONT,0,font);
                        return true;
                       }
//--- BMP file name for the "Bitmap Level" object
   string            BMPFile(const int modifier)   const { return this.GetProperty(GRAPH_OBJ_PROP_BMPFILE,modifier);                      }
   bool              SetBMPFile(const string bmp_file,const int modifier)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BMPFILE,bmp_file))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BMPFILE,modifier,bmp_file);
                        return true;
                       }
//--- Symbol for the Chart object 
   string            ChartObjSymbol(void)          const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0);                    }
   bool              SetChartObjSymbol(const string symbol)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0,symbol);
                        return true;
                       }

En el método que recupera y guarda las propiedades de tipo entero, primero escribimos los datos que tenemos en el objeto básico, en las propiedades del objeto básico, y luego, partiendo de ahí, escribimos los valores en las propiedades de la clase de objeto gráfico:

//+------------------------------------------------------------------+
//| Get and save the integer properties                              |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveINT(void)
  {
   //--- Properties inherent in all graphical objects and present in a graphical object
   CGBaseObj::SetVisibleOnTimeframes((int)::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES),true);   // Write Object visibility on timeframes to the base object
   CGBaseObj::SetFlagBack(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK),true);                         // Write Background object flag to the base object
   CGBaseObj::SetZorder(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER),true);                         // Write Priority of a graphical object for receiving the event of clicking on a chart to the base object
   CGBaseObj::SetFlagHidden(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN),true);                     // Write Disable displaying the name of a graphical object in the terminal object list to the base object
   CGBaseObj::SetFlagSelectable(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE),true);             // Write Object availability to the base object
   CGBaseObj::SetFlagSelected(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED),true);                 // Write Object selection to the base object
   
   this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,CGBaseObj::VisibleOnTimeframes());                                   // Object visibility on timeframes
   this.SetProperty(GRAPH_OBJ_PROP_BACK,0,CGBaseObj::IsBack());                                                      // Background object
   this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,CGBaseObj::Zorder());                                                    // Priority of a graphical object for receiving the event of clicking on a chart
   this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,CGBaseObj::IsHidden());                                                  // Disable displaying the name of a graphical object in the terminal object list
   this.SetProperty(GRAPH_OBJ_PROP_SELECTABLE,0,CGBaseObj::IsSelectable());                                          // Object availability
   this.SetProperty(GRAPH_OBJ_PROP_SELECTED,0,CGBaseObj::IsSelected());                                              // Object selection
   
   this.SetProperty(GRAPH_OBJ_PROP_CREATETIME,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME));  // Object creation time
   for(int i=0;i<this.m_pivots;i++)                                                                                  // Point time coordinates
      this.SetTimePivot(i);
   this.SetProperty(GRAPH_OBJ_PROP_COLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR));            // Color
   this.SetProperty(GRAPH_OBJ_PROP_STYLE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE));            // Style
   this.SetProperty(GRAPH_OBJ_PROP_WIDTH,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH));            // Line width
   //--- Properties belonging to different graphical objects
   this.SetProperty(GRAPH_OBJ_PROP_FILL,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL));              // Fill an object with color
   this.SetProperty(GRAPH_OBJ_PROP_READONLY,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY));      // Ability to edit text in the Edit object
   this.SetProperty(GRAPH_OBJ_PROP_LEVELS,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS));          // Number of levels
   
   if(this.GetProperty(GRAPH_OBJ_PROP_LEVELS,0)!=this.GetPropertyPrev(GRAPH_OBJ_PROP_LEVELS,0))                      // Check if the number of levels has changed
     {
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,this.Levels());
     }
   for(int i=0;i<this.Levels();i++)                                                                                  // Level data
     {
      this.SetLevelColor(i);
      this.SetLevelStyle(i);
      this.SetLevelWidth(i);
     }
   this.SetProperty(GRAPH_OBJ_PROP_ALIGN,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN));            // Horizontal text alignment in the Edit object (OBJ_EDIT)
   this.SetProperty(GRAPH_OBJ_PROP_FONTSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE));      // Font size
   this.SetProperty(GRAPH_OBJ_PROP_RAY_LEFT,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT));      // Ray goes to the left
   this.SetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT));    // Ray goes to the right
   this.SetProperty(GRAPH_OBJ_PROP_RAY,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY));                // Vertical line goes through all windows of a chart
   this.SetProperty(GRAPH_OBJ_PROP_ELLIPSE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE));        // Display the full ellipse of the Fibonacci Arc object
   this.SetProperty(GRAPH_OBJ_PROP_ARROWCODE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE));    // Arrow code for the "Arrow" object
   this.SetProperty(GRAPH_OBJ_PROP_ANCHOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR));          // Position of the binding point of the graphical object
   this.SetProperty(GRAPH_OBJ_PROP_XDISTANCE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE));    // Distance from the base corner along the X axis in pixels
   this.SetProperty(GRAPH_OBJ_PROP_YDISTANCE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE));    // Distance from the base corner along the Y axis in pixels
   this.SetProperty(GRAPH_OBJ_PROP_DIRECTION,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION));    // Gann object trend
   this.SetProperty(GRAPH_OBJ_PROP_DEGREE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE));          // Elliott wave marking level
   this.SetProperty(GRAPH_OBJ_PROP_DRAWLINES,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES));    // Display lines for Elliott wave marking
   this.SetProperty(GRAPH_OBJ_PROP_STATE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE));            // Button state (pressed/released)
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID));// Chart object ID (OBJ_CHART).
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD));    // Chart object period
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE));  // Time scale display flag for the Chart object
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE));// Price scale display flag for the Chart object
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE));// Chart object scale
   this.SetProperty(GRAPH_OBJ_PROP_XSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE));            // Object width along the X axis in pixels.
   this.SetProperty(GRAPH_OBJ_PROP_YSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE));            // Object height along the Y axis in pixels.
   this.SetProperty(GRAPH_OBJ_PROP_XOFFSET,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET));        // X coordinate of the upper-left corner of the visibility area.
   this.SetProperty(GRAPH_OBJ_PROP_YOFFSET,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET));        // Y coordinate of the upper-left corner of the visibility area.
   this.SetProperty(GRAPH_OBJ_PROP_BGCOLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR));        // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.SetProperty(GRAPH_OBJ_PROP_CORNER,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER));          // Chart corner for binding a graphical object
   this.SetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE));// Border type for "Rectangle border"
   this.SetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR));// Border color for OBJ_EDIT and OBJ_BUTTON
  }
//+------------------------------------------------------------------+

De este modo, rellenaremos todas las propiedades que estén duplicadas en la clase básica y su descendiente. De lo contrario, al obtener las propiedades del objeto, se usarán los métodos del objeto básico, y los datos del objeto gráfico no se escribirán en ellos.

Analizaremos algunas otras mejoras al crear la funcionalidad para el seguimiento de eventos de objetos gráficos estándar.


Eventos de objetos gráficos estándar

La lógica de trabajo con los eventos de objetos gráficos se basará en el siguiente concepto: para cada objeto ya podemos definir los eventos que ocurren a sus propiedades. Vamos a crear una pequeña clase de evento básico de objeto gráfico, y al definir cualquier evento sucedido con un objeto un gráfico, crearemos un nuevo objeto de evento, en el que se escribirá:

  • El identificador del evento,
  • El identificador del gráfico en el que se encuentra el objeto gráfico donde se ha producido el evento,
  • El nombre del objeto en el que se ha producido el evento.

En general, un evento personalizado enviado al gráfico especificado por la función EventChartCustom(), tiene cinco parámetros:

//+------------------------------------------------------------------+
bool  EventChartCustom(
   long    chart_id,            // event receiving chart ID
   ushort  custom_event_id,     // event ID
   long    lparam,              // long parameter
   double  dparam,              // double parameter
   string  sparam               // event string parameter
   );
//+------------------------------------------------------------------+

De estos, necesitaremos rellenar custom_event_id (aquí especificaremos el ID del evento del objeto gráfico), lparam (aquí especificaremos el ID del gráfico en el que se ubica el objeto gráfico en el que ha ocurrido el evento) y sparam (aquí especificaremos el nombre del objeto gráfico). Disponemos de otro parámetro libre, dparam, en el que indicaremos el valor de la constante de la propiedad modificada cuando este cambia, o el número de objetos gráficos borrados que se han eliminado junto con una ventana cerrada del gráfico.

Para cada evento del objeto, crearemos un objeto de evento y lo colocaremos en la lista de eventos. Después de comprobar las propiedades del objeto en busca de cambios, tendremos una lista completa con todos los eventos ocurridos en el objeto gráfico. Una vez completados los ciclos de comprobación de todas las propiedades de los objetos, recorreremos la lista de eventos creada y enviaremos cada uno de ellos al gráfico del programa de control, donde se llamará al manejador de eventos OnChartEvent() de la clase de colección de elementos gráficos. En este manejador, podemos procesar cada evento enviado desde la lista creada de eventos de objeto gráfico. Por consiguiente, cada uno de estos eventos contendrá el identificador del evento, el identificador del gráfico donde se encuentra el objeto gráfico modificado y su nombre. Esto nos permitirá localizar ese objeto gráfico, mientras que el valor de la constante de la propiedad modificada nos indicará exactamente qué propiedad ha sido cambiada, por lo que podremos obtener el puntero a ese objeto en la colección y leer el nuevo valor de la propiedad modificada. Esto nos permitirá localizar tanto el objeto gráfico en sí como la propiedad cambiada, de manera que podremos procesar el evento en el programa de la forma que se ha implementado en su lógica.

En esta ocasión, solo crearemos eventos y los enviaremos al manejador de eventos de la colección de objetos gráficos. En el próximo artículo, descompondremos estos eventos en sus diferentes componentes.

Vamos a escribir la nueva clase del evento básico de los objetos gráficos de la biblioteca al incio del archivo del objeto gráfico básico \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh:

//+------------------------------------------------------------------+
//|                                                     GBaseObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Services\DELib.mqh"
#include <Graphics\Graphic.mqh>
//+------------------------------------------------------------------+
//| Graphical object event class                                     |
//+------------------------------------------------------------------+
class CGBaseEvent : public CObject
  {
private:
   ushort            m_id;
   long              m_lparam;
   double            m_dparam;
   string            m_sparam;
public:
   void              ID(ushort id)                          { this.m_id=id;         }
   ushort            ID(void)                         const { return this.m_id;     }
   void              LParam(const long value)               { this.m_lparam=value;  }
   long              Lparam(void)                     const { return this.m_lparam; }
   void              DParam(const double value)             { this.m_dparam=value;  }
   double            Dparam(void)                     const { return this.m_dparam; }
   void              SParam(const string value)             { this.m_sparam=value;  }
   string            Sparam(void)                     const { return this.m_sparam; }
   bool              Send(const long chart_id)
                       {
                        ::ResetLastError();
                        return ::EventChartCustom(chart_id,m_id,m_lparam,m_dparam,m_sparam);
                       }
                     CGBaseEvent (const ushort event_id,const long lparam,const double dparam,const string sparam) : 
                        m_id(event_id),m_lparam(lparam),m_dparam(dparam),m_sparam(sparam){}
                    ~CGBaseEvent (void){}
  };
//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject

En la sección privada de la clase, declaramos las variables de miembro de clase para guardar todas las propiedades anteriores del objeto de evento, y en la sección pública, declaramos los métodos para establecer y retornar los valores de estas variables y el método para enviar un evento de usuario al gráfico especificado.
Todos los valores que se asignan a las variables de la clase se transmiten al constructor de la clase, y se asignan a las variables de inmediato en la lista de inicialización del constructor.

En la sección protegida de la clase del objeto gráfico básico, declaramos una lista para almacenar los eventos del objeto y escribimos los métodos para trabajar con la lista y los eventos del objeto gráfico:

//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject
  {
protected:
   CArrayObj         m_list_events;                      // Object event list
   ENUM_OBJECT       m_type_graph_obj;                   // Graphical object type
   ENUM_GRAPH_ELEMENT_TYPE m_type_element;               // Graphical element type
   ENUM_GRAPH_OBJ_BELONG m_belong;                       // Program affiliation
   ENUM_GRAPH_OBJ_GROUP m_group;                         // Graphical object group
   string            m_name_prefix;                      // Object name prefix
   string            m_name;                             // Object name
   long              m_chart_id;                         // Object chart ID
   long              m_object_id;                        // Object ID
   long              m_zorder;                           // Priority of a graphical object for receiving the mouse click event
   int               m_subwindow;                        // Subwindow index
   int               m_shift_y;                          // Subwindow Y coordinate shift
   int               m_type;                             // Object type
   int               m_timeframes_visible;               // Visibility of an object on timeframes (a set of flags)
   int               m_digits;                           // Number of decimal places in a quote
   bool              m_visible;                          // Object visibility
   bool              m_back;                             // "Background object" flag
   bool              m_selected;                         // "Object selection" flag
   bool              m_selectable;                       // "Object availability" flag
   bool              m_hidden;                           // "Disable displaying the name of a graphical object in the terminal object list" flag
   datetime          m_create_time;                      // Object creation time
   
//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void)                      { return true; }
   virtual void      StructToObject(void){;}

//--- Return the list of object events
   CArrayObj        *GetListEvents(void)                       { return &this.m_list_events;          }
//--- Create a new object event
   CGBaseEvent      *CreateNewEvent(const ushort event_id,const long lparam,const double dparam,const string sparam)
                       {
                        CGBaseEvent *event=new CGBaseEvent(event_id,lparam,dparam,sparam);
                        return event;
                       }
//--- Create a new object event and add it to the event list
   bool              CreateAndAddNewEvent(const ushort event_id,const long lparam,const double dparam,const string sparam)
                       {
                        return this.AddEvent(new CGBaseEvent(event_id,lparam,dparam,sparam));
                       }
//--- Add an event object to the event list
   bool              AddEvent(CGBaseEvent *event)              { return this.m_list_events.Add(event);}
//--- Clear the event list
   void              ClearEventsList(void)                     { this.m_list_events.Clear();          }
//--- Return the number of events in the list
   int               EventsTotal(void)                         { return this.m_list_events.Total();   }

public:

Todos los métodos escritos son bastante sencillos, así que esperamos que no planteen ninguna duda. En cualquier caso, podrán escribir cualquier duda en los comentarios al artículo.

En el constructor de la clase, limpiamos la lista de eventos del objeto y le asignamos la bandera de lista clasificada:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_shift_y(0),m_visible(false), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_"),m_belong(GRAPH_OBJ_BELONG_PROGRAM)
  {
   this.m_list_events.Clear();                  // Clear the event list
   this.m_list_events.Sort();                   // Sorted list flag
   this.m_type=OBJECT_DE_TYPE_GBASE;            // Object type
   this.m_type_graph_obj=WRONG_VALUE;           // Graphical object type
   this.m_type_element=WRONG_VALUE;             // Graphical object type
   this.m_belong=WRONG_VALUE;                   // Program/terminal affiliation
   this.m_name_prefix="";                       // Object name prefix
   this.m_name="";                              // Object name
   this.m_chart_id=0;                           // Object chart ID
   this.m_object_id=0;                          // Object ID
   this.m_zorder=0;                             // Priority of a graphical object for receiving the mouse click event
   this.m_subwindow=0;                          // Subwindow index
   this.m_shift_y=0;                            // Subwindow Y coordinate shift
   this.m_timeframes_visible=OBJ_ALL_PERIODS;   // Visibility of an object on timeframes (a set of flags)
   this.m_visible=true;                         // Object visibility
   this.m_back=false;                           // "Background object" flag
   this.m_selected=false;                       // "Object selection" flag
   this.m_selectable=false;                     // "Object availability" flag
   this.m_hidden=true;                          // "Disable displaying the name of a graphical object in the terminal object list" flag
   this.m_create_time=0;                        // Object creation time
   
  }
//+------------------------------------------------------------------+


En la clase de objeto gráfico estándar \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh, en el método que comprueba el cambio en las propiedades del objeto, limpiamos la lista de eventos, escribimos la creación y la adición de los objetos de evento y su envío al manejador de eventos de la colección de objetos gráficos de la lista creada :

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

Aquí, en un ciclo por todas las propiedades del objeto (de tipo entero, real y string), comprobamos los cambios en cada una de las siguientes propiedades del objeto y, si hay un cambio, creamos un objeto de evento y lo añadimos a la lista de eventos del objeto. Entonces, si hay al menos un cambio, obtenemos cada objeto de evento en un ciclo por la lista creada y lo enviamos al gráfico del programa de control. Estos eventos llegan al manejador OnChartEvent() de la clase de colección de elementos gráficos y son procesados allí.

Vamosa abrir el archivo de la clase de colección de elementos gráficos \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, y mejorar ligeramente el método de la clase control de objetos gráficos encargado de comprobar los objetos en el gráfico:

//+------------------------------------------------------------------+
//| CChartObjectsControl: Check objects on a chart                   |
//+------------------------------------------------------------------+
void CChartObjectsControl::Refresh(void)
  {
//--- Graphical 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)
     {
      //--- find the last added graphical object, select it and write its name
      string name=this.LastAddedGraphObjName();
      //--- Handle only non-programmatically created objects
      if(name!="" && ::StringFind(name,m_name_program)==WRONG_VALUE)
        {
         //--- 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(obj==NULL)
           {
            CMessage::ToLog(DFUN,MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ);
            return;
           }
         //--- Set the object affiliation and add the created object to the list of new objects
         obj.SetBelong(GRAPH_OBJ_BELONG_NO_PROGRAM); 
         if(this.m_list_new_graph_obj.Search(obj)==WRONG_VALUE)
           {
            //--- If failed to add the object to the list, inform of that, delete the object and leave
            if(!this.m_list_new_graph_obj.Add(obj))
              {
               CMessage::ToLog(DFUN_ERR_LINE,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
               delete obj;
               return;
              }
            //--- Send an event to the control program chart
            ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_CREATE,this.ChartID(),0,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;
  }
//+------------------------------------------------------------------+

Aquí, hemos añadido la muestra de mensajes sobre los errores en el diario, y también enviado el evento de creación de objetos al gráfico del programa de control.

En la sección pública de la clase de colección de elementos gráficos, añadimos un método que retorna la lista con los objetos de control del gráfico:

//--- Return the number of new graphical objects, (3) the flag of the occurred change in the list of graphical objects
   int               NewObjects(void)   const                                                            { return this.m_delta_graph_obj;       }
   bool              IsEvent(void) const                                                                 { return this.m_is_graph_obj_event;    }
//--- Return a graphical object by chart name and ID
   CGStdGraphObj    *GetStdGraphObject(const string name,const long chart_id);
//--- Return the chart management object list
   CArrayObj        *GetListChartsControl(void)                                                          { return &this.m_list_charts_control;  }
//--- Constructor
                     CGraphElementsCollection();


Como en los métodos para crear objetos gráficos estándar que hemos creado en el último artículo, tenemos un bloque de código repetitivo bastante grande, lo moveremos a un método privado aparte:

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

private:
//--- Create a new graphical object, return the pointer to the chart management object
   CChartObjectsControl *CreateNewStdGraphObjectAndGetCtrlObj(const long chart_id,
                                                              const string name,
                                                              int subwindow,
                                                              const ENUM_OBJECT type_object,
                                                              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)
                       {
                        //--- If an object with a chart ID and name is already present in the collection, inform of that and return NULL
                        if(this.IsPresentGraphObjInList(chart_id,name))
                          {
                           ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_GR_OBJ_ALREADY_EXISTS)," ChartID ",(string)chart_id,", ",name);
                           return NULL;
                          }
                        //--- If failed to create a new standard graphical object, inform of that and return NULL
                        if(!this.CreateNewStdGraphObject(chart_id,name,type_object,subwindow,time1,0))
                          {
                           ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ),StdGraphObjectTypeDescription(type_object));
                           CMessage::ToLog(::GetLastError(),true);
                           return NULL;
                          }
                        //--- If failed to get a chart management object, inform of that
                        CChartObjectsControl *ctrl=this.GetChartObjectCtrlObj(chart_id);
                        if(ctrl==NULL)
                           ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_CTRL_OBJ),(string)chart_id);
                        //--- Return the pointer to a chart management object or NULL in case of a failed attempt to get it
                        return ctrl;
                       }
//--- Add a newly created object to the list
   bool              AddCreatedObjToList(const string source,const long chart_id,const string name,CGStdGraphObj *obj)
                       {
                        bool res=true;
                        if(!this.m_list_all_graph_obj.Add(obj))
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                           ::ObjectDelete(chart_id,name);
                           delete obj;
                           res=false;
                          }
                        ::ChartRedraw(chart_id);
                        return res;
                       }
public:
//--- Create the "Vertical line" graphical object

Y a continuación, en todos los métodos encargados de crear objetos gráficos estándar, retornamos el resultado de este método:

public:
//--- Create the "Vertical line" graphical object
   bool              CreateLineVertical(const long chart_id,const string name,const int subwindow,const datetime time)
                       {
                        //--- Set the name and type of a created object
                        string nm=this.m_name_program+"_"+name;
                        ENUM_OBJECT type_object=OBJ_VLINE;
                        //--- Create a new graphical object and get the pointer to the chart management object
                        CChartObjectsControl *ctrl=this.CreateNewStdGraphObjectAndGetCtrlObj(chart_id,nm,subwindow,type_object,time,0);
                        if(ctrl==NULL)
                           return false;
                        //--- Create a new class object corresponding to the newly created graphical object
                        CGStdVLineObj *obj=ctrl.CreateNewGraphObj(type_object,nm);
                        if(obj==NULL)
                          {
                           ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ),StdGraphObjectTypeDescription(type_object));
                           return false;
                          }
                        //--- Set the necessary minimal parameters for an object
                        obj.SetBelong(GRAPH_OBJ_BELONG_PROGRAM);
                        obj.SetFlagSelectable(true,false);
                        obj.SetFlagSelected(true,false);
                        obj.SetObjectID(this.GetFreeGraphObjID(true));
                        obj.PropertiesCopyToPrevData();
                        //--- Return the result of adding the object to the list
                        return this.AddCreatedObjToList(DFUN,chart_id,nm,obj);
                       }

Aquí, escribiremos las banderas necesarias para que el método cambiado hoy (encargado de establecer los valores de las propiedades de un objeto gráfico) funcione correctamente.

Hemos modificado de la misma manera el resto de métodos para crear objetos gráficos estándar, así que no los analizaremos aquí. El lector los encontrará en los archivos adjuntos al artículo.

En el método que crea un nuevo objeto para controlar los objetos gráficos del gráfico especificado y que añade este objeto a la lista, añadimos la muestra de mensajes en el diario cuando el indicador de control de eventos es agregado al gráfico:

//+------------------------------------------------------------------+
//| Create a new graphical object management object                  |
//| for a specified and add it to the list                           |
//+------------------------------------------------------------------+
CChartObjectsControl *CGraphElementsCollection::CreateChartObjectCtrlObj(const long chart_id)
  {
//--- Create a new object for managing chart objects by ID
   CChartObjectsControl *obj=new CChartObjectsControl(chart_id);
//--- If the object is not created, inform of the error and return NULL
   if(obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ),(string)chart_id);
      return NULL;
     }
//--- If failed to add the object to the list, inform of the error, remove the object and return NULL
   if(!this.m_list_charts_control.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      delete obj;
      return NULL;
     }
   if(obj.ChartID()!=CBaseObj::GetMainChartID() && obj.CreateEventControlInd(CBaseObj::GetMainChartID()))
      if(!obj.AddEventControlInd())
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_EVN_CTRL_INDICATOR);
         CMessage::ToLog(DFUN,::GetLastError(),true);
        }
      else
         ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_ADDED_EVN_CTRL_INDICATOR),obj.Symbol()," #",obj.ChartID());
//--- Return the pointer to the object that was created and added to the list
   return obj;
  }
//+------------------------------------------------------------------+


En el método que actualiza la lista de todos los objetos gráficos, en lugar de la muestra simple de un objeto gráfico eliminado que se encuentra en la lista de colección, ahora enviaremos un evento sobre el objeto gráfico eliminado:

//+------------------------------------------------------------------+
//| 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)
           {
            // Find an extra object in the list
            CGStdGraphObj *obj=this.FindMissingObj(chart_id);
            if(obj!=NULL)
              {
               //--- Send an event to the control program chart
               ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,obj.ChartID(),0,obj.Name());
               //--- Remove the class object of a removed graphical object from the collection list
               if(!this.DeleteGraphObjFromList(obj))
                  CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
              }
           }
        }
      //--- Increase the loop index
      i++;
     }
  }
//+------------------------------------------------------------------+

En el evento, en su parámetro de línea, transmitimos el nombre del objeto remoto. Es muy posible que luego guardemos los objetos eliminados en una lista aparte, de forma que cuando recibamos un evento de eliminación, podamos ver qué objeto se ha eliminado y qué propiedades tenía y, si fuera necesario, restaurarlo mediante programación.

En el método que procesa la eliminación de una ventana del gráfico, en lugar de un simple mensaje al diario, ahora enviaremos un evento al gráfico del programa de control:

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

Aquí, transmitimos en los parámetros del evento el identificador del gráfico cerrado, el número de objetos gráficos eliminados junto con él y el símbolo del gráfico cerrado.

En el manejador de eventos de la clase de colección de elementos gráficos, añadimos el procesamiento de eventos básicos de los objetos gráficos:

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

La lógica del método se describe en los comentarios al código. Aquí, hemos añadido una reacción al evento de clic en el gráfico, y si ha habido un doble clic, el estado de la selección del objeto cambiará, lo que provocará la aparición de un evento sobre el cambio de los parámetros del objeto. Así, podremos procesar los eventos de selección y deselección del objeto.
Al final, el mismo método recibe los eventos ya procesados del objeto gráfico, para los que se han creado los eventos básicos. Estos eventos básicos se procesan en un nuevo bloque de código. Hasta ahora, aquí solo teníamos la muestra de mensajes en el diario. En futuros artículos, crearemos el procesamiento completo de cada uno de estos eventos para que el programa pueda reconocer el objeto modificado y acceder rápidamente tanto a este como a sus propiedades.


Para simplificar el trabajo con objetos gráficos, crearemos los métodos necesarios para trabajar con objetos gráficos en la clase principal de la biblioteca CEngine en \MQL5\Include\DoEasy\Engine.mqh.

Para determinar qué gráficos tenemos abiertos al usar las clases de objetos gráficos estándar, podemos buscar en la lista de objetos de control de gráficos. En esta lista, podemos tomar de los objetos los identificadores de todos los gráficos abiertos, con lo cual será posible referirse a ellos. Para simplificar la recuperación de los identificadores en el programa, crearemos un método que rellenará la matriz long transmitida a este con los valores de los identificadores de los gráficos abiertos para los que se han creado los objetos de control:

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

//--- Return the graphical object collection
   CGraphElementsCollection *GetGraphicObjCollection(void)              { return &this.m_graph_objects; }
   
//--- Fill in the array with IDs of the charts opened in the terminal
   void              GraphGetArrayChartsID(long &array_charts_id[])
                       {
                        CArrayObj *list=this.m_graph_objects.GetListChartsControl();
                        if(list==NULL)
                           return;
                        ::ArrayResize(array_charts_id,list.Total());
                        ::ArrayInitialize(array_charts_id,WRONG_VALUE);
                        for(int i=0;i<list.Total();i++)
                          {
                           CChartObjectsControl *obj=list.At(i);
                           if(obj==NULL)
                              continue;
                           array_charts_id[i]=obj.ChartID();
                          }
                       }

//--- Create the "Vertical line" graphical object

Aquí, fijamos inmediatamente un tamaño para la matriz transmitida al método igual al tamaño de la lista de objetos de control de gráficos e inicializamos la matriz con valores -1. Esto es necesario si se produce un error al obtener un objeto de control de gráficos en el ciclo, de forma que la celda de la matriz correspondiente al índice del objeto de control de gráficos que no se ha podido obtener contenga el valor -1, que serviría como señal de error de obtención de este objeto.
Por supuesto, podemos aumentar el tamaño de la matriz en el ciclo solo si el objeto se obtiene con éxito, pero llamar a ArrayResize() dentro del ciclo no resulta rápido. Por eso, la matriz se incrementa inmediatamente según el tamaño de la lista, y en caso de error, la celda correspondiente de la matriz contendrá -1.
Luego, en un ciclo sobre la lista recibida, obtenemos el siguiente objeto de control gráfico, y escribimos el identificador del gráfico (contenido en el objeto de control de gráficos) en la celda de la matriz que se corresponde con el índice del ciclo.

Para facilitar el acceso a los métodos de creación de objetos gráficos estándar, vamos a escribir los métodos necesarios para su creación, dos métodos para cada objeto. El primer método llamará al método de clase homónimo de la colección de elementos gráficos para crear el objeto gráfico estándar
. y el segundo llamará al primer método con el ID del gráfico actual:

//--- Create the "Vertical line" graphical object
   bool              CreateLineVertical(const long chart_id,const string name,const int subwindow,const datetime time)
                       { return this.m_graph_objects.CreateLineVertical(chart_id,name,subwindow,time); }
   bool              CreateLineVertical(const string name,const int subwindow,const datetime time)
                       { return this.CreateLineVertical(::ChartID(),name,subwindow,time); }

//--- Create the "Horizontal line" graphical object
   bool              CreateLineHorizontal(const long chart_id,const string name,const int subwindow,const double price)
                       { return this.m_graph_objects.CreateLineHorizontal(chart_id,name,subwindow,price); }
   bool              CreateLineHorizontal(const string name,const int subwindow,const double price)
                       { return this.CreateLineHorizontal(::ChartID(),name,subwindow,price); }
 
//--- Create the "Trend line" graphical object
   bool              CreateLineTrend(const long chart_id,const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateLineTrend(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateLineTrend(const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateLineTrend(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Create the "Trend line by angle" graphical object
   bool              CreateLineTrendByAngle(const long chart_id,const string name,const int subwindow,
                                            const datetime time1,const double price1,const datetime time2,const double price2,
                                            const double angle)
                       { return this.m_graph_objects.CreateLineTrendByAngle(chart_id,name,subwindow,time1,price1,time2,price2,angle); }
   bool              CreateLineTrendByAngle(const string name,const int subwindow,
                                            const datetime time1,const double price1,const datetime time2,const double price2,
                                            const double angle)
                       { return this.CreateLineTrendByAngle(::ChartID(),name,subwindow,time1,price1,time2,price2,angle); }

//--- Create the "Cyclic lines" graphical object
   bool              CreateLineCycle(const long chart_id,const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateLineCycle(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateLineCycle(const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateLineCycle(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Create the "Arrowed line" graphical object
   bool              CreateLineArrowed(const long chart_id,const string name,const int subwindow,
                                       const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateLineArrowed(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateLineArrowed(const string name,const int subwindow,
                                       const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateLineArrowed(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Create the "Equidistant channel" graphical object
   bool              CreateChannel(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const datetime time3,const double price3)
                       { return this.m_graph_objects.CreateChannel(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreateChannel(const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const datetime time3,const double price3)
                       { return this.CreateChannel(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Create the "Standard deviation channel" graphical object
   bool              CreateChannelStdDeviation(const long chart_id,const string name,const int subwindow,
                                               const datetime time1,const double price1,const datetime time2,const double price2,
                                               const double deviation=1.5)
                       { return this.m_graph_objects.CreateChannelStdDeviation(chart_id,name,subwindow,time1,price1,time2,price2,deviation); }
   bool              CreateChannelStdDeviation(const string name,const int subwindow,
                                               const datetime time1,const double price1,const datetime time2,const double price2,
                                               const double deviation=1.5)
                       { return this.CreateChannelStdDeviation(::ChartID(),name,subwindow,time1,price1,time2,price2,deviation); }

//--- Create the "Linear regression channel" graphical object
   bool              CreateChannelRegression(const long chart_id,const string name,const int subwindow,
                                             const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateChannelRegression(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateChannelRegression(const string name,const int subwindow,
                                             const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateChannelRegression(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Create the "Andrews' Pitchfork" graphical object
   bool              CreatePitchforkAndrews(const long chart_id,const string name,const int subwindow,
                                            const datetime time1,const double price1,const datetime time2,const double price2,
                                            const datetime time3,const double price3)
                       { return this.m_graph_objects.CreatePitchforkAndrews(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreatePitchforkAndrews(const string name,const int subwindow,
                                            const datetime time1,const double price1,const datetime time2,const double price2,
                                            const datetime time3,const double price3)
                       { return this.CreatePitchforkAndrews(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Create the "Gann line" graphical object
   bool              CreateGannLine(const long chart_id,const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,const double price2,const double angle)
                       { return this.m_graph_objects.CreateGannLine(chart_id,name,subwindow,time1,price1,time2,price2,angle); }
   bool              CreateGannLine(const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,const double price2,const double angle)
                       { return this.CreateGannLine(::ChartID(),name,subwindow,time1,price1,time2,price2,angle); }

//--- Create the "Gann fan" graphical object
   bool              CreateGannFan(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const ENUM_GANN_DIRECTION direction,const double scale)
                       { return this.m_graph_objects.CreateGannFan(chart_id,name,subwindow,time1,price1,time2,price2,direction,scale); }
   bool              CreateGannFan(const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const ENUM_GANN_DIRECTION direction,const double scale)
                       { return this.CreateGannFan(::ChartID(),name,subwindow,time1,price1,time2,price2,direction,scale); }

//--- Create the "Gann grid" graphical object
   bool              CreateGannGrid(const long chart_id,const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,
                                    const ENUM_GANN_DIRECTION direction,const double scale)
                       { return this.m_graph_objects.CreateGannGrid(chart_id,name,subwindow,time1,price1,time2,direction,scale); }
   bool              CreateGannGrid(const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,
                                    const ENUM_GANN_DIRECTION direction,const double scale)
                       { return this.CreateGannGrid(::ChartID(),name,subwindow,time1,price1,time2,direction,scale); }

//--- Create the "Fibo levels" graphical object
   bool              CreateFiboLevels(const long chart_id,const string name,const int subwindow,
                                      const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateFiboLevels(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateFiboLevels(const string name,const int subwindow,
                                      const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateFiboLevels(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Create the "Fibo Time Zones" graphical object
   bool              CreateFiboTimeZones(const long chart_id,const string name,const int subwindow,
                                         const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateFiboTimeZones(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateFiboTimeZones(const string name,const int subwindow,
                                         const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateFiboTimeZones(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Create the "Fibo fan" graphical object
   bool              CreateFiboFan(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateFiboFan(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateFiboFan(const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateFiboFan(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Create the "Fibo arc" graphical object
   bool              CreateFiboArc(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const double scale,const bool ellipse)
                       { return this.m_graph_objects.CreateFiboArc(chart_id,name,subwindow,time1,price1,time2,price2,scale,ellipse); }
   bool              CreateFiboArc(const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const double scale,const bool ellipse)
                       { return this.CreateFiboArc(::ChartID(),name,subwindow,time1,price1,time2,price2,scale,ellipse); }

//--- Create the "Fibo channel" graphical object
   bool              CreateFiboChannel(const long chart_id,const string name,const int subwindow,
                                       const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.m_graph_objects.CreateFiboChannel(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreateFiboChannel(const string name,const int subwindow,
                                       const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.CreateFiboChannel(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Create the "Fibo extension" graphical object
   bool              CreateFiboExpansion(const long chart_id,const string name,const int subwindow,
                                         const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.m_graph_objects.CreateFiboExpansion(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreateFiboExpansion(const string name,const int subwindow,
                                         const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.CreateFiboExpansion(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Create the "Elliott 5 waves" graphical object
   bool              CreateElliothWave5(const long chart_id,const string name,const int subwindow,
                                        const datetime time1,const double price1,const datetime time2,const double price2,
                                        const datetime time3,const double price3,const datetime time4,const double price4,
                                        const datetime time5,const double price5,const ENUM_ELLIOT_WAVE_DEGREE degree,
                                        const bool draw_lines)
                       { return this.m_graph_objects.CreateElliothWave5(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3,time4,price4,time5,price5,degree,draw_lines); }
   bool              CreateElliothWave5(const string name,const int subwindow,
                                        const datetime time1,const double price1,const datetime time2,const double price2,
                                        const datetime time3,const double price3,const datetime time4,const double price4,
                                        const datetime time5,const double price5,const ENUM_ELLIOT_WAVE_DEGREE degree,
                                        const bool draw_lines)
                       { return this.CreateElliothWave5(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3,time4,price4,time5,price5,degree,draw_lines); }

//--- Create the "Elliott 3 waves" graphical object
   bool              CreateElliothWave3(const long chart_id,const string name,const int subwindow,
                                        const datetime time1,const double price1,const datetime time2,
                                        const double price2,const datetime time3,const double price3,
                                        const ENUM_ELLIOT_WAVE_DEGREE degree,const bool draw_lines)
                       { return this.m_graph_objects.CreateElliothWave3(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3,degree,draw_lines); }
   bool              CreateElliothWave3(const string name,const int subwindow,
                                        const datetime time1,const double price1,const datetime time2,
                                        const double price2,const datetime time3,const double price3,
                                        const ENUM_ELLIOT_WAVE_DEGREE degree,const bool draw_lines)
                       { return this.CreateElliothWave3(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3,degree,draw_lines); }

//--- Create the Rectangle graphical object
   bool              CreateRectangle(const long chart_id,const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateRectangle(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateRectangle(const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateRectangle(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Create the Triangle graphical object
   bool              CreateTriangle(const long chart_id,const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.m_graph_objects.CreateTriangle(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreateTriangle(const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.CreateTriangle(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Create the Ellipse graphical object
   bool              CreateEllipse(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.m_graph_objects.CreateEllipse(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreateEllipse(const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.CreateEllipse(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Create the "Thumb up" graphical object
   bool              CreateThumbUp(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateThumbUp(chart_id,name,subwindow,time,price); }
   bool              CreateThumbUp(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateThumbUp(::ChartID(),name,subwindow,time,price); }

//--- Create the "Thumb down" graphical object
   bool              CreateThumbDown(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateThumbDown(chart_id,name,subwindow,time,price); }
   bool              CreateThumbDown(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateThumbDown(::ChartID(),name,subwindow,time,price); }

//--- Create the "Arrow up" graphical object
   bool              CreateArrowUp(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateArrowUp(chart_id,name,subwindow,time,price); }
   bool              CreateArrowUp(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateArrowUp(::ChartID(),name,subwindow,time,price); }

//--- Create the "Arrow down" graphical object
   bool              CreateArrowDown(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateArrowDown(chart_id,name,subwindow,time,price); }
   bool              CreateArrowDown(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateArrowDown(::ChartID(),name,subwindow,time,price); }

//--- Create the Stop graphical object
   bool              CreateSignalStop(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateSignalStop(chart_id,name,subwindow,time,price); }
   bool              CreateSignalStop(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateSignalStop(::ChartID(),name,subwindow,time,price); }

//--- Create the "Check mark" graphical object
   bool              CreateSignalCheck(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateSignalCheck(chart_id,name,subwindow,time,price); }
   bool              CreateSignalCheck(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateSignalCheck(::ChartID(),name,subwindow,time,price); }

//--- Create the "Left price label" graphical object
   bool              CreatePriceLabelLeft(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreatePriceLabelLeft(chart_id,name,subwindow,time,price); }
   bool              CreatePriceLabelLeft(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreatePriceLabelLeft(::ChartID(),name,subwindow,time,price); }

//--- Create the "Right price label" graphical object
   bool              CreatePriceLabelRight(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreatePriceLabelRight(chart_id,name,subwindow,time,price); }
   bool              CreatePriceLabelRight(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreatePriceLabelRight(::ChartID(),name,subwindow,time,price); }

//--- Create the Buy graphical object
   bool              CreateSignalBuy(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateSignalBuy(chart_id,name,subwindow,time,price); }
   bool              CreateSignalBuy(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateSignalBuy(::ChartID(),name,subwindow,time,price); }

//--- Create the Sell graphical object
   bool              CreateSignalSell(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateSignalSell(chart_id,name,subwindow,time,price); }
   bool              CreateSignalSell(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateSignalSell(::ChartID(),name,subwindow,time,price); }

//--- Create the Arrow graphical object
   bool              CreateArrow(const long chart_id,const string name,const int subwindow,const datetime time,const double price,
                                 const uchar arrow_code,const ENUM_ARROW_ANCHOR anchor)
                       { return this.m_graph_objects.CreateArrow(chart_id,name,subwindow,time,price,arrow_code,anchor); }
   bool              CreateArrow(const string name,const int subwindow,const datetime time,const double price,
                                 const uchar arrow_code,const ENUM_ARROW_ANCHOR anchor)
                       { return this.CreateArrow(::ChartID(),name,subwindow,time,price,arrow_code,anchor); }

//--- Create the Text graphical object
   bool              CreateText(const long chart_id,const string name,const int subwindow,const datetime time,const double price,
                                const string text,const int size,const ENUM_ANCHOR_POINT anchor_point,const double angle)
                       { return this.m_graph_objects.CreateText(chart_id,name,subwindow,time,price,text,size,anchor_point,angle); }
   bool              CreateText(const string name,const int subwindow,const datetime time,const double price,
                                const string text,const int size,const ENUM_ANCHOR_POINT anchor_point,const double angle)
                       { return this.CreateText(::ChartID(),name,subwindow,time,price,text,size,anchor_point,angle); }

//--- Create the "Text label" graphical object
   bool              CreateTextLabel(const long chart_id,const string name,const int subwindow,const int x,const int y,
                                     const string text,const int size,const ENUM_BASE_CORNER corner,
                                     const ENUM_ANCHOR_POINT anchor_point,const double angle)
                       { return this.m_graph_objects.CreateTextLabel(chart_id,name,subwindow,x,y,text,size,corner,anchor_point,angle); }
   bool              CreateTextLabel(const string name,const int subwindow,const int x,const int y,
                                     const string text,const int size,const ENUM_BASE_CORNER corner,
                                     const ENUM_ANCHOR_POINT anchor_point,const double angle)
                       { return this.CreateTextLabel(::ChartID(),name,subwindow,x,y,text,size,corner,anchor_point,angle); }

//--- Create the Button graphical object
   bool              CreateButton(const long chart_id,const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                  const ENUM_BASE_CORNER corner,const int font_size,const bool button_state)
                       { return this.m_graph_objects.CreateButton(chart_id,name,subwindow,x,y,w,h,corner,font_size,button_state); }
   bool              CreateButton(const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                  const ENUM_BASE_CORNER corner,const int font_size,const bool button_state)
                       { return this.CreateButton(::ChartID(),name,subwindow,x,y,w,h,corner,font_size,button_state); }

//--- Create the Chart graphical object
   bool              CreateChart(const long chart_id,const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                 const ENUM_BASE_CORNER corner,const int scale,const string symbol,const ENUM_TIMEFRAMES timeframe)
                       { return this.m_graph_objects.CreateChart(chart_id,name,subwindow,x,y,w,h,corner,scale,symbol,timeframe); }
   bool              CreateChart(const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                 const ENUM_BASE_CORNER corner,const int scale,const string symbol,const ENUM_TIMEFRAMES timeframe)
                       { return this.CreateChart(::ChartID(),name,subwindow,x,y,w,h,corner,scale,symbol,timeframe); }

//--- Create the Bitmap graphical object
   bool              CreateBitmap(const long chart_id,const string name,const int subwindow,const datetime time,const double price,
                                  const string image1,const string image2,const ENUM_ANCHOR_POINT anchor)
                       { return this.m_graph_objects.CreateBitmap(chart_id,name,subwindow,time,price,image1,image2,anchor); }
   bool              CreateBitmap(const string name,const int subwindow,const datetime time,const double price,
                                  const string image1,const string image2,const ENUM_ANCHOR_POINT anchor)
                       { return this.CreateBitmap(::ChartID(),name,subwindow,time,price,image1,image2,anchor); }

//--- Create the "Bitmap label" graphical object
   bool              CreateBitmapLabel(const long chart_id,const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                       const string image1,const string image2,const ENUM_BASE_CORNER corner,const ENUM_ANCHOR_POINT anchor,
                                       const bool state)
                       { return this.m_graph_objects.CreateBitmapLabel(chart_id,name,subwindow,x,y,w,h,image1,image2,corner,anchor,state); }
   bool              CreateBitmapLabel(const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                       const string image1,const string image2,const ENUM_BASE_CORNER corner,const ENUM_ANCHOR_POINT anchor,
                                       const bool state)
                       { return this.CreateBitmapLabel(::ChartID(),name,subwindow,x,y,w,h,image1,image2,corner,anchor,state); }

//--- Create the "Input field" graphical object
   bool              CreateEditField(const long chart_id,const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                     const int font_size,const ENUM_BASE_CORNER corner,const ENUM_ALIGN_MODE align,const bool readonly)
                       { return this.m_graph_objects.CreateEditField(chart_id,name,subwindow,x,y,w,h,font_size,corner,align,readonly); }
   bool              CreateEditField(const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                     const int font_size,const ENUM_BASE_CORNER corner,const ENUM_ALIGN_MODE align,const bool readonly)
                       { return this.CreateEditField(::ChartID(),name,subwindow,x,y,w,h,font_size,corner,align,readonly); }

//--- Create the "Economic calendar event" graphical object
   bool              CreateCalendarEvent(const long chart_id,const string name,const int subwindow,const datetime time)
                       { return this.m_graph_objects.CreateCalendarEvent(chart_id,name,subwindow,time); }
   bool              CreateCalendarEvent(const string name,const int subwindow,const datetime time)
                       { return this.CreateCalendarEvent(::ChartID(),name,subwindow,time); }

//--- Create the "Rectangular label" graphical object
   bool              CreateRectangleLabel(const long chart_id,const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                          const ENUM_BASE_CORNER corner,const ENUM_BORDER_TYPE border)
                       { return this.m_graph_objects.CreateRectangleLabel(chart_id,name,subwindow,x,y,w,h,corner,border); }
   bool              CreateRectangleLabel(const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                          const ENUM_BASE_CORNER corner,const ENUM_BORDER_TYPE border)
                       { return this.CreateRectangleLabel(::ChartID(),name,subwindow,x,y,w,h,corner,border); }
   
//--- Constructor/destructor
                        CEngine();
                       ~CEngine();

Por ahora, estas son todas las mejoras necesarias para crear una funcionalidad básica para monitorear los eventos de los objetos gráficos.


Simulación

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

¿Cómo lo probamos? En el artículo anterior, creamos una línea vertical clicando en un gráfico con la tecla Ctrl pulsada. Ahora también lo crearemos, pero en todos los gráficos abiertos en el terminal.

En el manejador OnChartEvent(), en el bloque de código para un evento de clic en el gráfico, añadiremos el código para rellenar la matriz con los IDs de todos los gráficos abiertos, y en un ciclo a través de la matriz obtenida, crearemos una línea vertical en cada uno de los gráficos:

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

Compilamos el EA y lo ejecutamos en el gráfico, abriendo primero otro gráfico, y disponiéndolos horizontalmente. Un clic sobre el gráfico con el EA creará líneas verticales, una en cada gráfico. Ahora, modificamos sus propiedades y vemos cómo el diario muestra los mensajes sobre los eventos recibidos:


Como podemos ver, los mensajes sobre los eventos de los objetos son registrados. Al crear objetos usando programación, no se crea el evento de creación de un objeto, porque el programador ya sabe en qué momento crea un objeto gráfico mediante programación. Por eso, creemos que será inútil duplicar este hecho enviando el evento.
Por supuesto, el simple registro de mensajes generalizados sobre un evento ocurrido no es suficiente para procesar los eventos. Pero estos son solo mensajes de eventos básicos, cuyos parámetros contienen toda la información sobre un evento; los definiremos más adelante.


¿Qué es lo próximo?

En el próximo artículo, seguiremos trabajando con los eventos de los objetos gráficos y crearemos el procesamiento de cada evento obtenido.

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

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

Archivos adjuntos |
MQL5.zip (4178.92 KB)
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.
Websockets para MetaTrader 5 — Usando la API de Windows Websockets para MetaTrader 5 — Usando la API de Windows
En este artículo, usaremos WinHttp.dll para crear un cliente de websocket para los programas de MetaTrader 5. El cliente se implementará finalmente como una clase, y también se probará contra la API de websocket de Binary.com.
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
En el artículo, finalizaremos la funcionalidad básica para posibilitar el control de eventos para los objetos gráficos desde un programa basado en la biblioteca. Comenzaremos creando la funcionalidad necesaria para almacenar la historia de cambios en las propiedades de los objetos gráficos usando la propiedad "Nombre del objeto" como ejemplo.
Desarrollo de robots comerciales usando programación visual Desarrollo de robots comerciales usando programación visual
El artículo muestra las capacidades del editor botbrains.app, una plataforma sin código para desarrollar robots comerciales. Para crear un robot comercial, no necesitamos programar: simplemente debemos arrastrar los bloques necesarios al esquema, indicar sus parámetros y establecer los vínculos entre ellos.