English Русский 中文 Español Deutsch 日本語
Gráficos na biblioteca DoEasy (Parte 87): coleção de objetos gráficos, controlamos a modificação de propriedades de objetos em todos os gráficos abertos

Gráficos na biblioteca DoEasy (Parte 87): coleção de objetos gráficos, controlamos a modificação de propriedades de objetos em todos os gráficos abertos

MetaTrader 5Exemplos | 22 dezembro 2021, 11:59
444 0
Artyom Trishkin
Artyom Trishkin

Sumário


Ideia

Já podemos controlar a mudança nas propriedades dos objetos gráficos padrão construídos na janela do gráfico, onde está sendo executado o programa criado com base nesta biblioteca. No último artigo, decidimos usar um modelo de evento, visando processar eventos de objetos gráficos no manipulador OnChartEvent(). Graças a isso, simplificamos o código (apesar de o processamento de evento estar em dois blocos de código diferentes na biblioteca) e resolvemos o problema do preenchimento incompleto das propriedades do objeto-classe durante a criação do objeto gráfico no gráfico, já falamos sobre isso no artigo anterior.

Tudo parece estar bem, mas agora há um grande mas, e é que não podemos receber eventos de objetos gráficos diretamente desde outros gráficos, uma vez que todos os eventos que ocorrem num gráfico vêm para o manipulador OnChartEvent() pertencente ao programa que está rodando no gráfico em questão. Isso significa que para determinar o evento acontecido no gráfico, sem programa, precisamos de alguma forma enviar um evento desde dado gráfico para o gráfico onde nosso programa está sendo executado.

Podemos enviar nosso evento, isto é, um evento personalizado enviado para o gráfico de nosso programa por meio da função EventChartCustom().
Esta pode gerar um evento personalizado para o gráfico especificado nela. Além disso, após enviar o identificador do evento para o gráfico especificado, a função adiciona automaticamente o valor da constante ao seu valor CHARTEVENT_CUSTOM. Após receber esse evento na biblioteca, só temos que subtrair dele esse valor agregado, para descobrir que tipo de evento aconteceu em algum outro gráfico. Para entender em qual gráfico ocorreu o evento, podemos especificar seu identificador no parâmetro long do evento lparam. Assim, como o parâmetro lparam tem um valor (por padrão para eventos de objetos gráficos, lparam e dparam não têm valores - são iguais a zero), já entendemos que este evento veio de outro gráfico - subtraímos do parâmetro id, que também é passado para o evento, o valor CHARTEVENT_CUSTOM, e obtemos o identificador do evento. Mas, com o parâmetro sparam podemos saber com qual objeto esse evento ocorreu, porque o nome do objeto é passado nele.

Assim, decidimos que ainda podemos enviar eventos desde outros gráficos para o gráfico do nosso programa. Também podemos determinar que tipo de evento é graças ao identificador de evento (id); conseguimos determinar o gráfico pelo parâmetro lparam e o nome do objeto, pelao parâmetro sparam. Mas agora precisamos descobrir como controlaremos esses eventos em outros gráficos. Afinal, o programa está sendo executado num gráfico e devemos receber eventos e enviá-los à biblioteca e ao gráfico desde outros gráficos. E nesses outros gráficos, deve funcionar um programa que nossa biblioteca conheça e possa executá-lo.

Lembre-se de que temos uma pequena classe para controlar eventos em gráficos diferentes (CChartObjectsControl), e na classe-coleção de objetos gráficos criamos listas de todos os gráficos abertos do terminal do cliente, registrados apenas nos parâmetros da classe mencionada, que também rastreia a alteração do número de objetos gráficos no gráfico controlado por um objeto desta classe. Assim, dentro desta classe, podemos criar um programa para o gráfico, que este objeto controla, e colocá-lo nele. Esse programa será um indicador - a linguagem MQL5 permite criar um indicador personalizado diretamente nos recursos do programa (de modo que ele seja uma parte integrante após a compilação), bem como gerar um indicador para cada um dos gráficos abertows no terminal e, ainda melhor, colocar o indicador criado programaticamente neste gráfico.

O indicador não terá buffers de desenho e só rastreará dois eventos dos objetos gráficos CHARTEVENT_OBJECT_CHANGE e CHARTEVENT_OBJECT_DRAG no manipulador OnChartEvent() e enviá-los-á para o gráfico do programa como um evento personalizado, que precisaremos definir e processar na biblioteca.

Antes de começar a implementar nosso plano, devemos lembrar que, em muitos arquivos de biblioteca, foram feitas alterações nos nomes de variáveis locais usadas para indicar o início de loops através das propriedades de objetos de biblioteca. O nome "beg"(eu me referia a ele como abreviação de "begin") simplesmente não parece certo... Assim, foi decidido substituí-lo em todos os arquivos pelo o nome completo (não abreviado) "begin".

Arquivos de biblioteca nos quais a variável foi renomeada:

DataTick.mqh, Symbol.mqh, Bar.mqh, PendRequest.mqh, Order.mqh, MQLSignal.mqh, IndicatorDE.mqh, DataInd.mqh, Buffer.mqh, GCnvElement.mqh, Event.mqh, ChartWnd.mqh, ChartObj.mqh, MarketBookOrd.mqh, Account.mqh and GStdGraphObj.mqh.


Aprimorando as classes da biblioteca

No arquivo \MQL5\Include\DoEasy\Data.mqh incluímos os índices das novas mensagens:

   MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT_UPPER,              // Anchor point at the upper left corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT,                    // Anchor point at the left center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT_LOWER,              // Anchor point at the lower left corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_LOWER,                   // Anchor point at the bottom center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT_LOWER,             // Anchor point at the lower right corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT,                   // Anchor point at the right center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT_UPPER,             // Anchor point at the upper right corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_UPPER,                   // Anchor point at the upper center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_CENTER,                  // Anchor point at the very center of the object

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Failed to get the list of newly added objects
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list
   
   MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR,           // Indicator for controlling and sending events created
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR,    // Failed to create the indicator for controlling and sending events
   MSG_GRAPH_OBJ_CLOSED_CHARTS,                       // Chart window closed:
   MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS,            // Objects removed together with charts:
   
  };
//+------------------------------------------------------------------+

e os textos que correspondem aos índices recém-adicionados:

   {"Точка привязки в левом верхнем углу","Anchor point at the upper left corner"},
   {"Точка привязки слева по центру","Anchor point to the left in the center"},
   {"Точка привязки в левом нижнем углу","Anchor point at the lower left corner"},
   {"Точка привязки снизу по центру","Anchor point below in the center"},
   {"Точка привязки в правом нижнем углу","Anchor point at the lower right corner"},
   {"Точка привязки справа по центру","Anchor point to the right in the center"},
   {"Точка привязки в правом верхнем углу","Anchor point at the upper right corner"},
   {"Точка привязки сверху по центру","Anchor point above in the center"},
   {"Точка привязки строго по центру объекта","Anchor point strictly in the center of the object"},

//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},
   
   {"Создан индикатор контроля и отправки событий","An indicator for monitoring and sending events has been created"},
   {"Не удалось создать индикатор контроля и отправки событий","Failed to create indicator for monitoring and sending events"},
   {"Закрыто окон графиков: ","Closed chart windows: "},
   {"С ними удалено объектов: ","Objects removed with them: "},
   
  };
//+---------------------------------------------------------------------+


O método Symbol() no arquivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh da classe do objeto gráfico abstrato padrão retorna o símbolo do objeto gráfico "Gráfico":

//--- Symbol for the Chart object 
   string            Symbol(void)                  const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL);                      }
   void              SetChartObjSymbol(const string symbol)
                       {
                        if(::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
                           this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,symbol);
                       }

Mas como todos os métodos que trabalham com o objeto gráfico "Gráfico" possuem o prefixo "ChartObj", para manter a consistência com outros métodos, renomearemos:

//--- Symbol for the Chart object 
   string            ChartObjSymbol(void)          const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL);                      }
   void              SetChartObjSymbol(const string symbol)
                       {
                        if(::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
                           this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,symbol);
                       }
//--- Return the flags indicating object visibility on timeframes

Aqui já vemos variáveis renomeadas, sobre os quais falamos desde o início:

//+------------------------------------------------------------------+
//| Compare CGStdGraphObj objects by all properties                  |
//+------------------------------------------------------------------+
bool CGStdGraphObj::IsEqual(CGStdGraphObj *compared_obj) const
  {
   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.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   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.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   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.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Display object properties in the journal                         |
//+------------------------------------------------------------------+
void CGStdGraphObj::Print(const bool full_prop=false,const bool dash=false)
  {
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG)," (",this.Header(),") =============");
   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(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   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(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   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(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_END)," (",this.Header(),") =============\n");
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   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;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   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;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   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;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }
   if(changed)
      PropertiesCopyToPrevData();
  }
//+------------------------------------------------------------------+


Indicador para envio de mensagens sobre mudanças nas propriedades dos objetos em todos os gráficos

Vamos escolher os parâmetros do indicador que rastreará os eventos dos objetos gráficos do gráfico ao qual será anexado.

Nosso indicador deve saber:

  1. o identificador do gráfico em que é iniciado (vamos chamá-lo de SourseID) e
  2. o identificador do gráfico para o qual deve enviar eventos personalizados (DestinationID).
Se tudo for claro quanto ao DestinationID, devemos especificá-lo nos parâmetros de entrada do indicador, e existem algumas nuances com o parâmetro SourseID.

Se apenas executarmos o indicador no gráfico manualmente, ou seja, se o encontrarmos no navegador e o arrastramos para o gráfico do símbolo, a função ChartID(), que retorna o identificador do gráfico atual, retornará o identificador do gráfico no qual o indicador está sendo executado. Tudo parece estar da maneira que precisamos. Mas... Se o indicador estiver localizado nos recursos do programa, isto é, se durante a compilação estiver embutido no código do programa e for inciado a partir do recurso embutido, a função ChartID() retornará o identificador do gráfico no qual o programa está sendo executado e não, uma instância do indicador. Isto é, executando o indicador programaticamente em diferentes gráficos, com a condição de que o indicador não é iniciado desde a pasta Indicators\, senão desde o recurso embutido, não sabemos o identificador do gráfico em que este indicador está rodando. Por isso, a nossa única opção é que nós também vamos transmitir o identificador do gráfico atual para as configurações, porque temos listas de identificadores de todos os gráficos abertos no terminal de cliente.

Assim, no navegador do editor na pasta \MQL5\Indicators\ criamos uma nova pasta DoEasy\


e dentro dela um novo indicador personalizado EventControl.mq5.



Ao criá-lo, especificamos dois parâmetros de entrada do tipo long com valor inicial 0:


Na próxima etapa do assistente, indicaremos a necessidade de incluir o manipulador OnChartEvent() no código do indicador, marcando a caixa de seleção apropriada:


Na próxima etapa, deixamos todos os campos e caixas de seleção vazios e clicamos no botão "Concluir":


É isso, nosso indicador foi criado.

Se o compilarmos agora, receberemos um aviso de que o indicador não possui um único buffer de desenho:


Para excluir este aviso, precisamos indicar explicitamente no código do indicador que não precisamos de buffers de desenho:

//+------------------------------------------------------------------+
//|                                                 EventControl.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0
//--- input parameters
input long     InpChartSRC = 0;
input long     InpChartDST = 0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

Na biblioteca, ao desinicializar suas classes, precisaremos remover de todos os gráficos abertos este indicador. Como podemos encontrar o indicador no gráfico por seu nome curto, precisamos definir explicitamente o nome no manipulador OnInit() do indicador - para usar este nome, encontrar o indicador e removê-lo do gráfico:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator shortname
   IndicatorSetString(INDICATOR_SHORTNAME,"EventSend_From#"+(string)InpChartSRC+"_To#"+(string)InpChartDST);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

O nome abreviado conterá:

  • nome do indicador: "EventSend",
  • identificador do gráfico desde o qual é enviada a mensagem: "_From#"+(string)InpChartSRC e
  • identificador do gráfico desde o qual é enviada a mensagem: "_To#"+(string)InpChartDST.

No manipulador OnCalculate() não fazemos nada, simplesmente retornamos o valor do número de barras no gráfico:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   return rates_total;
  }
//+------------------------------------------------------------------+

No manipulador OnChartEvent() do indicador acompanhamos dois eventos dos objetos gráficos (CHARTEVENT_OBJECT_CHANGE e CHARTEVENT_OBJECT_DRAG) e, se forem fixos, enviamos um evento personalizado para o gráfico do programa de controle:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG)
     {
      EventChartCustom(InpChartDST,(ushort)id,InpChartSRC,dparam,sparam);
     }
  }
//+------------------------------------------------------------------+

Na própria mensagem iremos indicar o gráfico, para o qual o evento é enviado, o identificador de evento (a função EventChartCustom() adicionará automaticamente o valor CHARTEVENT_CUSTOM ao valor do evento), o identificador do gráfico de onde foi enviado o evento e os outros dois valores com valores padrão - dparam será zero e sparam irá conter o nome do objeto gráfico no qual as mudanças ocorreram.

Vamos compilar o indicador e deixá-lo em nossa pasta - mais adiante iremos acessá-lo na biblioteca. Só precisaremos dele ao compilar a biblioteca, que o colocará em seus recursos e a seguir acessará a instância do indicador armazenada nos recursos.
Ao distribuir o programa compilado, não haverá necessidade de transferir o código-fonte ou o arquivo compilado do indicador, uma vez que o código do indicador - durante a compilação do programa - será embutido no seu código.

O arquivo do indicador pode ser encontrado nos arquivos anexados à biblioteca no final do artigo.

Agora precisamos criar um recurso que armazenará o código do indicador executável.

No arquivo \MQL5\Include\DoEasy\Defines.mqh definimos uma substituição de macro para indicar o caminho para o arquivo executável do indicador:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "DataSND.mqh"
#include "DataIMG.mqh"
#include "Data.mqh"
#ifdef __MQL4__
#include "ToMQL4.mqh"
#endif 
//+------------------------------------------------------------------+
//| Resources                                                        |
//+------------------------------------------------------------------+
#define PATH_TO_EVENT_CTRL_IND         "Indicators\\DoEasy\\EventControl.ex5"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+

Com esta substituição de macro, obteremos o caminho para o arquivo do indicador compilado nos recursos da biblioteca.

Num futuro próximo, a este arquivo adicionaremos a lista de possíveis eventos de objetos gráficos:

//+------------------------------------------------------------------+
//| Data for handling graphical elements                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of possible graphical object events                         |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_EVENT
  {
   GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,// No event
   GRAPH_OBJ_EVENT_CREATE,                            // "Creating a new graphical object" event
   GRAPH_OBJ_EVENT_CHANGE,                            // "Changing graphical object properties" event
   GRAPH_OBJ_EVENT_MOVE,                              // "Moving graphical object" event
   GRAPH_OBJ_EVENT_RENAME,                            // "Renaming graphical object" event
   GRAPH_OBJ_EVENT_DELETE,                            // "Removing graphical object" event
  };
#define GRAPH_OBJ_EVENTS_NEXT_CODE  (GRAPH_OBJ_EVENT_DELETE+1)  // The code of the next event after the last graphical object event code
//+------------------------------------------------------------------+
//| List of anchoring methods                                        |
//| (horizontal and vertical text alignment)                         |
//+------------------------------------------------------------------+

Esta lista contém uma lista preliminar de eventos que enviaremos ao programa após criarmos uma funcionalidade unificada para controlar eventos de objetos gráficos padrão, dos quais trataremos em breve.


Processamento de sinais do indicador sobre eventos de mudanças nas propriedades dos objetos

Ao abrir novas janelas de gráficos, a biblioteca cria automaticamente instâncias de objetos de classe de controle para os objetos de gráfico CChartObjectsControl e os salva na lista de objetos de controle de gráfico na classe-coleção de objetos gráficos.

As propriedades do objeto de controle de objetos do gráfico armazena o identificador do gráfico, que ele controla. Assim, no mesmo objeto, ao criá-lo para a janela do gráfico, podemos criar um indicador para controlar os eventos dos objetos gráficos. Assim, poderemos colocar nosso novo indicador para cada gráfico recém-aberto (ou nos existentes), indicador esse que irá monitorar os eventos dos objetos gráficos e enviá-los para o gráfico do programa de controle.

No arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh na seção privada da classe CChartObjectsControl declaramos três novas variáveis:

//+------------------------------------------------------------------+
//| Chart object management class                                    |
//+------------------------------------------------------------------+
class CChartObjectsControl : public CObject
  {
private:
   CArrayObj         m_list_new_graph_obj;      // List of added graphical objects
   ENUM_TIMEFRAMES   m_chart_timeframe;         // Chart timeframe
   long              m_chart_id;                // Chart ID
   long              m_chart_id_main;           // Control program chart ID
   string            m_chart_symbol;            // Chart symbol
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_last_objects;            // Number of graphical objects during the previous check
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
   int               m_handle_ind;              // Event controller indicator handle
   string            m_name_ind;                // Short name of the event controller indicator
   
//--- Return the name of the last graphical object added to the chart
   string            LastAddedGraphObjName(void);
//--- Set the permission to track mouse events and graphical objects
   void              SetMouseEvent(void);
   
public:

Podemos entender claramente sua finalidade, a partir da descrição das variáveis nos comentários.

Na seção pública da classe iremos declarar dois novos métodos para criar um indicador e adicioná-lo ao gráfico:

public:
//--- Return the variable values
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_chart_timeframe;    }
   long              ChartID(void)                             const { return this.m_chart_id;           }
   string            Symbol(void)                              const { return this.m_chart_symbol;       }
   bool              IsEvent(void)                             const { return this.m_is_graph_obj_event; }
   int               TotalObjects(void)                        const { return this.m_total_objects;      }
   int               Delta(void)                               const { return this.m_delta_graph_obj;    }
//--- Create a new standard graphical object
   CGStdGraphObj    *CreateNewGraphObj(const ENUM_OBJECT obj_type,const long chart_id, const string name);
//--- Return the list of newly added objects
   CArrayObj        *GetListNewAddedObj(void)                        { return &this.m_list_new_graph_obj;}
//--- Create the event control indicator
   bool              CreateEventControlInd(const long chart_id_main);
//--- Add the event control indicator to the chart
   bool              AddEventControlInd(void);
//--- Check the chart objects
   void              Refresh(void);
//--- Constructors

Nos construtores da classe definimos seus valores padrão para as novas variáveis e adicionamos um destruidor de classe, em que é excluído o indicador no gráfico e o indicador do indicador é excluído e a parte de cálculo do indicador é liberada:

//--- Check the chart objects
   void              Refresh(void);
//--- Constructors
                     CChartObjectsControl(void)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=::ChartID();
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_chart_id_main=::ChartID();
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.m_name_ind="";
                        this.m_handle_ind=INVALID_HANDLE;
                        this.SetMouseEvent();
                       }
                     CChartObjectsControl(const long chart_id)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=chart_id;
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_chart_id_main=::ChartID();
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.m_name_ind="";
                        this.m_handle_ind=INVALID_HANDLE;
                        this.SetMouseEvent();
                       }
//--- Destructor
                     ~CChartObjectsControl()
                       {
                        ::ChartIndicatorDelete(this.ChartID(),0,this.m_name_ind);
                        ::IndicatorRelease(this.m_handle_ind);
                       }
                     
//--- Compare CChartObjectsControl objects by a chart ID (for sorting the list by an object property)
   virtual int       Compare(const CObject *node,const int mode=0) const
                       {
                        const CChartObjectsControl *obj_compared=node;
                        return(this.ChartID()>obj_compared.ChartID() ? 1 : this.ChartID()<obj_compared.ChartID() ? -1 : 0);
                       }

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

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


Fora do corpo da classe, vamos escrever a implementação dos métodos declarados.

Método que cria o indicador de controle de eventos:

//+------------------------------------------------------------------+
//| CChartObjectsControl: Create the event control indicator         |
//+------------------------------------------------------------------+
bool CChartObjectsControl::CreateEventControlInd(const long chart_id_main)
  {
   this.m_chart_id_main=chart_id_main;
   string name="::"+PATH_TO_EVENT_CTRL_IND;
   ::ResetLastError();
   this.m_handle_ind=::iCustom(this.Symbol(),this.Timeframe(),name,this.ChartID(),this.m_chart_id_main);
   if(this.m_handle_ind==INVALID_HANDLE)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR);
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   this.m_name_ind="EventSend_From#"+(string)this.ChartID()+"_To#"+(string)this.m_chart_id_main;
   Print
     (
      DFUN,this.Symbol()," ",TimeframeDescription(this.Timeframe()),": ",
      CMessage::Text(MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR)," \"",this.m_name_ind,"\""
     );
   return true;
  }
//+------------------------------------------------------------------+

Aqui: definimos o identificador do programa de controle, passado para os parâmetros do método, especificamos o caminho nos recursos para nosso indicador e criamos o identificador do indicador, construído com base no símbolo e no período gráfico controlado por esta classe.
Nos parâmetros de entrada do indicador, passamos o identificador do gráfico, que é controlado pelo objeto da classe e o identificador do programa de controle
.
Se o indicador não puder ser criado, imprimimos isso no log do terminal, indicando o número do erro, e retornamos false
.
Se o identificador do indicador for criado (especificamos um nome curto com o qual o indicador é removido do gráfico no destruidor de classe), exibimos uma mensagem no log sobre a criação do indicador no gráfico e retornamos true.

Observe que ao definir o caminho para o indicador nos recursos da biblioteca, indicamos "::" antes da linha do caminho.
Já ao criar um recurso, indicaremos o sinal "\\".

Método que adiciona um indicador de controle de evento ao gráfico:

//+------------------------------------------------------------------+
//|CChartObjectsControl: Add the event control indicator to the chart|
//+------------------------------------------------------------------+
bool CChartObjectsControl::AddEventControlInd(void)
  {
   if(this.m_handle_ind==INVALID_HANDLE)
      return false;
   return ::ChartIndicatorAdd(this.ChartID(),0,this.m_handle_ind);
  }
//+------------------------------------------------------------------+

Aqui, verificamos o identificador do indicador e, se for inválido, retornamos false. Caso contrário, devolvemos o resultado da função de adição de indicador ao gráfico.

Antes de definir a classe da coleção de objetos gráficos especificamos o caminho para o recurso onde o indicador está armazenado:

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
#resource "\\"+PATH_TO_EVENT_CTRL_IND;          // Indicator for controlling graphical object events packed into the program resources
class CGraphElementsCollection : public CBaseObj
  {

Esta string cria um recurso na biblioteca onde se encontra o arquivo executável compilado do indicador para controle de eventos do gráfico.

Na seção privada da classe iremos declarar quatro novos métodos:

class CGraphElementsCollection : public CBaseObj
  {
private:
   CArrayObj         m_list_charts_control;     // List of chart management objects
   CListObj          m_list_all_canv_elm_obj;   // List of all graphical elements on canvas
   CListObj          m_list_all_graph_obj;      // List of all graphical objects
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
   
//--- Return the flag indicating the graphical element class object presence in the collection list of graphical elements
   bool              IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj);
//--- Return the flag indicating the presence of the graphical object class in the graphical object collection list
   bool              IsPresentGraphObjInList(const long chart_id,const string name);
//--- Return the flag indicating the presence of a graphical object on a chart by name
   bool              IsPresentGraphObjOnChart(const long chart_id,const string name);
//--- Return the pointer to the object of managing objects of the specified chart
   CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id);
//--- Create a new object of managing graphical objects of a specified chart and add it to the list
   CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id);
//--- Update the list of graphical objects by chart ID
   CChartObjectsControl *RefreshByChartID(const long chart_id);
//--- Check if the chart window is present
   bool              IsPresentChartWindow(const long chart_id);
//--- Handle removing the chart window
   void              RefreshForExtraObjects(void);
//--- Return the first free ID of the graphical (1) object and (2) element on canvas
   long              GetFreeGraphObjID(void);
   long              GetFreeCanvElmID(void);
//--- Add a graphical object to the collection
   bool              AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control);
//--- Find an object present in the collection but not on a chart
   CGStdGraphObj    *FindMissingObj(const long chart_id);
//--- Find the graphical object present on a chart but not in the collection
   string            FindExtraObj(const long chart_id);
//--- Remove the graphical object from the graphical object collection list: (1) specified object, (2) by chart ID
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   void              DeleteGraphObjectsFromList(const long chart_id);
//--- Remove the object of managing charts from the list
   bool              DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj);
  
public:

Para remover objetos de controle de gráfico desnecessários da lista de classe e objetos que descrevem objetos gráficos removidos da lista-coleção, precisamos saber se tal gráfico foi excluído. O método para isso será IsPresentChartWindow(). No método RefreshForExtraObjects() serão processadas as ocasiões em que houver objetos desnecessários nas listas da classe-coleção, enquanto os métodos DeleteGraphObjectsFromList() e DeleteGraphObjCtrlObjFromList() removerão diretamente os objetos especificados das listas da classe-coleção de objetos gráficos.

No método, que cria um novo objeto para contole de objetos gráficos do gráfico especificado, adicionamos o código para criar o indicador e adicioná-lo ao gráfico:

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

Aqui verificamos que o objeto de controle não foi criado para o gráfico atual, em que iniciado o programa, e que o indicador foi criado com sucesso para o gráfico controlado pelo objeto de controle do gráfico e, se este for o caso, adicionamos o indicador criado ao gráfico.

No método que atualiza a lista de todos os objetos gráficos, em primeiro lugar processamos as situações de fechamento de gráficos no terminal - para excluir os objetos de controle do gráfico correspondentes aos gráficos excluídos e os objetos das classes que descrevem os objetos gráficos excluídos junto com os gráficos, objetos esses que se tornaram redundantes na lista-coleção após o gráfico ser fechado:

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


Método que verifica a presença da janela do gráfico:

//+------------------------------------------------------------------+
//| Check if the chart window is present                             |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentChartWindow(const long chart_id)
  {
   long chart=0;
   int i=0;
//--- In the loop by all open charts in the terminal (no more than 100)
   while(i<CHARTS_MAX)
     {
      //--- Get the chart ID
      chart=::ChartNext(chart);
      if(chart<0)
         break;
      if(chart==chart_id)
         return true;
      //--- Increase the loop index
      i++;
     }
   return false;
  }
//+------------------------------------------------------------------+

Aqui: num loop percorrendo todos os gráficos abertos no terminal, obtemos o identificador do próximo gráfico e o comparamos com o passado para o método.
Se os identificadores coincidirem é porque o gráfico existe, portanto, retornamos true
.
No final do loop, retornamos false , indicando que não existe gráfico com o identificador especificado.

Método que processa a exclusão de janela do gráfico:

//+------------------------------------------------------------------+
//| Handle removing the chart window                                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::RefreshForExtraObjects(void)
  {
   for(int i=this.m_list_charts_control.Total()-1;i>WRONG_VALUE;i--)
     {
      CChartObjectsControl *obj_ctrl=this.m_list_charts_control.At(i);
      if(obj_ctrl==NULL)
         continue;
      if(!this.IsPresentChartWindow(obj_ctrl.ChartID()))
        {
         long chart_id=obj_ctrl.ChartID();
         int total_ctrl=m_list_charts_control.Total();
         this.DeleteGraphObjCtrlObjFromList(obj_ctrl);
         int total_obj=m_list_all_graph_obj.Total();
         this.DeleteGraphObjectsFromList(chart_id);
         int del_ctrl=total_ctrl-m_list_charts_control.Total();
         int del_obj=total_obj-m_list_all_graph_obj.Total();
         Print
           (
            DFUN,CMessage::Text(MSG_GRAPH_OBJ_CLOSED_CHARTS),(string)del_ctrl,". ",
            CMessage::Text(MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS),(string)del_obj
           );
        }
     }
  }
//+------------------------------------------------------------------+

Aqui: num loop pela lista de objetos de controle de gráficos recebemos o próximo objeto e, se não houver nenhum gráfico correspondente ao objeto no terminal, então isso significa que o gráfico foi fechado.
Do mesmo modo, obtemos o identificador do gráfico fechado e o número total de gráficos abertos anteriormente e
removemos da lista o objeto de controle correspondente ao gráfico já fechado no terminal
.
Obtemos o número total de objetos gráficos presentes no terminal antes de excluir o gráfico e removemos da lista-coleção os objetos das classes de objetos gráficos que estavam no gráfico agora fechado.
Em seguida, calculamos o número de gráficos fechados e o número de objetos gráficos excluídos junto com os gráficos e imprimir uma mensagem no log sobre o número de gráficos fechados e objetos gráficos neles.
Posteriormente, em vez desta mensagem, iremos criar um evento e enviá-lo para o gráfico do programa de controle.

Método que remove o objeto gráfico com base no identificador de gráfico a partir da lista-coleção de objetos gráficos:

//+------------------------------------------------------------------+
//| Remove a graphical object by a chart ID                          |
//| from the graphical object collection list                        |
//+------------------------------------------------------------------+
void CGraphElementsCollection::DeleteGraphObjectsFromList(const long chart_id)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   if(list==NULL)
      return;
   for(int i=list.Total();i>WRONG_VALUE;i--)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      this.DeleteGraphObjFromList(obj);
     }
  }
//+------------------------------------------------------------------+

Aqui: obtemos a lista de objetos gráficos com o identificador gráfico especificado. Num loop ao longo da lista resultante obtemos o seguinte objeto e o removemos da lista-coleção.

Método que remove o objeto de controle de gráficos da lista:

//+------------------------------------------------------------------+
//| Remove the object of managing charts from the list               |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj)
  {
   this.m_list_charts_control.Sort();
   int index=this.m_list_charts_control.Search(obj);
   return(index==WRONG_VALUE ? false : m_list_charts_control.Delete(index));
  }
//+------------------------------------------------------------------+

Aqui: definimos o sinalizador de lista classificada para a lista de objetos de controle de gráficos, usando o método Search() encontramos o índice do objeto especificado na lista e, se o objeto não estiver na lista, retornamos false, caso contrário, retornamos o resultado do método Delete().

No manipulador de eventos da classe-coleção de objetos gráficos precisamos substituir trabalho com o gráfico atual pelo trabalho com o gráfico por identificador. Para fazer isso, vamos controlar o valor do parâmetro lparam no evento. Esse valor será zero ao obtermos o evento desde o gráfico atual e será igual ao identificador do gráfico ao obtermos o evento personalizado desde o indicador.

Para obter o identificador de evento de objeto gráfico desde um evento personalizado, precisamos subtrair o valor CHARTEVENT_CUSTOM do valor do id recebido e junto com a verificação de id temos que verificar o valor calculado do identificador do evento na variável idx.

Em seguida, o facto de lparam não ser zero indica que o evento não é recebido desde o gráfico atual, caso contrário, desde o atual.
Resta substituir todas as correspondências de ::ChartID() no código pelo chart_id obtido:

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

Essas são todas as modificações de que precisávamos hoje. Vamos testar o resultado.


Teste

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

Não faremos nenhuma mudança no Expert Advisor, simplesmente o compilaremos e o executaremos no gráfico, tendo previamente aberto mais dois gráficos no terminal. Ao criar objetos em gráficos adicionais e alterá-los, todos os eventos produzidos com objetos gráficos serão registrados pela biblioteca e as mensagens serão exibidas no log. Ao abrir outro gráfico, também serão criados um objeto de controle de gráfico e um indicador que irá registrar os eventos de alteração de objetos e enviá-los para a biblioteca. A eliminação de gráficos adicionais será relatado no log:



O que vem agora?

No próximo artigo, começaremos a reunir todo o processamento de eventos de objetos gráficos criado até agora de forma a enviar para o gráfico do programa de controle todos os eventos que ocorrem com objetos gráficos em gráficos abertos (agora nossos eventos registrados são simplesmente exibidos no log, sem que o programa em execução sob o controle da biblioteca saiba sobre eles).

Todos os arquivos da versão atual da biblioteca e o arquivo do EA de teste 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 83): classe abstrata de objetos gráficos padrão
Gráficos na biblioteca DoEasy (Parte 84): classes herdeiras do objeto gráfico abstrato padrão
Gráficos na biblioteca DoEasy (Parte 85): coleção de objetos gráficos, adicionamos recém-criados
Gráficos na biblioteca DoEasy (Parte 86): coleção de objetos gráficos, controlamos a modificação de propriedades

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

Arquivos anexados |
MQL5.zip (4155.98 KB)
Gráficos na biblioteca DoEasy (Parte 88): coleção de objetos gráficos, matriz dinâmica bidimensional para armazenar propriedades de objetos que mudam dinamicamente Gráficos na biblioteca DoEasy (Parte 88): coleção de objetos gráficos, matriz dinâmica bidimensional para armazenar propriedades de objetos que mudam dinamicamente
Neste artigo, criaremos uma classe de matriz multidimensional dinâmica com a capacidade de alterar a quantidade de dados em qualquer dimensão. Com base na classe criada, criaremos uma matriz dinâmica bidimensional para armazenar algumas propriedades alteradas dinamicamente de objetos gráficos.
Desenvolvendo um EA de negociação do zero Desenvolvendo um EA de negociação do zero
Entenda como se dá o desenvolvimento de um EA para negociação programando o mínimo possível.
Trabalhando com o tempo (Parte 1): princípios básicos Trabalhando com o tempo (Parte 1): princípios básicos
As funções e o código discutidos no artigo o ajudarão a entender melhor os princípios de processamento de tempo, de mudança de horário da corretora e de horário de verão ou de inverno. O uso adequado do tempo é um aspecto muito importante do trading. Este nos permite saber, por exemplo, se a Bolsa de Londres ou Nova Iorque já abriu ou ainda não ou a que horas começa/termina o pregão no mercado de moedas.
Gráficos na biblioteca DoEasy (Parte 86): coleção de objetos gráficos, controlamos a modificação de propriedades Gráficos na biblioteca DoEasy (Parte 86): coleção de objetos gráficos, controlamos a modificação de propriedades
Este artigo analisa o rastreamento de modificações nos valores de propriedades, remoção e renomeação de objetos gráficos dentro da biblioteca.