Gráficos na biblioteca DoEasy (Parte 81): integrando gráficos nos objetos da biblioteca
Sumário
- Ideia
- Aprimorando as classes da biblioteca
- Classe de gerenciamento de objetos gráficos
- Integrando gráficos na biblioteca
- Teste
- O que vem agora?
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.
*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
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso