English Русский 中文 Español Deutsch 日本語
Gráficos na biblioteca DoEasy (Parte 81): integrando gráficos nos objetos da biblioteca

Gráficos na biblioteca DoEasy (Parte 81): integrando gráficos nos objetos da biblioteca

MetaTrader 5Exemplos | 20 setembro 2021, 14:26
1 037 0
Artyom Trishkin
Artyom Trishkin

Sumário

Ideia

Desta vez, não faremos novas construções gráficas e melhorias. Em vez disso, começaremos a integrar nos objetos da biblioteca as classes já criadas destinadas a elementos gráficos. Precisamos de tudo isso para poder continuar a desenvolver e aprimorar os elementos gráficos. Além disso, no futuro, precisaremos mover objetos dentro do gráfico para verificar que as construções de objetos gráficos compostos estejam corretas. Para isso, necessitamos pensar na integração desses objetos nos objetos da biblioteca agora, com o propósito de criar uma classe de gerenciamento de objetos gráficos e da classe-coleção destes últimos.

A classe de gerenciamento de objetos gráficos conterá métodos para criar formas e objetos gráficos que retornarão um ponteiro para o objeto gráfico criado que trabalharemos posteriormente. Precisaremos de uma classe-coleção de elementos gráficos no futuro, para criar listas contendo todos os objetos gráficos construídos, relacionados aos diferentes objetos da biblioteca, assim teremos chance de criar métodos para sua interação tanto mútua como com o usuário do programa.

Hoje integraremos objetos gráficos apenas no objeto-barra da biblioteca. Precisaremos de algum tempo para depurar a abordagem apresentada. E já com base no mecanismo criado e depurado, iremos anexá-lo ao restante dos objetos da biblioteca. Depois disso, daremos continuação ao desenvolvimento posterior dos objetos gráficos da biblioteca.

A ideia de hoje será a seguinte:

  • temos um objeto de elemento gráfico que contém vários métodos;
  • temos um objeto-barra que ainda não sabe nada sobre objetos gráficos;
  • precisamos criar uma classe de gerenciamento de objetos gráficos que permita sua criação e retorne um ponteiro para o objeto criado;
  • e necessitamos fazer dessa classe de gerenciamento um dos componentes do objeto-barra.

Essas quatro etapas nos permitirão obter qualquer objeto de biblioteca criado anteriormente (hoje, o objeto-barra servirá como cobaia) e usar o objeto de gerenciamento de objeto gráfico integrado para gerar o objeto desejado e obter um ponteiro para ele, isso tornará possível trabalhar posteriormente com o objeto gráfico usual da biblioteca que discutimos em artigos anteriores.

Depois de depurar os mecanismos desenvolvidos nesta ideia, para todos os objetos da biblioteca, escreveremos tudo o que faremos com o objeto-barra para integrá-lo com o componente gráfico da biblioteca. Assim, todos os objetos ganharão uma nova vida “visual”, e teremos uma nova ferramenta de interação interativa com a biblioteca.


Aprimorando as classes da biblioteca

Todo objeto gráfico que um objeto da biblioteca cria deve saber qual objeto o está gerando. Claro, se temos apenas um objeto (hoje é o objeto-barra), este não precisará saber isso. Mas se cada objeto da biblioteca for capaz de criar objetos gráficos próprios, estes últimos deverão saber qual objeto da biblioteca os gerou, para depois acessarem e receberem dados a partir dele. Isso é útil para exibição desses dados num objeto gráfico ou para relacionamentos mais complexos entre diferentes objetos.

Claro, não seremos capazes de fazer tudo isso hoje. Mas começaremos com o mais simples, precisamos pelo menos saber a descrição de tipo do objeto a partir do qual é criado o objeto gráfico. Para isso, inicialmente, usaremos o identificador da coleção de objetos (para cada objeto é registrado um identificador de coleção que corresponde ao seu tipo). Com esse identificador podemos entender a que tipo de objetos pertence o objeto da biblioteca a partir do qual é criado o objeto gráfico. Certamente, isso não é suficiente para especificar de maneira precisa um objeto em particular, mas é importante começar com um objeto simples e, depois, avançar para coisas mais complexas.

Além disso, precisaremos fazer os métodos para exibir a descrição de objetos do mesmo tipo para todos os objetos da biblioteca criados anteriormente. Os métodos Print() e PrintShort() exibem uma descrição completa e curta das propriedades do objeto. Vamos tornar esses métodos virtuais e declará-los na classe pai de todos os objetos da biblioteca CBaseObj. Para que a virtualização funcione, precisamos fazer com que os argumentos desses métodos sejam exatamente os mesmos em todas as classes. No momento, temos diferentes parâmetros para esses métodos em diferentes classes. É necessário dar-lhes a mesma aparência e corrigir as chamadas de métodos de acordo com os parâmetros alterados nos argumentos dos métodos.

Na classe CBaseObj do arquivo \MQL5\Include\DoEasy\Objects\BaseObj.mqh declaramos esses dois métodos virtuais com os parâmetros necessários:

//--- Return an object type
   virtual int       Type(void)                                const { return this.m_type;                     }
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false)  { return;                        }
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false){ return;                        }
   
//--- Constructor

Os parâmetros nos argumentos dos métodos já foram selecionados para que possam ser usados em todos os métodos que escrevemos anteriormente nas classes herdeiras.

Por exemplo, na classe COrder - a classe base de todo o sistema de ordens da biblioteca - já foram feitas as seguintes alterações:

//--- Return order/position direction
   string            DirectionDescription(void) const;
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false);
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);
//---
  };
//+------------------------------------------------------------------+

Aqui adicionamos mais um argumento no método Print() e declaramos o método PrintShort().

Na implementação do método fora do corpo da classe, também foi adicionado um argumento adicional do método:

//+------------------------------------------------------------------+
//| Send order properties to the journal                             |
//+------------------------------------------------------------------+
void COrder::Print(const bool full_prop=false,const bool dash=false)
  {
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG),": \"",this.StatusDescription(),"\" =============");
   int beg=0, end=ORDER_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_INTEGER prop=(ENUM_ORDER_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=ORDER_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_DOUBLE prop=(ENUM_ORDER_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=ORDER_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_STRING prop=(ENUM_ORDER_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",CMessage::Text(MSG_LIB_PARAMS_LIST_END),": \"",this.StatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

Por exemplo, como foram modificadas as chamadas para métodos com parâmetros adicionados aos argumentos:

//+------------------------------------------------------------------+
//| Display complete collection description to the journal           |
//+------------------------------------------------------------------+
void CMBookSeriesCollection::Print(const bool full_prop=false,const bool dash=false)
  {
   ::Print(CMessage::Text(MSG_MB_COLLECTION_TEXT_MBCOLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CMBookSeries *bookseries=this.m_list.At(i);
      if(bookseries==NULL)
         continue;
      bookseries.Print(false,true);
     }
  }
//+------------------------------------------------------------------+
//| Display the short collection description in the journal          |
//+------------------------------------------------------------------+
void CMBookSeriesCollection::PrintShort(const bool dash=false,const bool symbol=false)
  {
   ::Print(CMessage::Text(MSG_MB_COLLECTION_TEXT_MBCOLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CMBookSeries *bookseries=this.m_list.At(i);
      if(bookseries==NULL)
         continue;
      bookseries.PrintShort(true);
     }
  }
//+------------------------------------------------------------------+

Anteriormente, apenas havia um parâmetro e, além disso, o método era chamado como bookseries.Print(true). Agora, ao método Print() da classe CMBookSeries foi adicionado mais um parâmetro antes do que precisamos, por isso primeiro passamos false para o parâmetro adicionado, e já depois passamos true para o necessário - para o que estava anteriormente quando o método era chamado.

Mudanças semelhantes afetaram quase todas as classes previamente escritas de objetos de biblioteca e já foram feitas em todas as classes que contêm esses métodos e que são herdadas do objeto base de todos os objetos de biblioteca: 

BookSeriesCollection.mqh, ChartObjCollection.mqh, MQLSignalsCollection.mqh, TickSeriesCollection.mqh, TimeSeriesCollection.mqh.

Account.mqh, MarketBookOrd.mqh, MarketBookSnapshot.mqh, MBookSeries.mqh, ChartObj.mqh, ChartWnd.mqh, MQLSignal.mqh, Order.mqh.

Buffer.mqh, BufferArrow.mqh, BufferBars.mqh, BufferCalculate.mqh, BufferCandles.mqh, BufferFilling.mqh, BufferHistogram.mqh, BufferHistogram2.mqh, BufferLine.mqh, BufferSection.mqh, BufferZigZag.mqh, DataInd.mqh, IndicatorDE.mqh.

PendReqClose.mqh, PendReqModify.mqh, PendReqOpen.mqh, PendReqPlace.mqh, PendReqRemove.mqh, PendReqSLTP.mqh, PendRequest.mqh.

Bar.mqh, SeriesDE.mqh, TimeSeriesDE.mqh, DataTick.mqh, TickSeries.mqh.

Symbol.mqh, SymbolBonds.mqh, SymbolCFD.mqh, SymbolCollateral.mqh, SymbolCommodity.mqh, SymbolCommon.mqh, SymbolCrypto.mqh, SymbolCustom.mqh, SymbolExchange.mqh, SymbolFutures.mqh, SymbolFX.mqh, SymbolFXExotic.mqh, SymbolFXMajor.mqh, SymbolFXMinor.mqh, SymbolFXRub.mqh, SymbolIndex.mqh, SymbolIndicative.mqh, SymbolMetall.mqh, SymbolOption.mqh, SymbolStocks.mqh

BaseObj.mqh.

Em algumas classes da biblioteca, a exibição de mensagens por meio da função Print() padrão foi substituída pela exibição de mensagens usando o método ToLog() da classe CMessage como, por exemplo, neste método da classe de coleção de eventos:

//+------------------------------------------------------------------+
//| Select only market pending orders from the list                  |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListMarketPendings(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_MARKET_ID)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_ERROR_NOT_MARKET_LIST);
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

Anteriormente, esta linha era usada para exibir a mensagem:

Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_NOT_MARKET_LIST));

Lista de arquivos em que foram feitas essas alterações:

EventsCollection.mqh, HistoryCollection.mqh, TimeSeriesCollection.mqh.

Todas as mudanças nessas classes podem ser encontradas nos arquivos anexados ao artigo.

Se já houver um objeto-forma criado no gráfico, ele pode ser oculto ou mostrado após definir sinalizadores de exibição nos períodos gráficos especificados. Aproveitamos esta oportunidade para "mover" um objeto para a frente - em cima de todos os outros - usando o método BringToTop().
Mas não temos métodos "falantes" para mostrar/ocultar objetos gráficos.
Para isso, vamos criar dois métodos virtuais na classe do elemento gráfico CGCnvElement no arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh:

//--- Set the object above all
   void              BringToTop(void)                          { CGBaseObj::SetVisible(false); CGBaseObj::SetVisible(true);            }
//--- (1) Show and (2) hide the element
   virtual void      Show(void)                                { CGBaseObj::SetVisible(true);                                          }
   virtual void      Hide(void)                                { CGBaseObj::SetVisible(false);                                         }

Os métodos simplesmente definem os sinalizadores correspondentes para exibir o objeto em todos os períodos gráficos no objeto base dos objetos gráficos da biblioteca.

A classe do objeto-forma CForm é herdeira do objeto-elemento gráfico, já o objeto-forma pode ser composto, isto é, pode consistir em vários objetos-elementos gráficos. Por isso, para ela é necessário registrar sua própria implementação desses métodos.
Abrimos o arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh e na seção pública declaramos dois métodos virtuais:

//+------------------------------------------------------------------+
//| Visual design methods                                            |
//+------------------------------------------------------------------+
//--- (1) Show and (2) hide the form
   virtual void      Show(void);
   virtual void      Hide(void);

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+

Fora do corpo da classe, vamos escrever sua implementação:

//+------------------------------------------------------------------+
//| Show the form                                                    |
//+------------------------------------------------------------------+
void CForm::Show(void)
  {
//--- If the object has a shadow, display it
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Show();
//--- Display the main form
   CGCnvElement::Show();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next graphical element
      CGCnvElement *elment=this.m_list_elements.At(i);
      if(elment==NULL)
         continue;
      //--- and display it
      elment.Show();
     }
//--- Update the form
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+
//| Hide the form                                                    |
//+------------------------------------------------------------------+
void CForm::Hide(void)
  {
//--- If the object has a shadow, hide it
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Hide();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next graphical element
      CGCnvElement *elment=this.m_list_elements.At(i);
      if(elment==NULL)
         continue;
      //--- and hide it
      elment.Hide();
     }
//--- Hide the main form and update the object
   CGCnvElement::Hide();
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

Ambos os métodos são comentados em detalhes na listagem de código. Resumindo, ao ocultar objetos não há muita diferença na ordem de ocultá-los, mas ao exibi-los é necessário restaurar toda a sequência da disposição de todos os objetos ancorados na forma principal. Por isso, a exibição ocorre em camadas, isto é, primeiro, é mostrado o objeto mais inferior - a sombra da forma -, depois a forma principal sobre a sombra, e só depois disso todos os elementos gráficos anexados à forma principal. Nesta implementação, sua ordem de exibição corresponde à ordem em que são adicionados à lista de objetos ancorados.
Verificaremos esse algoritmo ao criar objetos-formas complexos (compostos).

Agora podemos começar a integrar objetos gráficos em objetos da biblioteca.

Classe de gerenciamento de objetos gráficos

Como cada objeto da biblioteca pode ser dotado da capacidade de criar objetos gráficos próprios?

A maioria dos nossos objetos da biblioteca é herdada do objeto base de todos os objetos da biblioteca CBaseObj. Portanto, se a este objeto adicionarmos uma instância da classe que criará todos os objetos gráficos possíveis (disponíveis e planejados para desenvolvimento posterior) e se fornecermos acesso ao ponteiro para o objeto criado, todos os seus herdeiros também conseguirão trabalhar com objetos gráficos.

Como teremos um grande número de objetos gráficos diferentes, precisaremos de uma classe que "conheça", crie e gerencie cada um deles. Vamos chamar essa classe de classe de gerenciamento de objetos gráficos.

Na pasta \MQL5\Include\DoEasy\Objects\Graph\ criamos o novo arquivo GraphElmControl.mqh da classe CGraphElmControl. A classe deve ser herdada da classe base para construir a biblioteca padrão MQL5 CObject. À listagem da classe devem ser anexados três arquivos: o arquivo da classe de uma matriz dinâmica de ponteiros para instâncias da classe CObject e seus herdeiros, o arquivo de funções de serviço e o arquivo da classe do objeto-forma:

//+------------------------------------------------------------------+
//|                                              GraphElmControl.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 <Arrays\ArrayObj.mqh>
#include "..\..\Services\DELib.mqh"
#include "Form.mqh"
//+------------------------------------------------------------------+
//| Class for managing graphical elements                            |
//+------------------------------------------------------------------+
class CGraphElmControl : public CObject
  {
private:
   int               m_type_node;                     // Type of the object the graphics is constructed for
public:
//--- Return itself
   CGraphElmControl *GetObject(void)                  { return &this;               }
//--- Set a type of the object the graphics is constructed for
   void              SetTypeNode(const int type_node) { this.m_type_node=type_node; }
   
//--- Create a form object
   CForm            *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
   CForm            *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
   CForm            *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h);

//--- Constructors
                     CGraphElmControl(){;}
                     CGraphElmControl(int type_node);
  };
//+------------------------------------------------------------------+

A variável m_type_node armazenará o tipo de objeto que contém o objeto desta classe. Ao criar um novo objeto (hoje, o objeto-barra), em seu construtor será chamado o método SetTypeNode() ao qual passaremos o tipo objeto-barra registrado em sua variável m_type (para uma barra é o identificador da coleção de objetos-barras). Desta forma, o gerenciador de objetos gráficos saberá qual classe está construindo seus objetos. Por enquanto, usaremos apenas o identificador da coleção. No futuro, vamos pensar em como transferir um ponteiro para o objeto a partir do qual é construído o gráfico.

Vejamos os métodos da classe.

Dentro do construtor de classe paramétrica, escrevemos na variável m_type_node o tipo do objeto passado nos argumentos do método:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGraphElmControl::CGraphElmControl(int type_node)
  {
   this.m_type_node=m_type_node;
  }
//+------------------------------------------------------------------+


Método que cria um objeto-forma no gráfico especificado na subjanela especificada:

//+-----------------------------------------------------------------------+
//| Create the form object on a specified chart in a specified subwindow  |
//+-----------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
   CForm *form=new CForm(chart_id,wnd,name,x,y,w,h);
   if(form==NULL)
      return NULL;
   form.SetID(form_id);
   form.SetNumber(0);
   return form;
  }
//+------------------------------------------------------------------+

Ao método são transferidos o identificador único do objeto-forma criado, o identificador do gráfico, o número da subjanela do gráfico, o nome do objeto-forma e as coordenadas X e Y, largura e altura necessárias para criar a forma.
Depois, criamos o novo objeto-forma com os parâmetros passados ao método. Se sua criação for bem-sucedida, definimos para o objeto o identificador da forma, o número na lista de objetos aqui é zero, porque este objeto é o objeto-forma principal e não contém nenhum outro objeto-forma ancorado). Retornamos um ponteiro para o objeto recém-criado.

Método que cria um objeto-forma no gráfico atual numa determinada subjanela:

//+-----------------------------------------------------------------------+
//| Create the form object on the current chart in a specified subwindow  |
//+-----------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
   return this.CreateForm(form_id,::ChartID(),wnd,name,x,y,w,h);
  }
//+------------------------------------------------------------------+

Ao método são transferidos o identificador único do objeto-forma criado, o número da subjanela do gráfico, o nome do objeto-forma e as coordenadas X e Y, largura e altura necessárias para criar a forma. O método retorna o resultado da forma de chamada este método (considerada acima) com uma indicação explícita do identificador do gráfico atual.

Método que cria um objeto-forma no gráfico atual na janela do gráfico principal:

//+-----------------------------------------------------------------------+
//| Create the form object on the current chart in the chart main window  |
//+-----------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h)
  {
   return this.CreateForm(form_id,::ChartID(),0,name,x,y,w,h);
  }
//+------------------------------------------------------------------+

Ao método são transferidos o identificador único do objeto-forma criado, o nome do objeto-forma e as coordenadas X e Y, largura e altura necessárias para criar a forma. O método retorna o resultado da primeira forma de chamada deste método com uma indicação explícita do identificador do gráfico atual e do número da janela principal do gráfico.

Para criar objetos-formas, não precisamos de mais nada - todo o trabalho de criação de várias animações no objeto de forma criado será realizado de acordo com o ponteiro para esse objeto, ponteiro esse retornado pelos métodos acima.

Estamos prontos para começar a integrar o trabalho com gráficos em todos os objetos da biblioteca que são herdeiros do objeto base de todos os objetos da biblioteca CBaseObj.


Integrando gráficos na biblioteca

Agora, precisamos que cada objeto da biblioteca "veja" as classes dos objetos gráficos e seja capaz de criar esses objetos. Para fazer isso, devemos apenas declarar uma instância da classe de gerenciamento de objetos gráficos como parte da classe de objeto base dos objetos da biblioteca. Assim, todos os seus herdeiros serão imediatamente dotados com a capacidade de criar gráficos por meio de uma instância da classe CGraphElmControl que acabamos de revisar.

Abrimos o arquivo \MQL5\Include\DoEasy\Objects\BaseObj.mqh e anexamos a ele o arquivo da classe de gerenciamento de objetos gráficos:

//+------------------------------------------------------------------+
//|                                                      BaseObj.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Services\DELib.mqh"
#include "..\Objects\Graph\GraphElmControl.mqh"
//+------------------------------------------------------------------+

Na seção protegida da classe CBaseObj declaramos uma instância do objeto da classe de gerenciamento de objeto gráfico:

//+------------------------------------------------------------------+
//| Base object class for all library objects                        |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   CGraphElmControl  m_graph_elm;                              // Instance of the class for managing graphical elements
   ENUM_LOG_LEVEL    m_log_level;                              // Logging level
   ENUM_PROGRAM_TYPE m_program;                                // Program type
   bool              m_first_start;                            // First launch flag
   bool              m_use_sound;                              // Flag of playing the sound set for an object
   bool              m_available;                              // Flag of using a descendant object in the program
   int               m_global_error;                           // Global error code
   long              m_chart_id_main;                          // Control program chart ID
   long              m_chart_id;                               // Chart ID
   string            m_name;                                   // Object name
   string            m_folder_name;                            // Name of the folder storing CBaseObj descendant objects 
   string            m_sound_name;                             // Object sound file name
   int               m_type;                                   // Object type (corresponds to the collection IDs)

public:

Na seção pública da classe escrevemos os métodos para criar um objeto-forma:

//--- Return an object type
   virtual int       Type(void)                                const { return this.m_type;                     }
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false)  { return;                        }
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false){ return;                        }
//--- Create a form object on a specified chart in a specified subwindow
   CForm            *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,chart_id,wnd,name,x,y,w,h);                }
//--- Create a form object on the current chart in a specified subwindow
   CForm            *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,wnd,name,x,y,w,h);                         }
//--- Create the form object on the current chart in the main window
   CForm            *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,name,x,y,w,h);                             }
   
//--- Constructor

Os métodos retornam o resultado do trabalho dos três métodos que têm o mesmo nome mencionados acima da classe de gerenciamento de objetos gráficos.

Agora, cada um dos objetos herdeiros da classe CBaseObj tem a capacidade de criar um objeto-forma através da chamada desses métodos.

Hoje vamos verificar como trabalham os objetos gráficos por meio da classe do objeto "Bar".
Abrimos o arquivo desta classe \MQL5\Include\DoEasy\Objects\Series\Bar.mqh e ao método de configuração de parâmetros SetProperties() anexamos a transferência do tipo objeto-barra para a classe de gerenciamento de objetos gráficos:

//+------------------------------------------------------------------+
//| Set bar object parameters                                        |
//+------------------------------------------------------------------+
void CBar::SetProperties(const MqlRates &rates)
  {
   this.SetProperty(BAR_PROP_SPREAD,rates.spread);
   this.SetProperty(BAR_PROP_VOLUME_TICK,rates.tick_volume);
   this.SetProperty(BAR_PROP_VOLUME_REAL,rates.real_volume);
   this.SetProperty(BAR_PROP_TIME,rates.time);
   this.SetProperty(BAR_PROP_TIME_YEAR,this.TimeYear());
   this.SetProperty(BAR_PROP_TIME_MONTH,this.TimeMonth());
   this.SetProperty(BAR_PROP_TIME_DAY_OF_YEAR,this.TimeDayOfYear());
   this.SetProperty(BAR_PROP_TIME_DAY_OF_WEEK,this.TimeDayOfWeek());
   this.SetProperty(BAR_PROP_TIME_DAY,this.TimeDay());
   this.SetProperty(BAR_PROP_TIME_HOUR,this.TimeHour());
   this.SetProperty(BAR_PROP_TIME_MINUTE,this.TimeMinute());
//---
   this.SetProperty(BAR_PROP_OPEN,rates.open);
   this.SetProperty(BAR_PROP_HIGH,rates.high);
   this.SetProperty(BAR_PROP_LOW,rates.low);
   this.SetProperty(BAR_PROP_CLOSE,rates.close);
   this.SetProperty(BAR_PROP_CANDLE_SIZE,this.CandleSize());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_BODY,this.BodySize());
   this.SetProperty(BAR_PROP_CANDLE_BODY_TOP,this.BodyHigh());
   this.SetProperty(BAR_PROP_CANDLE_BODY_BOTTOM,this.BodyLow());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_UP,this.ShadowUpSize());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_DOWN,this.ShadowDownSize());
//---
   this.SetProperty(BAR_PROP_TYPE,this.BodyType());
//--- Set the object type to the object of the graphical object management class
   this.m_graph_elm.SetTypeNode(this.m_type);
  }
//+------------------------------------------------------------------+

Temos quase tudo pronto para realizar o teste. Mas há uma outra coisa. No início, quando começamos a trabalhar com objetos gráficos, não os anexamos à biblioteca principal - apenas usamos as classes dos elementos gráficos tal qual como estavam. Agora precisamos fazer tudo como deve ser, todos os objetos da biblioteca são acessíveis por meio de seu objeto principal, isto é, através da classe CEngine na qual são integrados os arquivos de coleção de todos os objetos. Mas, para objetos gráficos, ainda não temos uma classe de coleção, porque ainda é muito cedo para fazê-lo por um motivo simples: nem todos os objetos estão criados. No entanto, podemos criar uma classe-coleção de objetos gráficos preliminar para que tudo já esteja feito "como planeado", e, só depois de criar todos os objetos, retornar a ela e terminá-la como deveria ser.

Com base nessas considerações, criaremos agora uma versão preliminar da classe-coleção de objetos gráficos. Para ela, precisamos especificar o identificador de lista da coleção de objetos gráficos no arquivo \MQL5\Include\DoEasy\Defines.mqh:

//--- Parameters of the chart collection timer
#define COLLECTION_CHARTS_PAUSE        (500)                      // Chart collection timer pause in milliseconds
#define COLLECTION_CHARTS_COUNTER_STEP (16)                       // Chart timer counter increment
#define COLLECTION_CHARTS_COUNTER_ID   (9)                        // Chart timer counter ID
//--- Collection list IDs
#define COLLECTION_HISTORY_ID          (0x777A)                   // Historical collection list ID
#define COLLECTION_MARKET_ID           (0x777B)                   // Market collection list ID
#define COLLECTION_EVENTS_ID           (0x777C)                   // Event collection list ID
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Account collection list ID
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Symbol collection list ID
#define COLLECTION_SERIES_ID           (0x777F)                   // Timeseries collection list ID
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Indicator buffer collection list ID
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Indicator collection list ID
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Indicator data collection list ID
#define COLLECTION_TICKSERIES_ID       (0x7783)                   // Tick series collection list ID
#define COLLECTION_MBOOKSERIES_ID      (0x7784)                   // DOM series collection list ID
#define COLLECTION_MQL5_SIGNALS_ID     (0x7785)                   // MQL5 signals collection list ID
#define COLLECTION_CHARTS_ID           (0x7786)                   // Chart collection list ID
#define COLLECTION_CHART_WND_ID        (0x7787)                   // Chart window list ID
#define COLLECTION_GRAPH_OBJ_ID        (0x7788)                   // Graphical object collection list ID
//--- Pending request type IDs

Na pasta da biblioteca \MQL5\Include\DoEasy\Collections\ criamos o novo arquivo GraphElementsCollection.mqh da classe CGraphElementsCollection:

//+------------------------------------------------------------------+
//|                                      GraphElementsCollection.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"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Graph\Form.mqh"
//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
class CGraphElementsCollection : public CBaseObj
  {
private:
   CListObj          m_list_all_graph_obj;      // List of all 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 object in the list of graphical objects
   bool              IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj);
public:
//--- Return itself
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
   //--- Return the full collection list 'as is'
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_all_graph_obj;   }
   //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByGraphCanvElementProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetList(),property,value,mode);  }
   //--- Return the number of new graphical objects
   int               NewObjects(void)   const                                                            { return this.m_delta_graph_obj;       }
   //--- Constructor
                     CGraphElementsCollection();
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false);
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGraphElementsCollection::CGraphElementsCollection()
  {
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
   this.m_list_all_graph_obj.Clear();
   this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID);
  }
//+------------------------------------------------------------------+

A estrutura da classe não é diferente da das classes-coleções de outros objetos da biblioteca. Neste caso, apenas estamos interessados no construtor da classe. Além disso, todos os outros métodos são declarados da mesma forma que em outras coleções de objetos da biblioteca, e iremos implementá-los mais tarde. Agora é importante que nesta classe seja integrado o arquivo da classe do objeto-forma através do qual os programas criados com base nesta biblioteca verão objetos gráficos. No construtor da classe para o gráfico atual estão habilitados o rastreamento de eventos de movimento do mouse e da roda do mouse.

Isso é tudo. Por enquanto, o restante neste esboço da classe-coleção de objetos gráficos não é importante - vamos desenvolver tudo isso mais tarde, depois de criar todos os objetos gráficos da biblioteca.

Resta anexar o arquivo da classe-coleção de objetos gráficos ao arquivo do objeto principal da biblioteca CEngine localizado em \MQL5\Include\DoEasy\Engine.mqh:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
#include "Collections\TimeSeriesCollection.mqh"
#include "Collections\BuffersCollection.mqh"
#include "Collections\IndicatorsCollection.mqh"
#include "Collections\TickSeriesCollection.mqh"
#include "Collections\BookSeriesCollection.mqh"
#include "Collections\MQLSignalsCollection.mqh"
#include "Collections\ChartObjCollection.mqh"
#include "Collections\GraphElementsCollection.mqh"
#include "TradingControl.mqh"
//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {

Agora estamos prontos para testar os objetos gráficos integrados na classe do objeto-barra.


Teste

Para testar, criaremos uma lista-série temporal para o símbolo e o período gráfico atuais. Esta lista armazenará nossos objetos-barras aos que, por sua vez, hoje adicionamos a classe de gerenciamento de objetos gráficos, classe essa que permite gerar um objeto-forma próprio para cada barra.

Assim, ao manter pressionada a tecla Ctrl e mover o mouse no gráfico, na barra onde está o cursor será criado um objeto-forma sombreado que, por sua vez, exibirá um texto descrevendo o tipo de barra (alta/baixa/doji). Adicionalmente, com a tecla Ctrl pressionada, será desabilitado o uso da roda do mouse e será exibida logo a forma com a descrição da barra. Já ao soltar a tecla Ctrl, a lista de objetos-formas criados será apagada se surgir um novo tick ou se for deslocado o gráfico, porque para o teste nunca é necessário rastrear os momentos pressionar/soltar a tecla Ctrl. A limpeza da lista de objetos criados, em princípio, só é necessária para "ocultar" alguns dos problemas que surgem ao alterar a escala do gráfico - os objetos-formas criados anteriormente começam a ser exibidos em seus lugares antigos, ou seja, não corresponderão à posição atual do candle na escala redimensionada do gráfico. Para um teste rápido, é mais fácil limpar a lista do que recalcular as coordenadas do objeto ao alterar a escala do gráfico.

Para o teste, vamos pegar o Expert Advisor do artigo anterior e o salvamos na nova pasta \MQL5\Experts\TestDoEasy\Part81\ com o novo nome TestDoEasyPart781.mq5.

Na área global em vez de anexar arquivos

#include <Arrays\ArrayObj.mqh>
#include <DoEasy\Services\Select.mqh>
#include <DoEasy\Objects\Graph\Form.mqh>

incluímos o arquivo do objeto principal da biblioteca e declaramos sua instância:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart81.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
//--- 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[];

//+------------------------------------------------------------------+

Do manipulador OnInit() do EA removemos o código que cria um objeto-forma, porque só precisamos especificar o símbolo usado na biblioteca e criar uma série temporal para o símbolo e período gráfico atual. Como resultado, o manipulador ficará assim:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'246,244,244';     // Original ≈pale gray
   array_clr[1]=C'249,251,250';     // Final ≈pale gray-green
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Do EA removemos as funções FigureType() e FigureProcessing(), porque não precisamos delas para este teste, mas, mesmo assim, ocupam quase todo o volume do código do EA.

Vamos escrever três funções em seu lugar.

Função que retorna um sinalizador que indica a existência de uma forma com o nome especificado:

//+-------------------------------------------------------------------------------+
//| Return the flag that indicates the existence of a form with a specified name  |
//+-------------------------------------------------------------------------------+
bool IsPresentForm(const string name)
  {
   //--- In the loop by the list of form objects,
   for(int i=0;i<list_forms.Total();i++)
     {
      //--- get the next form object
      CForm *form=list_forms.At(i);
      if(form==NULL)
         continue;
      //--- form the desired object name as "Program name_" + the form name passed to the function
      string nm=MQLInfoString(MQL_PROGRAM_NAME)+"_"+name;
      //--- If the current form object has such a name, return 'true'
      if(form.NameObj()==nm)
         return true;
     }
   //--- Upon the loop completion, return 'false'
   return false;
  }
//+------------------------------------------------------------------+

Função que oculta todas as formas, exceto uma com o nome especificado:

//+------------------------------------------------------------------+
//| Hide all forms except the one with the specified name            |
//+------------------------------------------------------------------+
void HideFormAllExceptOne(const string name)
  {
   //--- In the loop by the list of form objects,
   for(int i=0;i<list_forms.Total();i++)
     {
      //--- get the next form object
      CForm *form=list_forms.At(i);
      if(form==NULL)
         continue;
      //--- form the desired object name as "Program name_" + the form name passed to the function
      string nm=MQLInfoString(MQL_PROGRAM_NAME)+"_"+name;
      //--- If the current form object has such a name, display it,
      if(form.NameObj()==nm)
         form.Show();
      //--- otherwise - hide
      else
         form.Hide();
     }
  }
//+------------------------------------------------------------------+

Função que retorna o sinalizador que indica que a tecla "Ctrl" está pressionada:

//+------------------------------------------------------------------+
//| Return the flag of holding Ctrl                                  |
//+------------------------------------------------------------------+
bool IsCtrlKeyPressed(void)
  {
   return((TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)&0x80)!=0);
  }
//+------------------------------------------------------------------+

Todas as funções são bastante simples e, creio eu, não precisam de explicações.

Do manipulador OnChartEvent() removemos o processamento de pressionamentos de tecla e cliques em objetos - não precisamos disso hoje. Adicionamos apenas o processamento do movimento do mouse:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- 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 and allow scrolling a chart with the mouse
         list_forms.Clear();
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,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
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,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,76,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();
           }
         //--- Redraw the chart
         ChartRedraw();
        }
     }
  }
//+------------------------------------------------------------------+

O código do manipulador é totalmente comentado na listagem. Espero que tudo esteja claro. Em qualquer caso, você sempre pode colocar perguntas na discussão do artigo.

Vamos compilar o Expert Advisor e executá-lo no gráfico. Mantemos pressionada a tecla Ctrl e movemos o mouse sobre o gráfico. Para cada barra, será criado um objeto-barra que mostrará a descrição do tipo de barra (alta/baixa/doji). Ao soltar a tecla Ctrl, serão excluídos todos os objetos criados.



O que vem agora?

No próximo artigo, continuaremos a integrar objetos gráficos nos objetos da biblioteca.

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 perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.

Complementos

*Artigos desta série:

Gráficos na biblioteca DoEasy (Parte 73): objeto-forma de um elemento gráfico
Gráficos na biblioteca DoEasy (Parte 74): elemento gráfico básico baseado na classe CCanvas
Gráficos na biblioteca DoEasy (Parte 75): métodos para trabalhar com primitivas e texto num elemento gráfico básico
Gráficos na biblioteca DoEasy (Parte 76): objeto Forma e temas de cores predefinidos
Gráficos na biblioteca DoEasy (Parte 77): classe do objeto Sombra
Gráficos na Biblioteca DoEasy (Parte 78): princípios de animação dentro da biblioteca. Corte de imagens
Gráficos na biblioteca DoEasy (Parte 79): classe para o objeto quadro-de-animação e seus objetos herdeiros
Gráficos na biblioteca DoEasy (Parte 80): classe do objeto quadro de animação geométrica

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

Arquivos anexados |
MQL5.zip (4087.48 KB)
Perceptron Multicamadas e o Algoritmo Backpropagation (Parte II): Implementação em Python e Integração com MQL5 Perceptron Multicamadas e o Algoritmo Backpropagation (Parte II): Implementação em Python e Integração com MQL5
Um pacote python foi disponibilizado com o proposito de trazer integração com MQL, com isso abre-se as portas para enumeras possibilidades como, exploração de dados, criação e uso de modelos de machine learning. Com essa integração nativa entre MQL5 e Python, abriu-se as portas para muitas possibilidades de uso, podemos construir de uma simples regressão linear a um modelo de aprendizado profundo. Vamos entender como instalar e preparar o ambiente de desenvolvimento e usar algumas das bibliotecas de aprendizado de maquina.
Como se tornar um bom programador (Parte 1): cinco hábitos que devem ser abandonados para programar melhor em MQL5 Como se tornar um bom programador (Parte 1): cinco hábitos que devem ser abandonados para programar melhor em MQL5
Tanto iniciantes quanto programadores avançados têm alguns hábitos ruins que os impedem de melhorar. Neste artigo, vamos discuti-los e ver o que podemos fazer com eles. O artigo é destinado a todos que desejam se tornar um programador MQL5 de sucesso.
Combinatória e teoria da probabilidade para negociação (Parte II): fractal universal Combinatória e teoria da probabilidade para negociação (Parte II): fractal universal
Neste artigo, continuaremos a estudar fractais e prestaremos muita atenção a resumir todo o material. Tentarei apresentar todos os projetos da maneira mais compacta e compreensível para serem aplicados ao trading.
Gráficos na biblioteca DoEasy (Parte 80): classe do objeto quadro de animação geométrica Gráficos na biblioteca DoEasy (Parte 80): classe do objeto quadro de animação geométrica
Neste artigo, otimizaremos o código das classes vistas nos artigos anteriores e criaremos uma classe para o objeto do quadro de animação geométrica que nos permite desenhar polígonos regulares com um determinado número de vértices.