English Русский 中文 Español Deutsch 日本語
Gráficos na biblioteca do DoEasy (Parte 92): classe de memória de objetos gráficos padrão. Histórico de mudanças de propriedades do objeto

Gráficos na biblioteca do DoEasy (Parte 92): classe de memória de objetos gráficos padrão. Histórico de mudanças de propriedades do objeto

MetaTrader 5Exemplos | 22 março 2022, 09:40
512 0
Artyom Trishkin
Artyom Trishkin

Sumário


Ideia

Nós já tínhamos testado o registro do histórico de renomeação nas propriedades do objeto gráfico. Em essência, a funcionalidade criada no último artigo permite conhecer toda a sequência de renomeação do objeto gráfico. Não posso antever quão útil será tal recurso, mas, às vezes pode ser útil saber quais objetos estavam anteriormente no gráfico, quando o objeto antigo é removido e criado um novo com um nome diferente. E se considerarmos a possibilidade de armazenar todo o histórico de mudanças em um objeto, qualquer objeto gráfico terá todos os seus estados registrados, incluindo seus nomes anteriores. Desta forma, com o objeto atual, podemos conhecer qualquer um de seus nomes anteriores e, combinando o nome desejado com suas propriedades armazenadas, podemos simplesmente restaurar esse objeto no gráfico.


Não sei onde ou como isso poderia ser útil, mas seria uma ferramenta adicional para realizar análises técnicas. Por exemplo, com o nome do objeto podemos assinalar o dia, semana, mês ou outro período de tempo em que o objeto gráfico é construído. Quando aparece um novo período de tempo, o objeto antigo é renomeado para que corresponda ao novo período de tempo, e o objeto é rearranjado no gráfico. Assim, todas as suas propriedades serão armazenadas em sua memória (faremos tal funcionalidade hoje), incluindo seu histórico de renomeação. Se marcarmos algo no gráfico todos os dias com um objeto gráfico e o reorganizarmos todos os dias, podemos também renomeá-lo para corresponder à nova data. Em seguida, ao rolar o gráfico manualmente, podemos descobrir o tempo da barra visível e simplesmente "puxar" seu estado da memória do objeto gráfico atual, estado esse que corresponde ao tempo das barras no gráfico rolante e aplicar suas propriedades ao objeto atual. Desta forma, podemos criar um objeto inteligente que mudará seu estado dependendo do tempo das barras visíveis no gráfico.

Em outras palavras, o exemplo acima nos permitirá ajustar as propriedades do objeto gráfico conforme o estado atual do mercado diariamente, durante a semana de negociação, e em seguida, ao final da semana, simplesmente nos deixará rolar o gráfico de volta manualmente, e nosso objeto gráfico será exibido a cada dia de negociação prévio na parte visível do gráfico — afinal, este objeto tem sua própria memória, e cada vez que as propriedades do objeto eram alteradas, ele armazenava todas as mudanças em sua memória. E se, além disso, no gráfico estiver rodando um Expert Advisor sob controle da biblioteca que está programado para que o objeto gráfico tire um "instantâneo" (corresponde ao dia visível no gráfico) de seu estado a partir do histórico e aplique esses parâmetros a si mesmo, a rolagem do gráfico será um pré-requisito para alterar as propriedades do objeto gráfico, e ele mesmo nos mostrará todas as modificações que foram feitas nele durante a semana de negociação.

Novamente, qual é o propósito disto? Eu acho isso útil, para fazer uma análise da minha última semana de negociação. Devo dizer que este é apenas um exemplo que me veio imediatamente à mente e me pareceu útil.


Aprimorando as classes da biblioteca

Na lista de propriedades de objeto gráfico padrão, temos a propriedade Grupo, e esta propriedade é usada atualmente para especificar o grupo ao qual o objeto gráfico pertence:


Mas se nos lembrarmos que outros objetos de biblioteca também têm seus próprios grupos, e estes grupos são usados para agrupar objetos de acordo com certas propriedades, colocaremos tudo na mesma ordem nos objetos gráficos. A propriedade "Grupo" atual será renomeada para a propriedade "Tipo", e a nova propriedade "Grupo" será usada para agrupar objetos por algumas de suas propriedades. Para testar a funcionalidade que estamos criando hoje, estaremos atribuindo os objetos gráficos que criamos no gráfico ao grupo nº 1, e todos os objetos desse grupo registrarão seus estados em sua memória quando forem modificados.

No arquivo \MQL5\Include\DoEasy\Defines.mqh alteramos a enumeração "Grupo de objeto gráficos" :

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
  };
//+------------------------------------------------------------------+
//| Graphical object group                                           |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_GROUP
  {
   GRAPH_OBJ_GROUP_LINES,                             // Lines
   GRAPH_OBJ_GROUP_CHANNELS,                          // Channels
   GRAPH_OBJ_GROUP_GANN,                              // Gann
   GRAPH_OBJ_GROUP_FIBO,                              // Fibo
   GRAPH_OBJ_GROUP_ELLIOTT,                           // Elliott
   GRAPH_OBJ_GROUP_SHAPES,                            // Shapes
   GRAPH_OBJ_GROUP_ARROWS,                            // Arrows
   GRAPH_OBJ_GROUP_GRAPHICAL,                         // Graphical objects
  };
//+------------------------------------------------------------------+
//| Integer properties of a standard graphical object                |
//+------------------------------------------------------------------+

para a enumeração "Tipo de objeto gráfico":

//+------------------------------------------------------------------+
//| Graphical object species                                         |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_SPECIES
  {
   GRAPH_OBJ_SPECIES_LINES,                             // Lines
   GRAPH_OBJ_SPECIES_CHANNELS,                          // Channels
   GRAPH_OBJ_SPECIES_GANN,                              // Gann
   GRAPH_OBJ_SPECIES_FIBO,                              // Fibo
   GRAPH_OBJ_SPECIES_ELLIOTT,                           // Elliott
   GRAPH_OBJ_SPECIES_SHAPES,                            // Shapes
   GRAPH_OBJ_SPECIES_ARROWS,                            // Arrows
   GRAPH_OBJ_SPECIES_GRAPHICAL,                         // Graphical objects
  };
//+------------------------------------------------------------------+

Na enumeração das propriedades inteiras de objeto gráfico padrão alteramos a propriedade Grupo

   //--- Additional properties
   GRAPH_OBJ_PROP_ID = 0,                             // Object ID
   GRAPH_OBJ_PROP_TYPE,                               // Graphical object type (ENUM_OBJECT)
   GRAPH_OBJ_PROP_ELEMENT_TYPE,                       // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   GRAPH_OBJ_PROP_GROUP,                              // Graphical object group (ENUM_GRAPH_OBJ_GROUP)
   GRAPH_OBJ_PROP_BELONG,                             // Graphical object affiliation
   GRAPH_OBJ_PROP_CHART_ID,                           // Chart ID
   GRAPH_OBJ_PROP_WND_NUM,                            // Chart subwindow index
   GRAPH_OBJ_PROP_NUM,                                // Object index in the list

para a propriedade Tipo e escrevemos mais dois parâmetros — sinalizador de armazenamento do histórico de mudanças e o grupo de objetos:

//+------------------------------------------------------------------+
//| Integer properties of a standard graphical object                |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_PROP_INTEGER
  {
   //--- Additional properties
   GRAPH_OBJ_PROP_ID = 0,                             // Object ID
   GRAPH_OBJ_PROP_TYPE,                               // Graphical object type (ENUM_OBJECT)
   GRAPH_OBJ_PROP_ELEMENT_TYPE,                       // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   GRAPH_OBJ_PROP_SPECIES,                            // Graphical object species (ENUM_GRAPH_OBJ_SPECIES)
   GRAPH_OBJ_PROP_BELONG,                             // Graphical object affiliation
   GRAPH_OBJ_PROP_CHART_ID,                           // Chart ID
   GRAPH_OBJ_PROP_WND_NUM,                            // Chart subwindow index
   GRAPH_OBJ_PROP_NUM,                                // Object index in the list
   GRAPH_OBJ_PROP_CHANGE_HISTORY,                     // Flag of storing the change history
   GRAPH_OBJ_PROP_GROUP,                              // Group of objects the graphical object belongs to
   //--- Common properties of all graphical objects

Todos os objetos gráficos não irão, por padrão, armazenar o histórico de mudanças em suas propriedades. Por isso, foi introduzida uma propriedade que armazena um sinalizador que indica se o objeto gráfico registra ou não seu histórico de mudanças. Como mencionado acima, renomeamos a propriedade "Grupo" para a propriedade "Tipo" e criamos uma nova propriedade "Grupo" para armazenar o número de objetos do grupo agrupados por uma determinada propriedade.

Como adicionamos duas novas propriedades à lista de propriedades inteiras do objeto gráfico, indicaremos seu novo número (ao invés de 52, o número de propriedades inteiras é agora 54):

#define GRAPH_OBJ_PROP_INTEGER_TOTAL (54)             // Total number of integer properties
#define GRAPH_OBJ_PROP_INTEGER_SKIP  (0)              // Number of integer properties not used in sorting
//+------------------------------------------------------------------+
//| Real properties of a standard graphical object                   |
//+------------------------------------------------------------------+

Adicionamos estas novas propriedades à lista de possíveis critérios de classificação de objetos gráficos:

//+------------------------------------------------------------------+
//| Possible sorting criteria of graphical objects                   |
//+------------------------------------------------------------------+
#define FIRST_GRAPH_OBJ_DBL_PROP  (GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_INTEGER_SKIP)
#define FIRST_GRAPH_OBJ_STR_PROP  (GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_INTEGER_SKIP+GRAPH_OBJ_PROP_DOUBLE_TOTAL-GRAPH_OBJ_PROP_DOUBLE_SKIP)
enum ENUM_SORT_GRAPH_OBJ_MODE
  {
//--- Sort by integer properties
   SORT_BY_GRAPH_OBJ_ID = 0,                             // Sort by object ID
   SORT_BY_GRAPH_OBJ_TYPE,                               // Sort by object type
   SORT_BY_GRAPH_OBJ_ELEMENT_TYPE,                       // Sort by graphical element type
   SORT_BY_GRAPH_OBJ_SPECIES,                            // Sort by a graphical object species
   SORT_BY_GRAPH_OBJ_BELONG,                             //  Sort by a graphical element affiliation
   SORT_BY_GRAPH_OBJ_CHART_ID,                           // Sort by chart ID
   SORT_BY_GRAPH_OBJ_WND_NUM,                            // Sort by chart subwindow index
   SORT_BY_GRAPH_OBJ_NUM,                                // Sort by object index in the list
   SORT_BY_GRAPH_OBJ_CHANGE_HISTORY,                     // Sort by the flag of storing the change history
   SORT_BY_GRAPH_OBJ_GROUP,                              // Sort by the group of objects the graphical object belongs to
   SORT_BY_GRAPH_OBJ_CREATETIME,                         // Sort by object creation time
   SORT_BY_GRAPH_OBJ_TIMEFRAMES,                         // Sort by object visibility on timeframes
   SORT_BY_GRAPH_OBJ_BACK,                               // Sort by the "Background object" property
   SORT_BY_GRAPH_OBJ_ZORDER,                             // Sort by the priority of a graphical object for receiving the event of clicking on a chart
   SORT_BY_GRAPH_OBJ_HIDDEN,                             // Sort by a disabling display of the name of a graphical object in the terminal object list
   SORT_BY_GRAPH_OBJ_SELECTED,                           // Sort by the "Object selection" property
   SORT_BY_GRAPH_OBJ_SELECTABLE,                         // Sort by the "Object availability" property
   SORT_BY_GRAPH_OBJ_TIME,                               // Sort by time coordinate
   SORT_BY_GRAPH_OBJ_COLOR,                              // Sort by color
   SORT_BY_GRAPH_OBJ_STYLE,                              // Sort by style
   SORT_BY_GRAPH_OBJ_WIDTH,                              // Sort by line width
   SORT_BY_GRAPH_OBJ_FILL,                               // Sort by the "Object color filling" property
   SORT_BY_GRAPH_OBJ_READONLY,                           // Sort by the ability to edit text in the Edit object
   SORT_BY_GRAPH_OBJ_LEVELS,                             // Sort by number of levels
   SORT_BY_GRAPH_OBJ_LEVELCOLOR,                         // Sort by line level color
   SORT_BY_GRAPH_OBJ_LEVELSTYLE,                         // Sort by line level style
   SORT_BY_GRAPH_OBJ_LEVELWIDTH,                         // Sort by line level width
   SORT_BY_GRAPH_OBJ_ALIGN,                              // Sort by the "Horizontal text alignment in the Entry field" property
   SORT_BY_GRAPH_OBJ_FONTSIZE,                           // Sort by font size
   SORT_BY_GRAPH_OBJ_RAY_LEFT,                           // Sort by "Ray goes to the left" property
   SORT_BY_GRAPH_OBJ_RAY_RIGHT,                          // Sort by "Ray goes to the right" property
   SORT_BY_GRAPH_OBJ_RAY,                                // Sort by the "Vertical line goes through all windows of a chart" property
   SORT_BY_GRAPH_OBJ_ELLIPSE,                            // Sort by the "Display the full ellipse of the Fibonacci Arc object" property
   SORT_BY_GRAPH_OBJ_ARROWCODE,                          // Sort by an arrow code for the Arrow object
   SORT_BY_GRAPH_OBJ_ANCHOR,                             // Sort by the position of a binding point of a graphical object
   SORT_BY_GRAPH_OBJ_XDISTANCE,                          // Sort by a distance from the base corner along the X axis in pixels
   SORT_BY_GRAPH_OBJ_YDISTANCE,                          // Sort by a distance from the base corner along the Y axis in pixels
   SORT_BY_GRAPH_OBJ_DIRECTION,                          // Sort by the "Gann object trend" property
   SORT_BY_GRAPH_OBJ_DEGREE,                             // Sort by the "Elliott wave marking level" property
   SORT_BY_GRAPH_OBJ_DRAWLINES,                          // Sort by the "Display lines for Elliott wave marking" property
   SORT_BY_GRAPH_OBJ_STATE,                              // Sort by button state (pressed/released)
   SORT_BY_GRAPH_OBJ_OBJ_CHART_ID,                       // Sort by Chart object ID.
   SORT_BY_GRAPH_OBJ_CHART_OBJ_PERIOD,                   // Sort by Chart object period
   SORT_BY_GRAPH_OBJ_CHART_OBJ_DATE_SCALE,               // Sort by time scale display flag for the Chart object
   SORT_BY_GRAPH_OBJ_CHART_OBJ_PRICE_SCALE,              // Sort by price scale display flag for the Chart object
   SORT_BY_GRAPH_OBJ_CHART_OBJ_CHART_SCALE,              // Sort by Chart object scale
   SORT_BY_GRAPH_OBJ_XSIZE,                              // Sort by Object width along the X axis in pixels
   SORT_BY_GRAPH_OBJ_YSIZE,                              // Sort by object height along the Y axis in pixels
   SORT_BY_GRAPH_OBJ_XOFFSET,                            // Sort by X coordinate of the upper-left corner of the visibility area
   SORT_BY_GRAPH_OBJ_YOFFSET,                            // Sort by Y coordinate of the upper-left corner of the visibility area
   SORT_BY_GRAPH_OBJ_BGCOLOR,                            // Sort by background color for OBJ_EDIT, OBJ_BUTTON and OBJ_RECTANGLE_LABEL
   SORT_BY_GRAPH_OBJ_CORNER,                             // Sort by chart corner for binding a graphical object
   SORT_BY_GRAPH_OBJ_BORDER_TYPE,                        // Sort by border type for the "Rectangle border" object
   SORT_BY_GRAPH_OBJ_BORDER_COLOR,                       // Sort by frame color for the OBJ_EDIT and OBJ_BUTTON objects
//--- Sort by real properties
   SORT_BY_GRAPH_OBJ_PRICE = FIRST_GRAPH_OBJ_DBL_PROP,   // Sort by price coordinate
   SORT_BY_GRAPH_OBJ_LEVELVALUE,                         // Sort by level value
   SORT_BY_GRAPH_OBJ_SCALE,                              // Sort by scale (property of Gann objects and Fibonacci Arcs objects)
   SORT_BY_GRAPH_OBJ_ANGLE,                              // Sort by angle
   SORT_BY_GRAPH_OBJ_DEVIATION,                          // Sort by a deviation of the standard deviation channel
//--- Sort by string properties
   SORT_BY_GRAPH_OBJ_NAME = FIRST_GRAPH_OBJ_STR_PROP,    // Sort by object name
   SORT_BY_GRAPH_OBJ_TEXT,                               // Sort by object description
   SORT_BY_GRAPH_OBJ_TOOLTIP,                            // Sort by tooltip text
   SORT_BY_GRAPH_OBJ_LEVELTEXT,                          // Sort by level description
   SORT_BY_GRAPH_OBJ_FONT,                               // Sort by font
   SORT_BY_GRAPH_OBJ_BMPFILE,                            // Sort by BMP file name for the "Bitmap Level" object
   SORT_BY_GRAPH_OBJ_CHART_OBJ_SYMBOL,                   // Sort by Chart object period symbol
  };
//+------------------------------------------------------------------+


No arquivo \MQL5\Include\DoEasy\Data.mqh inserimos os índices das novas mensagens e corrigimos os nomes das constantes de enumerações:

//--- GStdGraphObj
   MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ,     // Failed to create the class object for a graphical object 
   MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ, // Failed to create a graphical object 
   MSG_GRAPH_STD_OBJ_ERR_NOT_FIND_SUBWINDOW,          // Failed to find the chart subwindow
   MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_SNAPSHOT,      // Failed to create a snapshot of the graphical object change history
   MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT,         // Created a snapshot of the graphical object change history

...

   MSG_GRAPH_OBJ_PROP_ID,                             // Object ID
   MSG_GRAPH_OBJ_PROP_TYPE,                           // Graphical object type (ENUM_OBJECT)
   MSG_GRAPH_OBJ_PROP_ELEMENT_TYPE,                   // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   MSG_GRAPH_OBJ_PROP_BELONG,                         // Graphical object affiliation
   MSG_GRAPH_OBJ_PROP_CHART_ID,                       // Chart ID
   MSG_GRAPH_OBJ_PROP_WND_NUM,                        // Chart subwindow index
   MSG_GRAPH_OBJ_PROP_CHANGE_MEMORY,                  // Change history
   MSG_GRAPH_OBJ_PROP_CREATETIME,                     // Creation time
   MSG_GRAPH_OBJ_PROP_TIMEFRAMES,                     // Object visibility on timeframes

...

   MSG_GRAPH_OBJ_PROP_SPECIES,                        // Graphical object species
   MSG_GRAPH_OBJ_PROP_SPECIES_LINES,                  // Lines
   MSG_GRAPH_OBJ_PROP_SPECIES_CHANNELS,               // Channels
   MSG_GRAPH_OBJ_PROP_SPECIES_GANN,                   // Gann
   MSG_GRAPH_OBJ_PROP_SPECIES_FIBO,                   // Fibo
   MSG_GRAPH_OBJ_PROP_SPECIES_ELLIOTT,                // Elliott
   MSG_GRAPH_OBJ_PROP_SPECIES_SHAPES,                 // Shapes
   MSG_GRAPH_OBJ_PROP_SPECIES_ARROWS,                 // Arrows
   MSG_GRAPH_OBJ_PROP_SPECIES_GRAPHICAL,              // Graphical objects
   MSG_GRAPH_OBJ_PROP_GROUP,                          // Group of objects
   MSG_GRAPH_OBJ_TEXT_CLICK_COORD,                    // (Chart click coordinate)
   MSG_GRAPH_OBJ_TEXT_ANCHOR_TOP,                     // Arrow anchor point is located at the top
   MSG_GRAPH_OBJ_TEXT_ANCHOR_BOTTOM,                  // Arrow anchor point is located at the bottom

...

//--- CDataPropObj
   MSG_DATA_PROP_OBJ_OUT_OF_PROP_RANGE,               // Passed property is out of object property range
   MSG_GRAPH_OBJ_FAILED_CREATE_NEW_HIST_OBJ,          // Failed to create an object of the graphical object change history
   MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_HIST_LIST,         // Failed to add the change history object to the list
   MSG_GRAPH_OBJ_FAILED_GET_HIST_OBJ,                 // Failed to receive the change history object
   MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE,               // Failed to increase the array size

e as mensagens de textocorrespondentes aos índices recém-adicionados:

//--- GStdGraphObj
   {"Не удалось создать объект класса для графического объекта ","Failed to create class object for graphic object"},
   {"Не удалось создать графический объект ","Failed to create graphic object "},
   {"Не удалось найти подокно графика","Could not find chart subwindow"},
   {"Не удалось создать снимок истории изменений графического объекта","Failed to create a snapshot of the change history of a graphic object"},
   {"Создан снимок истории изменений графического объекта","A snapshot of the history of changes to a graphical object has been created"},

...

   {"Идентификатор объекта","Object ID"},
   {"Тип объекта","Object type"},
   {"Тип графического элемента","Graphic element type"},
   {"Принадлежность объекта","Object belongs to"},
   {"Идентификатор графика объекта","Object chart ID"},
   {"Номер подокна графика","Chart subwindow number"},
   {"История изменений","Change history"},
   {"Время создания","Time of creation"},
   {"Видимость объекта на таймфреймах","Visibility of an object at timeframes"},

...

   {"Вид графического объекта","Graphic object species"},
   {"Линии","Lines"},
   {"Каналы","Channels"},
   {"Ганн","Gann"},
   {"Фибоначчи","Fibonacci"},
   {"Эллиотт","Elliott"},
   {"Фигуры","Shapes"},
   {"Стрелки","Arrows"},
   {"Графические объекты","Graphical"},
   {"Группа объектов","Object group"},

...

//--- CDataPropObj
   {"Переданное свойство находится за пределами диапазона свойств объекта","The passed property is outside the range of the object's properties"},
   {"Не удалось создать объект истории изменений графического объекта","Failed to create a graphical object change history object"},
   {"Не удалось добавить объект истории изменений в список","Failed to add change history object to the list"},
   {"Не удалось получить объект истории изменений","Failed to get change history object"},
   {"Не удалось увеличить размер массива","Failed to increase array size"},


Fazemos algumas alterações em todos os objetos-herdeiros do objeto gráfico padrão abstrato localizado na pasta \MQL5\Include\DoEasy\Objects\Graph\Standard\ (a título de exemplo, GStdArrowBuyObj.mqh).

Na lista de inicialização do construtor da classe, ajustamos o nome da constante de enumeração que indica o tipo de objeto gráfico:

   //--- Constructor
                     CGStdArrowBuyObj(const long chart_id,const string name) : 
                        CGStdGraphObj(OBJECT_DE_TYPE_GSTD_ARROW_BUY,GRAPH_OBJ_BELONG_NO_PROGRAM,GRAPH_OBJ_SPECIES_ARROWS,chart_id,1,name)
                          {
                           //--- Specify the object property
                           CGStdGraphObj::SetProperty(GRAPH_OBJ_PROP_ANCHOR,0,ANCHOR_TOP);
                          }

Em outros arquivos, estes serão diferentes tipos de objetos gráficos, mas em todos os lugares será necessário substituir "_GROUP_" por "_SPECIES_".

E adicionamos a propriedade "Sinalizador para registrar o histórico de mudanças do objeto" aos métodos que devolvem o sinalizador para manter as propriedades inteiras do objeto:

//+------------------------------------------------------------------+
//| Return 'true' if an object supports a passed                     |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CGStdArrowBuyObj::SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property)
  {
   switch((int)property)
     {
      //--- Supported properties
      case GRAPH_OBJ_PROP_ID           :
      case GRAPH_OBJ_PROP_TYPE         :
      case GRAPH_OBJ_PROP_ELEMENT_TYPE : 
      case GRAPH_OBJ_PROP_GROUP        : 
      case GRAPH_OBJ_PROP_BELONG       :
      case GRAPH_OBJ_PROP_CHART_ID     :
      case GRAPH_OBJ_PROP_WND_NUM      :
      case GRAPH_OBJ_PROP_NUM          :
      case GRAPH_OBJ_PROP_CREATETIME   :
      case GRAPH_OBJ_PROP_CHANGE_HISTORY:
      case GRAPH_OBJ_PROP_TIMEFRAMES   :
      case GRAPH_OBJ_PROP_BACK         :
      case GRAPH_OBJ_PROP_ZORDER       :
      case GRAPH_OBJ_PROP_HIDDEN       :
      case GRAPH_OBJ_PROP_SELECTED     :
      case GRAPH_OBJ_PROP_SELECTABLE   :
      case GRAPH_OBJ_PROP_TIME         :
      case GRAPH_OBJ_PROP_COLOR        :
      case GRAPH_OBJ_PROP_STYLE        :
      case GRAPH_OBJ_PROP_WIDTH        :
      case GRAPH_OBJ_PROP_ANCHOR       : return true;
      //--- Other properties are not supported
      //--- Default is 'false'
      default: break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Como todas essas mudanças já foram feitas em todos os arquivos da pasta acima e são idênticas às discutidas acima, não as descreveremos - elas podem ser encontradas nos arquivos anexos ao artigo.

No arquivo básico do objeto gráfico de todos os objetos gráficos da biblioteca \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh escrevemos variáveis para armazenar o tipo e grupos de objetos gráficos:

//+------------------------------------------------------------------+
//| 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_SPECIES m_species;                     // Graphical object species
   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
   int               m_group;                            // Graphical object group
   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

e métodos de definição e de retorno de valores de estas variáveis:

public:
//--- Set the values of the class variables
   void              SetObjectID(const long value)             { this.m_object_id=value;              }
   void              SetBelong(const ENUM_GRAPH_OBJ_BELONG belong){ this.m_belong=belong;             }
   void              SetTypeGraphObject(const ENUM_OBJECT obj) { this.m_type_graph_obj=obj;           }
   void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type;   }
   void              SetSpecies(const ENUM_GRAPH_OBJ_SPECIES species){ this.m_species=species;        }
   void              SetGroup(const int group)                 { this.m_group=group;                  }
   void              SetName(const string name)                { this.m_name=name;                    }
   void              SetChartID(const long chart_id)           { this.m_chart_id=chart_id;            }
   void              SetDigits(const int value)                { this.m_digits=value;                 }

...

//--- Return the values of class variables
   ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void)        const { return this.m_type_element;       }
   ENUM_GRAPH_OBJ_BELONG   Belong(void)                  const { return this.m_belong;             }
   ENUM_GRAPH_OBJ_SPECIES  Species(void)                 const { return this.m_species;            }
   ENUM_OBJECT       TypeGraphObject(void)               const { return this.m_type_graph_obj;     }
   datetime          TimeCreate(void)                    const { return this.m_create_time;        }
   string            Name(void)                          const { return this.m_name;               }
   long              ChartID(void)                       const { return this.m_chart_id;           }
   long              ObjectID(void)                      const { return this.m_object_id;          }
   long              Zorder(void)                        const { return this.m_zorder;             }
   int               SubWindow(void)                     const { return this.m_subwindow;          }
   int               ShiftY(void)                        const { return this.m_shift_y;            }
   int               VisibleOnTimeframes(void)           const { return this.m_timeframes_visible; }
   int               Digits(void)                        const { return this.m_digits;             }
   int               Group(void)                         const { return this.m_group;              }
   bool              IsBack(void)                        const { return this.m_back;               }
   bool              IsSelected(void)                    const { return this.m_selected;           }
   bool              IsSelectable(void)                  const { return this.m_selectable;         }
   bool              IsHidden(void)                      const { return this.m_hidden;             }
   bool              IsVisible(void)                     const { return this.m_visible;            }

Renomeamos o método que anteriormente retornava a descrição do grupo de objeto gráfico para o método que retorna a descrição de tipo de objeto gráfico:

//--- Return the description of the type of the graphical object (1) type, (2) element, (3) affiliation and (4) species
string               TypeGraphObjectDescription(void);
string               TypeElementDescription(void);
string               BelongDescription(void);
string               SpeciesDescription(void);


e corrigimos sua implementação de acordo com os novos nomes das constantes de enumeração de tipo de objeto:

//+------------------------------------------------------------------+
//| Return the description of the graphical object group             |
//+------------------------------------------------------------------+
string CGBaseObj::SpeciesDescription(void)
  {
   return
     (
      this.Species()==GRAPH_OBJ_SPECIES_LINES      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_LINES)      :
      this.Species()==GRAPH_OBJ_SPECIES_CHANNELS   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_CHANNELS)   :
      this.Species()==GRAPH_OBJ_SPECIES_GANN       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_GANN)       :
      this.Species()==GRAPH_OBJ_SPECIES_FIBO       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_FIBO)       :
      this.Species()==GRAPH_OBJ_SPECIES_ELLIOTT    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_ELLIOTT)    :
      this.Species()==GRAPH_OBJ_SPECIES_SHAPES     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_SHAPES)     :
      this.Species()==GRAPH_OBJ_SPECIES_ARROWS     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_ARROWS)     :
      this.Species()==GRAPH_OBJ_SPECIES_GRAPHICAL  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_GRAPHICAL)  :
      "Unknown"
     );
  }

No construtor da classe definimos o grupo padrão para o objeto como 0, o que corresponde à sua ausência:

//+------------------------------------------------------------------+
//| 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_group=0;                              // Graphical object group
   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
  }
//+------------------------------------------------------------------+


Classe de memória de objetos gráficos padrão

A classe de memória do objeto gráfico será uma lista de objetos na qual serão armazenadas todas as propriedades do objeto gráfico — inteiras, reais e de string, no momento de mudança de qualquer uma delas. Por que não apenas uma propriedade modificada? Basicamente, partindo do fato de que não só precisamos saber qual propriedade foi alterada, mas também definir todas as propriedades para o objeto e obter assim seu estado, vamos salvar um instantâneo completo de suas propriedades, e então poderemos simplesmente copiar todas essas propriedades da memória do objeto para suas propriedades reais, sem ter que calcular que propriedade tirar da memória e qual de seu estado atual.

Para implementar uma classe-instantâneo de propriedades de objetos modificados, vamos usar a classe de propriedades do objeto CDataPropObj. Mas como precisaremos conhecer e considerar alguns parâmetros adicionais (hora da mudança, símbolo e seu Digits), nossa classe de instantâneo de propriedades alteradas do objeto será herdada da classe do objeto de propriedades do objeto gráfico.

A classe de memória de objeto gráfico conterá uma lista de objetos-instantâneos de propriedades alteradas e dará acesso ao trabalho com esta lista e os objetos que ela contém.

Colocamos essas duas classes no arquivo de classe de propriedades do objeto que já temos \MQL5\Include\DoEasy\Services\Properties.mqh.

Anexamos o arquivo de funções do serviço de biblioteca a ele:

//+------------------------------------------------------------------+
//|                                                   Properties.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 "DELib.mqh"
#include "XDimArray.mqh"
//+------------------------------------------------------------------+

Na seção pública da classe de propriedades do objeto inserimos os métodos que retornam o número de propriedades inteiras, reais e de string do objeto:

//--- Return the number of (1) integer, (2) real and (3) string parameters
   int               TotalLong(void)   const { return this.m_total_int; }
   int               TotalDouble(void) const { return this.m_total_dbl; }
   int               TotalString(void) const { return this.m_total_str; }

//--- Constructor

Precisaremos desses métodos para definir o número de propriedades dos objetos da classe de histórico de mudanças.

Logo após a classe de propriedades do objeto, escrevemis uma classe de instantâneo de propriedades do objeto modificado:

//+------------------------------------------------------------------+
//| Object changed property snapshot class                           |
//+------------------------------------------------------------------+
class CChangedProps : public CDataPropObj
  {
private:
  long               m_time_change;                         // Property modification time
  string             m_symbol;                              // Chart window symbol
  int                m_digits;                              // Symbol's Digits
public:
//--- Set the (1) change time value, (2) symbol and (3) symbol's Digits
   void              SetTimeChanged(const long time)        { this.m_time_change=time;                   }
   void              SetSymbol(const string symbol)         { this.m_symbol=symbol;                      }
   void              SetDigits(const int digits)            { this.m_digits=digits;                      }
//--- Return the (1) change time value, (2) change time, (3) symbol and (4) symbol's Digits
   long              TimeChanged(void)                const { return this.m_time_change;                 }
   string            TimeChangedToString(void)        const { return TimeMSCtoString(this.m_time_change);}
   string            Symbol(void)                     const { return this.m_symbol;                      }
   int               Digits(void)                     const { return this.m_digits;                      }
//--- Constructor/destructor
                     CChangedProps (const int prop_total_integer,const int prop_total_double,const int prop_total_string,const long time_changed) : 
                        CDataPropObj(prop_total_integer,prop_total_double,prop_total_string) { this.m_time_change=time_changed;}
                    ~CChangedProps (void){;}
  };
//+------------------------------------------------------------------+

Como se pode ver, a classe é herdeira da classe do objeto-propriedade de objeto gráfico. Assim, ela tem todas as propriedades de seu pai, mais as propriedades adicionais prescritas nesta classe. E aqui temos apenas o tempo de modificação do objeto em milissegundos, o símbolo gráfico onde o objeto gráfico foi modificado e o valor Digits do símbolo — para indicar corretamente o número de casas decimais na propriedade de preço do objeto.
Passamos o número de propriedades inteiras, reais e de strings e seu tempo de mudança em milissegundos ao construtor da classe.

Desta forma, podemos sempre criar uma cópia dos parâmetros do objeto gráfico e colocá-la na lista do objeto da classe de histórico de parâmetros. Escreveremos esta classe no mesmo arquivo - logo abaixo da classe de instantâneo das propriedades do objeto modificado:

//+------------------------------------------------------------------+
//| Class of the history of graphical object property changes        |
//+------------------------------------------------------------------+
class CChangeHistory
  {
private:
  CArrayObj          m_list_changes;                  // List of the property change history
public:
//--- Return (1) the pointer to the property change history object and (2) the number of changes
   CChangedProps    *GetChangedPropsObj(const string source,const int index)
                       {
                        CChangedProps *props=this.m_list_changes.At(index<0 ? 0 : index);
                        if(props==NULL)
                           CMessage::ToLog(source,MSG_GRAPH_OBJ_FAILED_GET_HIST_OBJ);
                        return props;
                       }
   int               TotalChanges(void)               { return this.m_list_changes.Total();  }
//--- Create a new object of the graphical object property change history
   bool              CreateNewElement(CDataPropObj *element,const long time_change)
                       {
                        //--- Create a new object of the graphical object property snapshot
                        CChangedProps *obj=new CChangedProps(element.TotalLong(),element.TotalDouble(),element.TotalString(),time_change);
                        //--- If failed to create an object, inform of that and return 'false'
                        if(obj==NULL)
                          {
                           CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_NEW_HIST_OBJ);
                           return false;
                          }
                        //--- If failed to add the object to the list, inform of that, remove the object and return 'false'
                        if(!this.m_list_changes.Add(obj))
                          {
                           CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_HIST_LIST);
                           delete obj;
                           return false;
                          }
                        //--- Get the ID of the chart the graphical object is located on
                        long chart_id=element.GetLong(GRAPH_OBJ_PROP_CHART_ID,0);
                        //--- Set a chart symbol and symbol's Digits for the graphical object property snapshot object
                        obj.SetSymbol(::ChartSymbol(chart_id));
                        obj.SetDigits((int)::SymbolInfoInteger(obj.Symbol(),SYMBOL_DIGITS));
                        //--- Copy all integer properties
                        for(int i=0;i<element.TotalLong();i++)
                          {
                           int total=element.Long().Size(i);
                           if(obj.SetSizeRange(i,total))
                             {
                              for(int r=0;r<total;r++)
                                 obj.Long().Set(i,r,element.Long().Get(i,r));
                             }
                           else
                              CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE);
                          }
                        //--- Copy all real properties
                        for(int i=0;i<element.TotalDouble();i++)
                          {
                           int total=element.Double().Size(i);
                           if(obj.Double().SetSizeRange(i,total))
                             {
                              for(int r=0;r<total;r++)
                                 obj.Double().Set(i,r,element.Double().Get(i,r));
                             }
                           else
                              CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE);
                          }
                        //--- Copy all string properties
                        for(int i=0;i<element.TotalString();i++)
                          {
                           int total=element.String().Size(i);
                           if(obj.String().SetSizeRange(i,total))
                             {
                              for(int r=0;r<total;r++)
                                 obj.String().Set(i,r,element.String().Get(i,r));
                             }
                           else
                              CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE);
                          }
                        return true;
                       }
//--- Return by index in the list of the graphical object change history object
//--- the value from the specified index of the (1) long, (2) double and (3) string array
   long              GetLong(const int time_index,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const int index)
                       {
                        CChangedProps *properties=this.GetChangedPropsObj(DFUN,time_index);
                        if(properties==NULL)
                           return 0;
                        return properties.GetLong(prop,index);
                       }
   double            GetDouble(const int time_index,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const int index)
                       {
                        CChangedProps *properties=this.GetChangedPropsObj(DFUN,time_index);
                        if(properties==NULL)
                           return 0;
                        return properties.GetDouble(prop,index);
                       }
   string            GetString(const int time_index,const ENUM_GRAPH_OBJ_PROP_STRING prop,const int index)
                       {
                        CChangedProps *properties=this.GetChangedPropsObj(DFUN,time_index);
                        if(properties==NULL)
                           return "";
                        return properties.GetString(prop,index);
                       }
//--- Constructor/destructor
                     CChangeHistory(void){;}
                    ~CChangeHistory(void){;}
  };
//+------------------------------------------------------------------+

A classe também é simples. Ela possui uma lista na qual serão registradas todas as mudanças do objeto gráfico, representadas pelos objetos da classe de instantâneo das propriedades alteradas do objeto gráfico.

O método para criar um novo instantâneo das propriedades modificadas recebe as propriedades atuais do objeto gráfico (já modificado) e o tempo de modificação em milissegundos, um novo objeto-instantâneo de propriedades é criado e adicionado à lista. Em seguida, os parâmetros adicionais são definidos para o objeto, e então todas as propriedades do objeto gráfico modificado passadas para o método de objeto são copiadas para o objeto criado em três loops.

Desta forma, sempre que um objeto gráfico é modificado, uma cópia de suas propriedades é criada e inserida na lista. Assim, podemos recuperar um ponteiro para qualquer objeto de propriedade armazenado a partir desta lista e usá-lo para as necessidades do programa.

Todos os métodos da classe são idênticos entre si, sua lógica é bastante transparente: recuperamos o objeto de propriedades desejado da lista e devolvemos a propriedade solicitada da mesma.

Você pode colocar quaisquer perguntas sobre como esses métodos funcionam na discussão do artigo — penso que não adianta descrevê-las aqui, porque a lógica de tais métodos já foi discutida muitas vezes em artigos anteriores, e não vamos repetí-la para não ocupar espaço com sua descrição.

Agora na classe de dados de propriedades atuais e anteriores, no mesmo arquivo, adicionamos um ponteiro para o objeto do histórico de mudanças, método que retorna o número de mudanças no objeto gráfico, construímos um novo objeto de histórico de mudanças no construtor da classe, e o eliminamos no destruidor:

//+------------------------------------------------------------------+
//| Data class of the current and previous properties                |
//+------------------------------------------------------------------+
class CProperties : public CObject
  {
private:
   CArrayObj         m_list;  // List for storing the pointers to property objects
public:
   CDataPropObj     *Curr;    // Pointer to the current properties object
   CDataPropObj     *Prev;    // Pointer to the previous properties object
   CChangeHistory   *History; // Pointer to the change history object
//--- Set the array size ('size') in the specified dimension ('range')
   bool              SetSizeRange(const int range,const int size)
                       {
                        return(this.Curr.SetSizeRange(range,size) && this.Prev.SetSizeRange(range,size) ? true : false);
                       }
//--- Return the size of the specified array of the (1) current and (2) previous first dimension data
   int               CurrSize(const int range)  const { return Curr.Size(range); }
   int               PrevSize(const int range)  const { return Prev.Size(range); }
//--- Copy the current data to the previous one
   void              CurrentToPrevious(void)
                       {
                        //--- Copy all integer properties
                        for(int i=0;i<this.Curr.Long().Total();i++)
                           for(int r=0;r<this.Curr.Long().Size(i);r++)
                              this.Prev.Long().Set(i,r,this.Curr.Long().Get(i,r));
                        //--- Copy all real properties
                        for(int i=0;i<this.Curr.Double().Total();i++)
                           for(int r=0;r<this.Curr.Double().Size(i);r++)
                              this.Prev.Double().Set(i,r,this.Curr.Double().Get(i,r));
                        //--- Copy all string properties
                        for(int i=0;i<this.Curr.String().Total();i++)
                           for(int r=0;r<this.Curr.String().Size(i);r++)
                              this.Prev.String().Set(i,r,this.Curr.String().Get(i,r));
                       }
//--- Return the amount of graphical object changes since the start of recording them
   int               TotalChanges(void)   { return this.History.TotalChanges();  }
                       
//--- Constructor
                     CProperties(const int prop_int_total,const int prop_double_total,const int prop_string_total)
                       {
                        //--- Create new objects of the current and previous properties
                        this.Curr=new CDataPropObj(prop_int_total,prop_double_total,prop_string_total);
                        this.Prev=new CDataPropObj(prop_int_total,prop_double_total,prop_string_total);
                        //--- Add newly created objects to the list
                        this.m_list.Add(this.Curr);
                        this.m_list.Add(this.Prev);
                        //--- Create the change history object
                        this.History=new CChangeHistory();
                       }
//--- Destructor
                    ~CProperties()
                       {
                        this.m_list.Clear();
                        this.m_list.Shutdown();
                        if(this.History!=NULL)
                           delete this.History;
                       }
  };
//+------------------------------------------------------------------+

Uma lista de mudanças destas propriedades será agora armazenada nas propriedades de cada objeto gráfico.

Para poder interagir com o histórico de mudança de objeto gráfico, no arquivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh da classe de objeto gráfico padrão abstrato nós precisamos adicionar o método que retorna um ponteiro para a lista do histórico de mudanças, localizado nas propriedades do objeto:

public:
//--- Set object's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value)     { this.Prop.Curr.SetLong(property,index,value);    }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value)    { this.Prop.Curr.SetDouble(property,index,value);  }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value)    { this.Prop.Curr.SetString(property,index,value);  }
//--- Return object’s (1) integer, (2) real and (3) string property from the properties array
   long              GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)          const { return this.Prop.Curr.GetLong(property,index);   }
   double            GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)           const { return this.Prop.Curr.GetDouble(property,index); }
   string            GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index)           const { return this.Prop.Curr.GetString(property,index); }
//--- Set object's previous (1) integer, (2) real and (3) string properties
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value) { this.Prop.Prev.SetLong(property,index,value);    }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value){ this.Prop.Prev.SetDouble(property,index,value);  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value){ this.Prop.Prev.SetString(property,index,value);  }
//--- Return object’s (1) integer, (2) real and (3) string property from the previous properties array
   long              GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)      const { return this.Prop.Prev.GetLong(property,index);   }
   double            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)       const { return this.Prop.Prev.GetDouble(property,index); }
   string            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index)       const { return this.Prop.Prev.GetString(property,index); }
   
//--- Return (1) itself, (2) properties and (3) the change history
   CGStdGraphObj    *GetObject(void)                                       { return &this;            }
   CProperties      *Properties(void)                                      { return this.Prop;        }
   CChangeHistory   *History(void)                                         { return this.Prop.History;}
   
//--- Return the flag of the object supporting this property

No construtor padrão da classe modificamos a indicação do grupo do objeto para a indicação de tipo, e também passamos o tipo de objeto a um construtor paramétrico fechado, em vez de um grupo, como antes:

//--- Default constructor
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; }
//--- Destructor
                    ~CGStdGraphObj()
                       {
                        if(this.Prop!=NULL)
                           delete this.Prop;
                       }
protected:
//--- Protected parametric constructor
                     CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                                   const ENUM_GRAPH_OBJ_BELONG belong,
                                   const ENUM_GRAPH_OBJ_SPECIES species,
                                   const long chart_id, const int pivots,
                                   const string name);

Escrevemos os métodos para definição e retorno do sinalizador de armazenamento de histórico de alterações do objeto e os grupos de objetos gráficos no bloco de métodos de acesso simplificado e de definição de propriedades de objeto gráfico:

public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+
//--- Object index in the list
   int               Number(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM,0);                            }
   void              SetNumber(const int number)         { this.SetProperty(GRAPH_OBJ_PROP_NUM,0,number);                                 }
//--- Flag of storing the change history
   bool              AllowChangeHistory(void)      const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0);                }
   void              SetAllowChangeMemory(const bool flag){ this.SetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0,flag);                       }
//--- Object ID
   long              ObjectID(void)                const { return this.GetProperty(GRAPH_OBJ_PROP_ID,0);                                  }
   void              SetObjectID(const long obj_id)
                       {
                        CGBaseObj::SetObjectID(obj_id);
                        this.SetProperty(GRAPH_OBJ_PROP_ID,0,obj_id);
                        this.SetPropertyPrev(GRAPH_OBJ_PROP_ID,0,obj_id);
                       }
//--- Graphical object type
   ENUM_OBJECT       GraphObjectType(void)         const { return (ENUM_OBJECT)this.GetProperty(GRAPH_OBJ_PROP_TYPE,0);                   }
   void              SetGraphObjectType(const ENUM_OBJECT obj_type)
                       {
                        CGBaseObj::SetTypeGraphObject(obj_type);
                        this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,obj_type);
                       }
//--- Graphical element type
   ENUM_GRAPH_ELEMENT_TYPE GraphElementType(void)  const { return (ENUM_GRAPH_ELEMENT_TYPE)this.GetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0);}
   void              SetGraphElementType(const ENUM_GRAPH_ELEMENT_TYPE elm_type)
                       {
                        CGBaseObj::SetTypeElement(elm_type);
                        this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,elm_type);
                       }
//--- Graphical object affiliation
   ENUM_GRAPH_OBJ_BELONG Belong(void)              const { return (ENUM_GRAPH_OBJ_BELONG)this.GetProperty(GRAPH_OBJ_PROP_BELONG,0);       }
   void              SetBelong(const ENUM_GRAPH_OBJ_BELONG belong)
                       {
                        CGBaseObj::SetBelong(belong);
                        this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,belong);
                       }
//--- Group of graphical objects
   int               Group(void)                   const { return (int)this.GetProperty(GRAPH_OBJ_PROP_GROUP,0);                          }
   void              SetGroup(const int group)
                       {
                        CGBaseObj::SetGroup(group);
                        this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,group);
                       }
//--- Chart ID

Cada objeto gráfico no gráfico não registrará seu histórico de mudanças por padrão. Para que comece a fazer isso, é usado o método de definição do sinalizador que permite o registro do histórico de mudanças. Bem, um grupo de objetos gráficos é diferentes objetos gráficos em um gráfico, que combinamos em um grupo geral, pelo qual podemos selecioná-los e manuseá-los como quisermos.

Inserimos os métodos para lidar com o histórico de mudanças do objeto:

//--- Re-write all graphical object properties
   void              PropertiesRefresh(void);
//--- Check object property changes
   void              PropertiesCheckChanged(void);
//--- Copy the current data to the previous one
   void              PropertiesCopyToPrevData(void);
//--- Return (1) the number of property changes in history specified (2) by the property index, (3) the last and (4) the first changed object
   int               HistoryChangesTotal(void)                          { return this.History().TotalChanges();                                          }
   CChangedProps    *GetHistoryChangedProps(const string source,const int index) { return this.History().GetChangedPropsObj(source,index);               }
   CChangedProps    *GetHistoryChangedPropsLast(const string source)    { return this.History().GetChangedPropsObj(source,this.HistoryChangesTotal()-1); }
   CChangedProps    *GetHistoryChangedPropsFirst(const string source)   { return this.History().GetChangedPropsObj(source,0);                            }
//--- Using the specified index in the list of change history objects, return
//--- the specified value of (1) integer, (2) real and (3) string property
   long              HistoryChangedObjGetLong(const int time_index,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const int prop_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.GetLong(prop,prop_index) : 0);
                       }
   double            HistoryChangedObjGetDouble(const int time_index,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const int prop_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.GetDouble(prop,prop_index) : 0);
                       }
   string            HistoryChangedObjGetString(const int time_index,const ENUM_GRAPH_OBJ_PROP_STRING prop,const int prop_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.GetString(prop,prop_index) : "ERROR");
                       }
//--- Return (1) a symbol, (2) symbol's Digits and (3) the time of changing the change history object
   string            HistoryChangedObjSymbol(const int time_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.Symbol() : "ERROR");
                       }
   int               HistoryChangedObjDigits(const int time_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.Digits() : 0);
                       }
   long              HistoryChangedObjTimeChanged(const int time_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.TimeChanged() : 0);
                       }
   string            HistoryChangedObjTimeChangedToString(const int time_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.TimeChangedToString() : "ERROR");
                       }
//--- Set object parameters from the specified history snapshot
   bool              SetPropertiesFromHistory(const int time_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        if(obj==NULL)
                           return 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;
                           for(int j=0;j<this.Prop.CurrSize(prop);j++)
                              if(this.GetProperty(prop,j)!=obj.GetLong(prop,j))
                                 this.SetHistoryINT(prop,obj.GetLong(prop,j),j);
                          }
                        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;
                           for(int j=0;j<this.Prop.CurrSize(prop);j++)
                             {
                              if(this.GetProperty(prop,j)!=obj.GetDouble(prop,j))
                                 this.SetHistoryDBL(prop,obj.GetDouble(prop,j),j);
                             }
                          }
                        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;
                           for(int j=0;j<this.Prop.CurrSize(prop);j++)
                              if(this.GetProperty(prop,j)!=obj.GetString(prop,j))
                                 this.SetHistorySTR(prop,obj.GetString(prop,j),j);
                          }
                        return true;
                       }

Quase todos os métodos retornam o resultado da chamada de métodos homônimos da classe de histórico de mudanças do objeto gráfico.

O método que define as propriedades do objeto gráfico a partir de um determinado instantâneo de histórico recupera esse objeto por índice, e então em três loops define todas as propriedades do objeto-instantâneo de histórico em um objeto gráfico por meio dos métodos SetHistoryINT(), SetHistoryDBL() e  SetHistorySTR() que veremos a seguir.

E adicionamos os métodos à seção privada da classe:

private:
//--- Get and save (1) integer, (2) real and (3) string properties
   void              GetAndSaveINT(void);
   void              GetAndSaveDBL(void);
   void              GetAndSaveSTR(void);
   
//--- Create a new object of the graphical object change history
   bool              CreateNewChangeHistoryObj(const bool first)
                       {
                        bool res=true;
                        if(first)
                           res &=this.History().CreateNewElement(this.Prop.Prev,this.GetMarketWatchTime());
                        res &=this.History().CreateNewElement(this.Prop.Curr,this.GetMarketWatchTime());
                        if(!res)
                           CMessage::ToLog(DFUN,MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_SNAPSHOT);
                        return res;
                       }
//--- Set (1) integer, (2) real and (3) string property values from the change history
   void              SetHistoryINT(const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier);
   void              SetHistoryDBL(const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier);
   void              SetHistorySTR(const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier);
//--- Return the time of the last symbol tick
   long              GetSymbolTime(const string symbol)
                       {
                        MqlTick tick;
                        return(::SymbolInfoTick(symbol,tick) ? tick.time_msc : 0);
                       }
//--- Return the time of the last Market Watch tick
   long              GetMarketWatchTime(void)
                       {
                        long res=0;
                        for(int i=::SymbolsTotal(true)-1;i>WRONG_VALUE;i--)
                          {
                           const long time=this.GetSymbolTime(::SymbolName(i,true));
                           if(time>res)
                              res=time;
                          }
                        return res;
                       }   
  };
//+------------------------------------------------------------------+

O método que cria o novo objeto do histórico de alterações do objeto gráfico recebe o sinalizador da primeira alteração do objeto gráfico.
Caso o sinalizador estiver definido, esta é a primeira alteração, e primeiro devemos salvar o estado anterior do objeto gráfico no histórico (estado esse que era antes da mudança de propriedades). E em seguida registramos o estado atual do objeto no histórico. Caso o sinalizador não esteja definido, salvamos imediatamente o estado atual do objeto em seu histórico de mudanças:

//--- Create a new object of the graphical object change history
   bool              CreateNewChangeHistoryObj(const bool first)
                       {
                        bool res=true;
                        if(first)
                           res &=this.History().CreateNewElement(this.Prop.Prev,this.GetMarketWatchTime());
                        res &=this.History().CreateNewElement(this.Prop.Curr,this.GetMarketWatchTime());
                        if(!res)
                           CMessage::ToLog(DFUN,MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_SNAPSHOT);
                        return res;
                       }

O resultado dos métodos invocados é adicionado ao valor da variável resultante res, e terá valor false somente se algum dos métodos chamados tiver retornado false. Como resultado, devolvemos o valor desta variável.

O método que retorna o tempo do último tick da Observação do mercado percorrerá todos os símbolo na janela Observação do mercado, lerá seu tempo atual em milissegundos e comparando o tempo de cada símbolo devolve o mais recente:

//--- Return the time of the last Market Watch tick
   long              GetMarketWatchTime(void)
                       {
                        long res=0;
                        for(int i=::SymbolsTotal(true)-1;i>WRONG_VALUE;i--)
                          {
                           const long time=this.GetSymbolTime(::SymbolName(i,true));
                           if(time>res)
                              res=time;
                          }
                        return res;
                       }   

Ao construtor paramétrico protegido passamos o tipo de objeto gráfico (anteriormente passávamos ao grupo) e definimos todas as novas propriedades do objeto:

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_SPECIES species,
                             const long chart_id,const int pivots,
                             const string name)
  {
   //--- Create the property object with the default values
   this.Prop=new CProperties(GRAPH_OBJ_PROP_INTEGER_TOTAL,GRAPH_OBJ_PROP_DOUBLE_TOTAL,GRAPH_OBJ_PROP_STRING_TOTAL);
   
//--- Set the number of pivot points and object levels
   this.m_pivots=pivots;
   int levels=(int)::ObjectGetInteger(chart_id,name,OBJPROP_LEVELS);

//--- Set the property array dimensionalities according to the number of pivot points and levels
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_TIME,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_PRICE,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_BMPFILE,2);
   
//--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits
   this.m_type=obj_type;
   this.SetName(name);
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetSpecies(species);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Save the integer properties inherent in all graphical objects but not present in the current one
   this.SetProperty(GRAPH_OBJ_PROP_CHART_ID,0,CGBaseObj::ChartID());                // Chart ID
   this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow());               // Chart subwindow index
   this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,CGBaseObj::TypeGraphObject());            // Graphical object type (ENUM_OBJECT)
   this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,CGBaseObj::TypeGraphElement());   // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,CGBaseObj::Belong());                   // Graphical object affiliation
   this.SetProperty(GRAPH_OBJ_PROP_SPECIES,0,CGBaseObj::Species());                 // Graphical object species
   this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,0);                                      // Graphical object group
   this.SetProperty(GRAPH_OBJ_PROP_ID,0,0);                                         // Object ID
   this.SetProperty(GRAPH_OBJ_PROP_NUM,0,0);                                        // Object index in the list
   this.SetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0,false);                         // Flag of storing the change history<
   
//--- Save the properties inherent in all graphical objects and present in a graphical object
   this.PropertiesRefresh();
   
//--- Save basic properties in the parent object
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME,0);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,0);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,0);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0);

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


Ao método que retorna uma descrição de uma propriedade inteira de um objeto adicionar uma descrição das propriedades do novo objeto:

//+------------------------------------------------------------------+
//| Return description of object's integer property                  |
//+------------------------------------------------------------------+
string CGStdGraphObj::GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_INTEGER property)
  {
   return
     (
      property==GRAPH_OBJ_PROP_ID         ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ID)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_TYPE       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.TypeDescription()
         )  :
      property==GRAPH_OBJ_PROP_ELEMENT_TYPE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ELEMENT_TYPE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+CGBaseObj::TypeElementDescription()
         )  :
      property==GRAPH_OBJ_PROP_SPECIES    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+CGBaseObj::SpeciesDescription()
         )  :
      property==GRAPH_OBJ_PROP_GROUP    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(CGBaseObj::Group()>0 ? (string)this.GetProperty(property,0) : CMessage::Text(MSG_LIB_PROP_EMPTY))
         )  :
      property==GRAPH_OBJ_PROP_BELONG     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_BELONG)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+CGBaseObj::BelongDescription()
         )  :
      property==GRAPH_OBJ_PROP_CHART_ID   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_ID)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_WND_NUM    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_WND_NUM)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_CHANGE_HISTORY ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHANGE_MEMORY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_CREATETIME   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CREATETIME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::TimeToString(this.GetProperty(property,0),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==GRAPH_OBJ_PROP_TIMEFRAMES ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_TIMEFRAMES)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.VisibleOnTimeframeDescription()
         )  :
      property==GRAPH_OBJ_PROP_BACK       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_BACK)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IsBack() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_ZORDER     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_HIDDEN     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_HIDDEN)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IsHidden() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_SELECTED   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SELECTED)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IsSelected() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_SELECTABLE ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SELECTABLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IsSelectable() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_NUM        ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_NUM)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_TIME       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_TIME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+"\n"+this.TimesDescription()
         )  :
      property==GRAPH_OBJ_PROP_COLOR      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::ColorToString((color)this.GetProperty(property,0),true)
         )  :
      property==GRAPH_OBJ_PROP_STYLE      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_STYLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+LineStyleDescription((ENUM_LINE_STYLE)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_WIDTH     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_WIDTH)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_FILL       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_FILL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_READONLY   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_READONLY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_LEVELS     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_LEVELCOLOR ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELCOLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ":\n"+this.LevelsColorDescription()
         )  :
      property==GRAPH_OBJ_PROP_LEVELSTYLE ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELSTYLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ":\n"+this.LevelsStyleDescription()
         )  :
      property==GRAPH_OBJ_PROP_LEVELWIDTH ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELWIDTH)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ":\n"+this.LevelsWidthDescription()
         )  :
      property==GRAPH_OBJ_PROP_ALIGN      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ALIGN)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignModeDescription((ENUM_ALIGN_MODE)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_FONTSIZE   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_FONTSIZE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_RAY_LEFT   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_RAY_LEFT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_RAY_RIGHT  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_RAY_RIGHT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_RAY        ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_RAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_ELLIPSE    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ELLIPSE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_ARROWCODE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ARROWCODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_ANCHOR     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ANCHOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.AnchorDescription()
         )  :
      property==GRAPH_OBJ_PROP_XDISTANCE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_XDISTANCE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_YDISTANCE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_YDISTANCE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_DIRECTION  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_DIRECTION)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+GannDirectDescription((ENUM_GANN_DIRECTION)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_DEGREE     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_DEGREE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+ElliotWaveDegreeDescription((ENUM_ELLIOT_WAVE_DEGREE)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_DRAWLINES  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_DRAWLINES)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_STATE      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_STATE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_BUTTON_STATE_PRESSED) : CMessage::Text(MSG_LIB_TEXT_BUTTON_STATE_DEPRESSED))
         )  :
      property==GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID  ?  CMessage::Text(MSG_CHART_OBJ_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_CHART_OBJ_PERIOD ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_OBJ_PERIOD)+
         (!this.SupportProperty(property)       ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TimeframeDescription((ENUM_TIMEFRAMES)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE)+
         (!this.SupportProperty(property)             ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE)+
         (!this.SupportProperty(property)             ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE)+
         (!this.SupportProperty(property)             ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_XSIZE      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_XSIZE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_YSIZE      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_YSIZE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_XOFFSET    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_XOFFSET)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_YOFFSET    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_YOFFSET)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_BGCOLOR    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_BGCOLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::ColorToString((color)this.GetProperty(property,0),true)
         )  :
      property==GRAPH_OBJ_PROP_CORNER     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CORNER)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+BaseCornerDescription((ENUM_BASE_CORNER)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_BORDER_TYPE   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_BORDER_TYPE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+BorderTypeDescription((ENUM_BORDER_TYPE)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_BORDER_COLOR  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_BORDER_COLOR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::ColorToString((color)this.GetProperty(property,0),true)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

No método que verifica as mudanças das propriedades do objeto inserimos um bloco de código que cria um novo objeto de histórico de modificações de um objeto gráfico desde que o sinalizador de permissão de gravação do histórico de mudanças esteja definida:

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

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

   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j) && prop!=GRAPH_OBJ_PROP_NAME)
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }
   if(changed)
     {
      for(int i=0;i<this.m_list_events.Total();i++)
        {
         CGBaseEvent *event=this.m_list_events.At(i);
         if(event==NULL)
            continue;
         ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam());
        }
      if(this.AllowChangeHistory())
        {
         int total=HistoryChangesTotal();
         if(this.CreateNewChangeHistoryObj(total<1))
            ::Print
              (
               DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT)," #",(total==0 ? "0-1" : (string)total),
               ": ",this.HistoryChangedObjTimeChangedToString(total-1)
              );
        }
      this.PropertiesCopyToPrevData();
     }
  }
//+------------------------------------------------------------------+

Aqui: obtemos o número de mudanças do objeto gráfico e o passamos para o método de criação de novo objeto-instantâneo de propriedades de objeto como um sinalizador bool (se total for menor que 1, o valor passado será true, o que significa que esta é a primeira mudança do objeto gráfico). Se o objeto foi criado e adicionado à lista de modificações com sucesso, no log é exibida uma mensagem com o número da modificação. Caso esta seja a primeira mudança, a mensagem indica "0-1"., o que significa que dois objetos foram criados de uma só vez (0 — estado de um objeto gráfico antes de suas propriedades serem alteradas, 1 — seu estado atual).

Métodos que definem os valores das propriedades inteiras, reais e de string do objeto gráfico por meio do histórico de modificações:

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

Aqui: dependendo da propriedade passada para o método, no operador-alternador switch é escolhido o método apropriado que define o valor para esta propriedade tanto no objeto-classe como no objeto gráfico. Como as propriedades, que não precisam ser definidas para o objeto do histórico de mudança de propriedades, não possuem seu próprio manipulador case, a execução do código atinge o rótulo default, onde termina com o operador break.

No arquivo da classe-coleção de elementos gráficos \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh inserimos o método que retorna uma lista de objetos por ID do gráfico e grupo:

//--- Return (1) the last removed graphical object and (2) the array size of graphical object properties
   CGStdGraphObj    *GetLastDeletedGraphObj(void)                 const { return this.m_list_deleted_obj.At(this.m_list_deleted_obj.Total()-1); }
   int               GetSizeProperty(const string name,const long chart_id,const int prop)
                       {
                        CGStdGraphObj *obj=this.GetStdGraphObject(name,chart_id);
                        return(obj!=NULL ? obj.Properties().CurrSize(prop) : 0);
                       }
//--- Return the list of objects by chart ID and group
   CArrayObj        *GetListStdGraphObjByGroup(const long chart_id,const int group)
                       {
                        CArrayObj *list=GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
                        return CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_GROUP,0,group,EQUAL);
                       }
   
//--- Constructor

O método não apenas recebe a lista que contém a lista de todos os objetos gráficos pelo identificador do gráfico, senão que também retorna a lista de objetos de valor especificado por meio da lista obtida.

Em nossos programas, com este método podemos obter a lista de objetos gráficos à qual é atribuído um grupo, para que se possam tomar as ações necessárias sobre esses objetos.

Para recuperar todos os dados do histórico de mudanças de objetos gráficos em programas sob controle da biblioteca, precisamos fazer mudanças no objeto principal da biblioteca CEngine no arquivo \MQL5\Include\DoEasy\Engine.mqh.

Inserimos o método que retorna a lista de objetos gráficos existentes e o método que retorna o ponteiro para a classe do objeto gráfico padrão pelo nome e o identificador do gráfico:

//--- Return the (1) collection of graphical objects, the list of (2) existing and (3) removed graphical objects
   CGraphElementsCollection *GetGraphicObjCollection(void)              { return &this.m_graph_objects;                       }
   CArrayObj           *GetListStdGraphObj(void)                        { return this.m_graph_objects.GetListGraphObj();      }
   CArrayObj           *GetListDeletedObj(void)                         { return this.m_graph_objects.GetListDeletedObj();    }
//--- Return (1) the number of removed graphical objects and (2) the size of the property array
   int                  TotalDeletedGraphObjects(void)                  { return this.GetListDeletedObj().Total();            }
   int                  GraphGetSizeProperty(const string name,const long chart_id,const int prop)
                          {
                           return this.m_graph_objects.GetSizeProperty(name,chart_id,prop);
                          }
//--- Return the class of the object of the standard graphical object by chart name and ID
   CGStdGraphObj       *GraphGetStdGraphObject(const string name,const long chart_id)
                          {
                           return this.m_graph_objects.GetStdGraphObject(name,chart_id);
                          }
   
//--- Fill in the array with IDs of the charts opened in the terminal

Os métodos retornam o resultado da chamada dos métodos homônimos da classe-coleção de elementos gráficos.

Estas são todas as mudanças e ajustes para as classes da biblioteca necessários para testar a operação do histórico de mudança de objetos gráficos.


Teste

Para o teste, usaremos o Expert Advisor do último artigo e o salvaremos na nova pasta \MQL5\Experts\TestDoEasy\Part92\ com o novo nome TestDoEasyPart92.mq5.

Como vamos testar? Para cada objeto gráfico recém-criado no gráfico definimos um sinalizador que permita salvar seu histórico de mudanças e definimos o grupo número 1. Desse modo, todos os objetos gráficos a serem adicionados serão incluídos no mesmo grupo e poderão registrar seu histórico de mudanças. Em seguida, alteramos cada objeto gráfico - todas as mudanças serão gravadas na memória de cada um deles.

Para controlar a visualização do histórico de mudanças, definimos teclas.

  • A tecla ">" ("." sem Shift) irá mover o índice na lista de mudança de objeto em 1 para cima,
  • A tecla "<" ("," sem Shift) irá mover o índice na lista de mudança de objeto em 1 para baixo,
  • A tecla "/" irá mover o índice na lista de mudança de objeto para o início — nesse local será armazenado o objeto de propriedades do objeto gráfico com seus valores originais, isto é, com aqueles antes de ser modificado pela primeira vez.

Ao pressionar estas teclas, veremos o objeto gráfico obter todas as propriedades que ele tinha cada vez que era alterado.

Para os botões especificados, definimos as substituições de macros:

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

Adicionamos o bloco de código que processa o pressionamento de tecla ao o manipulador de eventos; adicionamos a definição de sinalizador que permite salvar o histórico de mudanças ao bloco de processamento do evento de criação do objeto gráfico; e o grupo nº1:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If working in the tester, exit
   if(MQLInfoInteger(MQL_TESTER))
      return;
//--- If the mouse is moved
   /*
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      CForm *form=NULL;
      datetime time=0;
      double price=0;
      int wnd=0;
      
      //--- If Ctrl is not pressed,
      if(!IsCtrlKeyPressed())
        {
         //--- clear the list of created form objects, allow scrolling a chart with the mouse and show the context menu
         list_forms.Clear();
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,true);
         ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,true);
         return;
        }
      
      //--- If X and Y chart coordinates are successfully converted into time and price,
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- get the bar index the cursor is hovered over
         int index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         if(index==WRONG_VALUE)
            return;
         
         //--- Get the bar index by index
         CBar *bar=engine.SeriesGetBar(Symbol(),Period(),index);
         if(bar==NULL)
            return;
         
         //--- Convert the coordinates of a chart from the time/price representation of the bar object to the X and Y coordinates
         int x=(int)lparam,y=(int)dparam;
         if(!ChartTimePriceToXY(ChartID(),0,bar.Time(),(bar.Open()+bar.Close())/2.0,x,y))
            return;
         
         //--- Disable moving a chart with the mouse and showing the context menu
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,false);
         ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,false);
         
         //--- Create the form object name and hide all objects except one having such a name
         string name="FormBar_"+(string)index;
         HideFormAllExceptOne(name);
         
         //--- If the form object with such a name does not exist yet,
         if(!IsPresentForm(name))
           {
            //--- create a new form object
            form=bar.CreateForm(index,name,x,y,114,16);   
            if(form==NULL)
               return;
            
            //--- Set activity and unmoveability flags for the form
            form.SetActive(true);
            form.SetMovable(false);
            //--- Set the opacity of 200
            form.SetOpacity(200);
            //--- The form background color is set as the first color from the color array
            form.SetColorBackground(array_clr[0]);
            //--- Form outlining frame color
            form.SetColorFrame(C'47,70,59');
            //--- Draw the shadow drawing flag
            form.SetShadow(true);
            //--- Calculate the shadow color as the chart background color converted to the monochrome one
            color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
            //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units
            //--- Otherwise, use the color specified in the settings for drawing the shadow
            color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3);
            //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
            //--- Set the shadow opacity to 200, while the blur radius is equal to 4
            form.DrawShadow(2,2,clr,200,3);
            //--- Fill the form background with a vertical gradient
            form.Erase(array_clr,form.Opacity());
            //--- Draw an outlining rectangle at the edges of the form
            form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
            //--- If failed to add the form object to the list, remove the form and exit the handler
            if(!list_forms.Add(form))
              {
               delete form;
               return;
              }
            //--- Capture the form appearance
            form.Done();
           }
         //--- If the form object exists,
         if(form!=NULL)
           {
            //--- draw a text with the bar type description on it and show the form. The description corresponds to the mouse cursor position
            form.TextOnBG(0,bar.BodyTypeDescription(),form.Width()/2,form.Height()/2-1,FRAME_ANCHOR_CENTER,C'7,28,21');
            form.Show();
           }
         //--- Re-draw the chart
         ChartRedraw();
        }
     }
   */
   //--- If a key is pressed
   if(id==CHARTEVENT_KEYDOWN)
     {
      //---Declare the index of the current graphical object change history object
      static int index=0;
      //--- Get the list of all graphical objects with the specified group index (1)
      CArrayObj *list=engine.GetListStdGraphObj();
      list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_GROUP,0,1,EQUAL);
      if(list==NULL || list.Total()==0)
         return;
      //--- If "/" is pressed
      if(lparam==KEY_ORIGIN)
        {
         //--- Set the index 0 in the list of the graphical object change history object
         index=0;
         //--- In the loop by the number of group 1 objects on the chart
         for(int i=0;i<list.Total();i++)
           {
            //--- Get the next object from the list and set the initial properties from the change history to it
            CGStdGraphObj *obj=list.At(i);
            if(obj==NULL)
               continue;
            obj.SetPropertiesFromHistory(index);
           }
        }
      //--- If "." is pressed
      if(lparam==KEY_RIGHT)
        {
         //--- Declare the variables to search for the maximum number of changes of all group 1 graphical objects
         int change_max=0, changes_total=0;
         //--- Increase the object index in the list of the graphical object change history
         index++;
         //--- In the loop by the number of group 1 objects on the chart
         for(int i=0;i<list.Total();i++)
           {
            //--- Get the next object from the list
            CGStdGraphObj *obj=list.At(i);
            if(obj==NULL)
               continue;
            //--- Calculate the maximum number of changes of all group 1 graphical objects
            changes_total=obj.HistoryChangesTotal();
            if(changes_total>change_max)
               change_max=changes_total;
            //--- Set the properties (by 'index' from the list) from the change history
            obj.SetPropertiesFromHistory(index>obj.HistoryChangesTotal()-1 ? obj.HistoryChangesTotal()-1 : index);
           }
         //--- If the change history object index exceeds the maximum number of changes of all objects,
         //--- set the index equal to the maximum amount of changes of all graphical objects
         if(index>change_max-1)
            index=change_max-1;
        }
      //--- If "," is pressed
      if(lparam==KEY_LEFT)
        {
         //--- Decrease the object index in the list of the graphical object change history
         index--;
         //--- If the index is less than zero, set it to 0
         if(index<0)
            index=0;
         //--- In the loop by the number of group 1 objects on the chart
         for(int i=0;i<list.Total();i++)
           {
            //--- Get the next object from the list
            CGStdGraphObj *obj=list.At(i);
            if(obj==NULL)
               continue;
            //--- Set the properties (by 'index' from the list) from the change history
            obj.SetPropertiesFromHistory(index);
           }
        }
      //--- Re-draw the chart for displaying changes in graphical objects
      ChartRedraw();
     }
   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         long array[];
         engine.GraphGetArrayChartsID(array);
         for(int i=0;i<ArraySize(array);i++)
            engine.CreateLineVertical(array[i],"LineVertical",0,time);
        }
     }
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);

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

Toda a lógica do manuseio de teclas é explicada nos comentários ao código.

Vamos compilar o Expert Advisor e executá-lo no gráfico. Adicionamos objetos gráficos, modificamos suas propriedades, e, em seguida, pressionamos a tecla "/" — os objetos assumem os valores que eles tinham antes de serem modificados pela primeira vez. Então pressionamos as teclas "." e "," — os objetos assumem propriedades e visuais correspondentes à lista do histórico de propriedades:


 

O que vem agora?

No próximo artigo, vamos começar a desenvolver objetos gráficos compostos.

Todos os arquivos da versão atual da biblioteca e o arquivo do EA de teste, bem como o indicador de controle de eventos de gráficos para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo por conta própria. Se você tiver dúvidas, comentários e sugestões, pode expressá-los nos comentários ao artigo.

Complementos

*Artigos desta série:

Gráficos na biblioteca DoEasy (Parte 89): programando objetos gráficos padrão. Funcionalidade básica
Gráficos na biblioteca DoEasy (Parte 90): eventos de objetos gráficos padrão. Funcionalidade básica
Gráficos na biblioteca DoEasy (Parte 91): eventos de objetos gráficos padrão no programa. Histórico de alterações de nome do objeto

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/10237

Arquivos anexados |
MQL5.zip (4189.12 KB)
Desenvolvendo um EA de negociação do zero (Parte 07): Adicionando o Volume At Price (I) Desenvolvendo um EA de negociação do zero (Parte 07): Adicionando o Volume At Price (I)
Este é um dos indicadores mais poderosos que existe. Para quem opera e tenta ter um certo grau de assertividade, não pode deixar de ter este indicador em seu gráfico, apesar de ele ser mais utilizado por quem opera observando o Fluxo (Tape Reading ) ele também pode ser usado por aqueles que fazem uso apenas do Price Action.
Avaliação visual de resultados de otimização Avaliação visual de resultados de otimização
Neste artigo discutiremos como plotar todas passagens das otimizações e como selecionar o critério ótimo personalizado. Além disso, falaremos sobre como programarmos o que quisermos, simplesmente recorrendo a um conhecimento mínimo em MQL5, a uma grande vontade, ao uso dos artigos do site e aos comentários do fórum.
Matemática na negociação: indices de Sharpe e de Sortino Matemática na negociação: indices de Sharpe e de Sortino
A rentabilidade é a medida mais óbvia que investidores e operadores novatos utilizam para analisar o desempenho da negociação. Já os traders profissionais empregam ferramentas mais robustas para analisar estratégias, entre elas os índices de Sharpe e de Sortino.
Indicadores múltiplos em um gráfico (Parte 06): Transformando o MetaTrader 5 em um sistema RAD (II) Indicadores múltiplos em um gráfico (Parte 06): Transformando o MetaTrader 5 em um sistema RAD (II)
No artigo anterior mostrei como criar um Chart Trade usando os objetos do MetaTrader 5, transformando a plataforma em um sistema RAD, o sistema funciona muito bem, e acredito que muitos tenham pensado em criar uma biblioteca para ter cada vez mais funcionalidade no sistema proposto, e assim conseguir desenvolver um EA que seja mais intuitivo ao mesmo tempo que tenha uma interface mais agradável e simples de usar.