English Русский 中文 Español Deutsch 日本語
preview
Componente View para tabelas no paradigma MVC em MQL5: elemento gráfico básico

Componente View para tabelas no paradigma MVC em MQL5: elemento gráfico básico

MetaTrader 5Exemplos |
66 0
Artyom Trishkin
Artyom Trishkin

Conteúdo



Introdução

Na programação moderna, o paradigma MVC (Model-View-Controller) é uma das abordagens populares para o desenvolvimento de aplicações complexas. Ele permite separar a lógica da aplicação em três componentes independentes: Model (modelo de dados), View (visualização) e Controller (controlador). Essa abordagem simplifica o desenvolvimento, o teste e a manutenção do código, tornando-o mais estruturado e legível.

No contexto desta série de artigos, analisamos o processo de criação de tabelas no paradigma MVC na linguagem MQL5. Nos dois primeiros artigos, desenvolvemos os modelos de dados (Model) e a arquitetura básica das tabelas. Agora é o momento de avançar para o componente View, que é responsável pela visualização dos dados e, parcialmente, pela interação com o usuário.

Neste artigo, criaremos um objeto básico para desenho no canvas, que servirá como base para a construção de todos os componentes visuais das tabelas e de quaisquer outros elementos de controle. O objeto incluirá:

  • gerenciamento de cores em diferentes estados, estado padrão, foco, pressionado e bloqueio;
  • suporte a transparência e alteração dinâmica de tamanhos;
  • possibilidade de recorte de objetos pelos limites do contêiner;
  • gerenciamento de visibilidade e bloqueio de objetos;
  • separação da gráfica em duas camadas, fundo e primeiro plano.

Aqui não abordaremos a integração com o componente Model já criado e, muito menos, com o componente Controller ainda não criado, porém as classes desenvolvidas serão projetadas considerando a integração futura. Isso permitirá, posteriormente, vincular facilmente os elementos visuais aos dados e à lógica de controle, garantindo a interação completa dentro do paradigma MVC. Como resultado, obteremos uma ferramenta flexível para a criação de tabelas e de outros elementos gráficos que podem ser utilizados em seus projetos.

Como a implementação da arquitetura do componente View em MQL5 é bastante trabalhosa, incluindo muitas classes auxiliares e heranças, acordamos adotar uma exposição relativamente concisa. Definiremos a classe, apresentaremos sua descrição breve e, em seguida, também de forma sucinta, analisaremos sua implementação. Hoje teremos cinco classes:

  1. classe base para todos os objetos gráficos,
  2. classe para gerenciamento de cor,
  3. classe para gerenciamento das cores dos diferentes estados do elemento gráfico,
  4. classe para gerenciamento de área retangular,
  5. classe base para desenho de elementos gráficos no canvas.

Todas essas classes, ao final, são necessárias para o funcionamento da classe base de desenho de elementos gráficos. A partir dela serão herdadas todas as demais classes que serão criadas na implementação de diversos elementos de controle, em particular do elemento de controle Table Control.

As quatro primeiras classes dessa lista são classes auxiliares para facilitar a implementação do funcional da classe base de desenho de elementos gráficos (5), da qual posteriormente herdaremos para a criação de todos os elementos de controle e de seus componentes.


Classes auxiliares

Se ainda não existir, criaremos no diretório do terminal \MQL5\Scripts\ uma nova pasta Tables, e dentro dela a pasta Controls. Nela iremos armazenar os arquivos criados no âmbito dos artigos sobre a criação do componente View para tabelas.

Nessa nova pasta criaremos um novo arquivo de inclusão Base.mqh. Nele, hoje escreveremos os códigos das classes do objeto base para a criação de elementos de controle. Previamente, escreveremos as macrossubstituições, enumerações e funções:

//+------------------------------------------------------------------+
//|                                                         Base.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"

//+------------------------------------------------------------------+
//| Include libraries                                                |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>              // CCanvas class
#include <Arrays\List.mqh>                // CList class

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define  clrNULL              0x00FFFFFF  // Transparent color for CCanvas
#define  MARKER_START_DATA    -1          // Data start marker in a file

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_TYPE                    // Enumeration of graphical element types
  {
   ELEMENT_TYPE_BASE = 0x10000,           // Basic object of graphical elements
   ELEMENT_TYPE_COLOR,                    // Color object
   ELEMENT_TYPE_COLORS_ELEMENT,           // Color object of the graphical object element
   ELEMENT_TYPE_RECTANGLE_AREA,           // Rectangular area of the element
   ELEMENT_TYPE_CANVAS_BASE,              // Basic canvas object for graphical elements
  };

enum ENUM_COLOR_STATE                     // Enumeration of element state colors
  {
   COLOR_STATE_DEFAULT,                   // Normal state color
   COLOR_STATE_FOCUSED,                   // Color when hovering over an element
   COLOR_STATE_PRESSED,                   // Color when clicking on an element
   COLOR_STATE_BLOCKED,                   // Blocked element color
  };
//+------------------------------------------------------------------+ 
//| Functions                                                        |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Return the element type as a string                             |
//+------------------------------------------------------------------+
string ElementDescription(const ENUM_ELEMENT_TYPE type)
  {
   string array[];
   int total=StringSplit(EnumToString(type),StringGetCharacter("_",0),array);
   string result="";
   for(int i=2;i<total;i++)
     {
      array[i]+=" ";
      array[i].Lower();
      array[i].SetChar(0,ushort(array[i].GetChar(0)-0x20));
      result+=array[i];
     }
   result.TrimLeft();
   result.TrimRight();
   return result;
  }
//+------------------------------------------------------------------+
//| Classes                                                          |
//+------------------------------------------------------------------+

A função que retorna o tipo do objeto como string nós já fizemos nos artigos anteriores. A função que retorna o tipo do elemento de controle na forma de uma descrição em string é absolutamente idêntica à escrita anteriormente. Apenas a string da enumeração é dividida em substrings pelo delimitador "*" e, a partir das substrings obtidas, é criada a string final.

Por enquanto, como essas duas funções idênticas estão localizadas em arquivos diferentes, vamos mantê-las assim. Mais adiante, ao unirmos todos os arquivos em um único projeto, reformularemos ambas as funções em uma só, que retornará o nome não a partir da enumeração, mas da string passada. Nesse caso, o mesmo algoritmo retornará de forma uniforme o nome do objeto, do elemento etc. É importante que, nas constantes de todas as enumerações, esteja registrada a mesma estrutura de nome da constante: OBJECT_TYPE*XXX_YYY, ELEMENT_TYPE_XXX_YYY, ANYOTHER_TYPE_XXX_YYY_ZZZ ... Nesse caso, será retornado aquilo que está escrito em XXX_YYY (XXX_YYY_ZZZ e assim por diante), enquanto a parte destacada aqui em amarelo será descartada.

Em todos os objetos de elementos gráficos e nas classes auxiliares, em cada um deles, existem as mesmas variáveis e métodos de acesso a elas, identificador e nome do objeto. E com base nessas propriedades pode ser realizada a busca e a ordenação dos elementos que se encontram em listas. É razoável extrair essas variáveis e os métodos de acesso a elas para uma classe separada, da qual todos os demais elementos herdarão.

Esse será a classe base dos objetos de elementos gráficos:

//+------------------------------------------------------------------+
//| Base class of graphical elements                                 |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   int               m_id;                                     // ID
   ushort            m_name[];                                 // Name
   
public:
//--- Set (1) name and (2) ID
   void              SetName(const string name)                { ::StringToShortArray(name,this.m_name);    }
   void              SetID(const int id)                       { this.m_id=id;                              }
//--- Return (1) name and (2) ID
   string            Name(void)                          const { return ::ShortArrayToString(this.m_name);  }
   int               ID(void)                            const { return this.m_id;                          }

//--- Virtual (1) comparison and object type (2) methods
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BASE); }
   
//--- Constructors/destructor
                     CBaseObj (void) : m_id(-1) {}
                    ~CBaseObj (void) {}
  };
//+------------------------------------------------------------------+
//| CBaseObj::Compare two objects                                    |
//+------------------------------------------------------------------+
int CBaseObj::Compare(const CObject *node,const int mode=0) const
  {
   const CBaseObj *obj=node;
   switch(mode)
     {
      case 0   :  return(this.Name()>obj.Name() ? 1 : this.Name()<obj.Name() ? -1 : 0);
      default  :  return(this.ID()>obj.ID()     ? 1 : this.ID()<obj.ID()     ? -1 : 0);
     }
  }

Descrição breve:

Classe base para todos os objetos gráficos:

  • contém propriedades comuns, como identificador (m_id) e nome (m_name),
  • fornece métodos básicos para comparação de objetos (Compare) e obtenção do tipo do objeto (Type),
  • é utilizada como classe pai para todas as demais classes, garantindo a uniformidade da hierarquia.

A classe fornece um conjunto mínimo de propriedades inerentes a cada um dos objetos de elementos gráficos. A herança dessa classe nos livrará da necessidade de declarar essas variáveis em cada nova classe e de escrever métodos de acesso a elas, pois essas variáveis e métodos estarão disponíveis nas classes herdadas. Consequentemente, o método Compare também fornecerá a cada um dos objetos herdados da classe base a possibilidade de busca e ordenação por essas duas propriedades.



Classes de gerenciamento de cor

Quando formos desenvolver o componente Controller, será necessário criar o acabamento visual da interação do usuário com os elementos gráficos. Uma das formas de demonstrar a atividade de um objeto é a alteração de sua cor quando o cursor passa sobre a área do elemento gráfico, sua reação ao clique do mouse ou ainda o bloqueio programático.

Cada objeto possui três componentes do seu elemento que podem mudar de cor em diferentes eventos de interação com o usuário: a cor do fundo, da borda e do texto. Cada um desses três componentes pode conter seu próprio conjunto de cores para diferentes estados. Para facilitar o gerenciamento da cor de um único elemento, bem como o gerenciamento das cores de todos os elementos que alteram sua cor, escreveremos duas classes auxiliares.

Classe de cor

//+------------------------------------------------------------------+
//| Color class                                                      |
//+------------------------------------------------------------------+
class CColor : public CBaseObj
  {
protected:
   color             m_color;                                  // Color
   
public:
//--- Set color
   bool              SetColor(const color clr)
                       {
                        if(this.m_color==clr)
                           return false;
                        this.m_color=clr;
                        return true;
                       }
//--- Return color
   color             Get(void)                           const { return this.m_color;              }

//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_COLOR);       }
   
//--- Constructors/destructor
                     CColor(void) : m_color(clrNULL)                          { this.SetName("");  }
                     CColor(const color clr) : m_color(clr)                   { this.SetName("");  }
                     CColor(const color clr,const string name) : m_color(clr) { this.SetName(name);}
                    ~CColor(void) {}
  };
//+------------------------------------------------------------------+
//| CColor::Return the object description                            |
//+------------------------------------------------------------------+
string CColor::Description(void)
  {
   string color_name=(this.Get()!=clrNULL ? ::ColorToString(this.Get(),true) : "clrNULL (0x00FFFFFF)");
   return(this.Name()+(this.Name()!="" ? " " : "")+"Color: "+color_name);
  }
//+------------------------------------------------------------------+
//| CColor::Display object description in the journal                |
//+------------------------------------------------------------------+
void CColor::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CColor::Save to file                                             |
//+------------------------------------------------------------------+
bool CColor::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;

//--- Save the color
   if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the ID
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the name
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| CColor::Load from file                                           |
//+------------------------------------------------------------------+
bool CColor::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;

//--- Load the color
   this.m_color=(color)::FileReadInteger(file_handle,INT_VALUE);
//--- Load the ID
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Load the name
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   
//--- All is successful
   return true;
  }

Descrição breve da classe:

Classe para gerenciamento de cor:

  • armazena a cor como um valor do tipo color (m_color),
  • fornece métodos para definir e obter a cor (SetColor, Get),
  • implementa métodos de salvamento e carregamento da cor em/de arquivo (Save, Load),
  • pode ser utilizada para representar qualquer cor em elementos gráficos.

Classe de cores do elemento do objeto gráfico

//+------------------------------------------------------------------+
//| Color class of a graphical object element                        |
//+------------------------------------------------------------------+
class CColorElement : public CBaseObj
  {
protected:
   CColor            m_current;                                // Current color. It could be one of the following
   CColor            m_default;                                // Normal state color
   CColor            m_focused;                                // Color on hover
   CColor            m_pressed;                                // Color on click
   CColor            m_blocked;                                // Blocked element color
   
//--- Convert RGB to color
   color             RGBToColor(const double r,const double g,const double b) const;
//--- Write RGB component values to variables
   void              ColorToRGB(const color clr,double &r,double &g,double &b);
//--- Return (1) Red, (2) Green, (3) Blue color components
   double            GetR(const color clr)                     { return clr&0xFF;                           }
   double            GetG(const color clr)                     { return(clr>>8)&0xFF;                       }
   double            GetB(const color clr)                     { return(clr>>16)&0xFF;                      }
   
public:
//--- Return a new color
   color             NewColor(color base_color, int shift_red, int shift_green, int shift_blue);

//--- Initialize colors for different states
   bool              InitDefault(const color clr)              { return this.m_default.SetColor(clr);       }
   bool              InitFocused(const color clr)              { return this.m_focused.SetColor(clr);       }
   bool              InitPressed(const color clr)              { return this.m_pressed.SetColor(clr);       }
   bool              InitBlocked(const color clr)              { return this.m_blocked.SetColor(clr);       }
   
//--- Set colors for all states
   void              InitColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked);
   void              InitColors(const color clr);
    
//--- Return colors of different states
   color             GetCurrent(void)                    const { return this.m_current.Get();               }
   color             GetDefault(void)                    const { return this.m_default.Get();               }
   color             GetFocused(void)                    const { return this.m_focused.Get();               }
   color             GetPressed(void)                    const { return this.m_pressed.Get();               }
   color             GetBlocked(void)                    const { return this.m_blocked.Get();               }
   
//--- Set one of the colors from the list as the current one
   bool              SetCurrentAs(const ENUM_COLOR_STATE color_state);

//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_COLORS_ELEMENT);       }
   
//--- Constructors/destructor
                     CColorElement(void);
                     CColorElement(const color clr);
                     CColorElement(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked);
                    ~CColorElement(void) {}
  };
//+-----------------------------------------------------------------------------+
//| CColorControl::Constructor with setting the transparent colors of the object|
//+-----------------------------------------------------------------------------+
CColorElement::CColorElement(void)
  {
   this.InitColors(clrNULL,clrNULL,clrNULL,clrNULL);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Constructor specifying the object colors          |
//+------------------------------------------------------------------+
CColorElement::CColorElement(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked)
  {
   this.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Constructor specifying the object colors          |
//+------------------------------------------------------------------+
CColorElement::CColorElement(const color clr)
  {
   this.InitColors(clr);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Set colors for all states                         |
//+------------------------------------------------------------------+
void CColorElement::InitColors(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked)
  {
   this.InitDefault(clr_default);
   this.InitFocused(clr_focused);
   this.InitPressed(clr_pressed);
   this.InitBlocked(clr_blocked);   
  }
//+----------------------------------------------------------------------+
//| CColorControl::Set the colors for all states based on the current one|
//+----------------------------------------------------------------------+
void CColorElement::InitColors(const color clr)
  {
   this.InitDefault(clr);
   this.InitFocused(this.NewColor(clr,-3,-3,-3));
   this.InitPressed(this.NewColor(clr,-6,-6,-6));
   this.InitBlocked(clrSilver);   
  }
//+---------------------------------------------------------------------+
//|CColorControl::Set one of the colors from the list as the current one|
//+---------------------------------------------------------------------+
bool CColorElement::SetCurrentAs(const ENUM_COLOR_STATE color_state)
  {
   switch(color_state)
     {
      case COLOR_STATE_DEFAULT   :  return this.m_current.SetColor(this.m_default.Get());
      case COLOR_STATE_FOCUSED   :  return this.m_current.SetColor(this.m_focused.Get());
      case COLOR_STATE_PRESSED   :  return this.m_current.SetColor(this.m_pressed.Get());
      case COLOR_STATE_BLOCKED   :  return this.m_current.SetColor(this.m_blocked.Get());
      default                    :  return false;
     }
  }
//+------------------------------------------------------------------+
//| CColorControl::Convert RGB to color                              |
//+------------------------------------------------------------------+
color CColorElement::RGBToColor(const double r,const double g,const double b) const
  {
   int int_r=(int)::round(r);
   int int_g=(int)::round(g);
   int int_b=(int)::round(b);
   int clr=0;
   clr=int_b;
   clr<<=8;
   clr|=int_g;
   clr<<=8;
   clr|=int_r;

   return (color)clr;
  }
//+------------------------------------------------------------------+
//| CColorControl::Get RGB component values                          |
//+------------------------------------------------------------------+
void CColorElement::ColorToRGB(const color clr,double &r,double &g,double &b)
  {
   r=this.GetR(clr);
   g=this.GetG(clr);
   b=this.GetB(clr);
  }
//+------------------------------------------------------------------+
//| CColorControl::Return color with a new color component           |
//+------------------------------------------------------------------+
color CColorElement::NewColor(color base_color, int shift_red, int shift_green, int shift_blue)
  {
   double clrR=0, clrG=0, clrB=0;
   this.ColorToRGB(base_color,clrR,clrG,clrB);
   double clrRx=(clrR+shift_red  < 0 ? 0 : clrR+shift_red  > 255 ? 255 : clrR+shift_red);
   double clrGx=(clrG+shift_green< 0 ? 0 : clrG+shift_green> 255 ? 255 : clrG+shift_green);
   double clrBx=(clrB+shift_blue < 0 ? 0 : clrB+shift_blue > 255 ? 255 : clrB+shift_blue);
   return this.RGBToColor(clrRx,clrGx,clrBx);
  }
//+------------------------------------------------------------------+
//| CColorElement::Return the object description                     |
//+------------------------------------------------------------------+
string CColorElement::Description(void)
  {
   string res=::StringFormat("%s Colors. %s",this.Name(),this.m_current.Description());
   res+="\n  1: "+this.m_default.Description();
   res+="\n  2: "+this.m_focused.Description();
   res+="\n  3: "+this.m_pressed.Description();
   res+="\n  4: "+this.m_blocked.Description();
   return res;
  }
//+------------------------------------------------------------------+
//| CColorElement::Display object description in the journal         |
//+------------------------------------------------------------------+
void CColorElement::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CColorElement::Save to file                                      |
//+------------------------------------------------------------------+
bool CColorElement::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Save the ID
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the name of the element colors
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
//--- Save the current color
   if(!this.m_current.Save(file_handle))
      return false;
//--- Save the color of the normal state
   if(!this.m_default.Save(file_handle))
      return false;
//--- Save the color when hovering the cursor
   if(!this.m_focused.Save(file_handle))
      return false;
//--- Save the color when clicking
   if(!this.m_pressed.Save(file_handle))
      return false;
//--- Save the color of the blocked element
   if(!this.m_blocked.Save(file_handle))
      return false;
   
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| CColorElement::Load from file                                    |
//+------------------------------------------------------------------+
bool CColorElement::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;
   
//--- Load the ID
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Load the name of the element colors
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
//--- Load the current color
   if(!this.m_current.Load(file_handle))
      return false;
//--- Load the normal state color
   if(!this.m_default.Load(file_handle))
      return false;
//--- Load the color on hover
   if(!this.m_focused.Load(file_handle))
      return false;
//--- Load the color on click
   if(!this.m_pressed.Load(file_handle))
      return false;
//--- Load the color of the blocked element
   if(!this.m_blocked.Load(file_handle))
      return false;
   
//--- All is successful
   return true;
  }

Descrição breve da classe:

Classe para gerenciamento das cores dos diferentes estados do elemento gráfico:

  • armazena cores para quatro estados: normal (m_default), foco (m_focused), pressionado (m_pressed) e bloqueado (m_blocked),
  • mantém a cor atual (m_current), que pode ser definida como um dos estados,
  • fornece métodos para inicializar as cores de todos os estados (InitColors) e alternar a cor atual (SetCurrentAs),
  • inclui um método para obter uma nova cor a partir da cor base, com indicação dos deslocamentos dos componentes de cor (NewColor),
  • implementa métodos de salvamento e carregamento de todas as cores em/de arquivo (Save, Load),
  • é útil para a criação de elementos interativos, como botões, linhas ou células de tabelas.



Classe de gerenciamento de área retangular

No terminal, na pasta \MQL5\Include\Controls, é apresentada uma estrutura interessante CRect no arquivo Rect.mqh. Ela fornece um conjunto de métodos para controlar uma janela de forma retangular, que pode delimitar um contorno definido sobre um elemento gráfico. Com esses contornos, é possível destacar virtualmente várias áreas de um mesmo elemento e acompanhar suas coordenadas e limites. Isso permitirá, nas áreas destacadas, rastrear as coordenadas do cursor do mouse e organizar a interação interativa do mouse com a área do elemento gráfico.

O exemplo mais simples são os limites de todo o elemento gráfico. Outro exemplo de área delimitada pode ser, por exemplo, a área destinada à linha de status, às barras de rolagem ou ao cabeçalho da tabela.

Utilizando a estrutura apresentada, podemos criar um objeto especial que permita definir uma área retangular sobre o elemento gráfico. E uma lista desses objetos permitirá armazenar, em um único elemento gráfico, diversas áreas monitoradas, onde cada uma é destinada a seus próprios propósitos e às quais é fornecido acesso pelo nome ou pelo identificador da área.

Para que possamos armazenar essas estruturas em listas de objetos, precisamos criar um objeto herdado de CObject e, nele, declarar essa estrutura.

//+------------------------------------------------------------------+
//| Rectangular region class                                         |
//+------------------------------------------------------------------+
class CBound : public CBaseObj
  {
protected:
   CRect             m_bound;                                  // Rectangular area structure

public:
//--- Change the bounding rectangular (1) width, (2) height and (3) size
   void              ResizeW(const int size)                   { this.m_bound.Width(size);                        }
   void              ResizeH(const int size)                   { this.m_bound.Height(size);                       }
   void              Resize(const int w,const int h)           { this.m_bound.Width(w); this.m_bound.Height(h);   }
   
//--- Set (1) X, (2) Y and (3) both coordinates of the bounding rectangle
   void              SetX(const int x)                         { this.m_bound.left=x;                             }
   void              SetY(const int y)                         { this.m_bound.top=y;                              }
   void              SetXY(const int x,const int y)            { this.m_bound.LeftTop(x,y);                       }
   
//--- (1) Set and (2) shift the bounding rectangle by the specified coordinates/offset size
   void              Move(const int x,const int y)             { this.m_bound.Move(x,y);                          }
   void              Shift(const int dx,const int dy)          { this.m_bound.Shift(dx,dy);                       }
   
//--- Returns the object coordinates, dimensions, and boundaries
   int               X(void)                             const { return this.m_bound.left;                        }
   int               Y(void)                             const { return this.m_bound.top;                         }
   int               Width(void)                         const { return this.m_bound.Width();                     }
   int               Height(void)                        const { return this.m_bound.Height();                    }
   int               Right(void)                         const { return this.m_bound.right-1;                     }
   int               Bottom(void)                        const { return this.m_bound.bottom-1;                    }
   
//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_RECTANGLE_AREA);             }
   
//--- Constructors/destructor
                     CBound(void) { ::ZeroMemory(this.m_bound); }
                     CBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.Resize(w,h); }
                    ~CBound(void) { ::ZeroMemory(this.m_bound); }
  };
//+------------------------------------------------------------------+
//| CBound::Return the object description                            |
//+------------------------------------------------------------------+
string CBound::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   return ::StringFormat("%s%s: x %d, y %d, w %d, h %d",
                         ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,
                         this.X(),this.Y(),this.Width(),this.Height());
  }
//+------------------------------------------------------------------+
//| CBound::Display the object description in the journal            |
//+------------------------------------------------------------------+
void CBound::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CBound::Save to file                                             |
//+------------------------------------------------------------------+
bool CBound::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Save the ID
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the name
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   //--- Save the area structure
   if(::FileWriteStruct(file_handle,this.m_bound)!=sizeof(this.m_bound))
      return(false);
   
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| CBound::Load from file                                           |
//+------------------------------------------------------------------+
bool CBound::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;
   
//--- Load the ID
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Load the name
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   //--- Load the region structure
   if(::FileReadStruct(file_handle,this.m_bound)!=sizeof(this.m_bound))
      return(false);
   
//--- All is successful
   return true;
  }

Descrição breve da classe:

Classe para gerenciamento de área retangular:

  • armazena os limites da área na forma da estrutura CRect (m_bound),
  • fornece métodos para alteração de tamanho (Resize, ResizeW, ResizeH), definição de coordenadas (SetX, SetY, SetXY) e deslocamento da área (Move, Shift),
  • permite obter as coordenadas e dimensões da área (X, Y, Width, Height, Right, Bottom),
  • implementa métodos de salvamento e carregamento da área em/de arquivo (Save, Load),
  • é utilizada para definir os limites de elementos gráficos ou de áreas retangulares dentro deles.

Utilizando as classes auxiliares escritas acima, podemos prosseguir para a criação da classe base de todos os elementos gráficos.



Classe base de desenho

A classe será bastante extensa, portanto, antes de iniciar sua criação e para um melhor entendimento, vamos nos familiarizar com sua descrição breve:

Classe base para trabalhar com elementos gráficos no canvas:

  • contém dois canvases: para o fundo (m_background) e para o primeiro plano (m_foreground),
  • armazena os limites do elemento (m_bound), bem como informações sobre o contêiner (m_container),
  • suporta o gerenciamento de cores por meio de objetos CColorElement para o fundo, o primeiro plano e a borda,
  • implementa métodos para controle de visibilidade (Hide, Show), bloqueio (Block, Unblock) e recorte pelos limites do contêiner (ObjectTrim),
  • oferece suporte à alteração dinâmica de tamanho e coordenadas (ObjectResize, ObjectSetX, ObjectSetY),
  • fornece métodos para desenho (Draw), atualização (Update) e limpeza (Clear) do elemento gráfico,
  • implementa métodos de salvamento e carregamento do objeto em/de arquivo (Save, Load),
  • é a base para a criação de elementos gráficos complexos, como células de tabelas, linhas e cabeçalhos.

Classe base do canvas dos elementos gráficos

//+------------------------------------------------------------------+
//| Base class of graphical elements canvas                          |
//+------------------------------------------------------------------+
class CCanvasBase : public CBaseObj
  {
protected:
   CCanvas           m_background;                             // Background canvas
   CCanvas           m_foreground;                             // Foreground canvas
   CBound            m_bound;                                  // Object boundaries
   CCanvasBase      *m_container;                              // Parent container object
   CColorElement     m_color_background;                       // Background color control object
   CColorElement     m_color_foreground;                       // Foreground color control object
   CColorElement     m_color_border;                           // Border color control object
   long              m_chart_id;                               // Chart ID
   int               m_wnd;                                    // Chart subwindow index
   int               m_wnd_y;                                  // Cursor Y coordinate offset in the subwindow
   int               m_obj_x;                                  // Graphical object X coordinate
   int               m_obj_y;                                  // Graphical object Y coordinate
   uchar             m_alpha;                                  // Transparency
   uint              m_border_width;                           // Frame width
   string            m_program_name;                           // Program name
   bool              m_hidden;                                 // Hidden object flag
   bool              m_blocked;                                // Blocked element flag
   bool              m_focused;                                // Element flag in focus
   
private:
//--- Return the offset of the initial drawing coordinates on the canvas relative to the canvas and the object coordinates
   int               CanvasOffsetX(void)                 const { return(this.ObjectX()-this.X());                                                  }
   int               CanvasOffsetY(void)                 const { return(this.ObjectY()-this.Y());                                                  }
//--- Return the adjusted coordinate of a point on the canvas, taking into account the offset of the canvas relative to the object
   int               AdjX(const int x)                   const { return(x-this.CanvasOffsetX());                                                   }
   int               AdjY(const int y)                   const { return(y-this.CanvasOffsetY());                                                   }
   
protected:
//--- Returns the adjusted chart ID
   long              CorrectChartID(const long chart_id) const { return(chart_id!=0 ? chart_id : ::ChartID());                                     }

//--- Get the boundaries of the parent container object
   int               ContainerLimitLeft(void)            const { return(this.m_container==NULL ? this.X()      :  this.m_container.LimitLeft());   }
   int               ContainerLimitRight(void)           const { return(this.m_container==NULL ? this.Right()  :  this.m_container.LimitRight());  }
   int               ContainerLimitTop(void)             const { return(this.m_container==NULL ? this.Y()      :  this.m_container.LimitTop());    }
   int               ContainerLimitBottom(void)          const { return(this.m_container==NULL ? this.Bottom() :  this.m_container.LimitBottom()); }
   
//--- Return the graphical object coordinates, boundaries and dimensions
   int               ObjectX(void)                       const { return this.m_obj_x;                                                              }
   int               ObjectY(void)                       const { return this.m_obj_y;                                                              }
   int               ObjectWidth(void)                   const { return this.m_background.Width();                                                 }
   int               ObjectHeight(void)                  const { return this.m_background.Height();                                                }
   int               ObjectRight(void)                   const { return this.ObjectX()+this.ObjectWidth()-1;                                       }
   int               ObjectBottom(void)                  const { return this.ObjectY()+this.ObjectHeight()-1;                                      }
   
//--- Change the bounding rectangular (1) width, (2) height and (3) size
   void              BoundResizeW(const int size)              { this.m_bound.ResizeW(size);                                                       }
   void              BoundResizeH(const int size)              { this.m_bound.ResizeH(size);                                                       }
   void              BoundResize(const int w,const int h)      { this.m_bound.Resize(w,h);                                                         }
   
//--- Set (1) X, (2) Y and (3) both coordinates of the bounding rectangle
   void              BoundSetX(const int x)                    { this.m_bound.SetX(x);                                                             }
   void              BoundSetY(const int y)                    { this.m_bound.SetY(y);                                                             }
   void              BoundSetXY(const int x,const int y)       { this.m_bound.SetXY(x,y);                                                          }
   
//--- (1) Set and (2) shift the bounding rectangle by the specified coordinates/offset size
   void              BoundMove(const int x,const int y)        { this.m_bound.Move(x,y);                                                           }
   void              BoundShift(const int dx,const int dy)     { this.m_bound.Shift(dx,dy);                                                        }
   
//--- Change the graphical object (1) width, (2) height and (3) size
   bool              ObjectResizeW(const int size);
   bool              ObjectResizeH(const int size);
   bool              ObjectResize(const int w,const int h);
   
//--- Set the graphical object (1) X, (2) Y and (3) both coordinates
   bool              ObjectSetX(const int x);
   bool              ObjectSetY(const int y);
   bool              ObjectSetXY(const int x,const int y)      { return(this.ObjectSetX(x) && this.ObjectSetY(y));                                 }
   
//--- (1) Set and (2) relocate the graphical object by the specified coordinates/offset size
   bool              ObjectMove(const int x,const int y)       { return this.ObjectSetXY(x,y);                                                     }
   bool              ObjectShift(const int dx,const int dy)    { return this.ObjectSetXY(this.ObjectX()+dx,this.ObjectY()+dy);                     }
   
//--- Limit the graphical object by the container dimensions
   virtual void      ObjectTrim(void);
   
public:
//--- Return the pointer to the canvas (1) background and (2) foreground
   CCanvas          *GetBackground(void)                       { return &this.m_background;                                                        }
   CCanvas          *GetForeground(void)                       { return &this.m_foreground;                                                        }
   
//--- Return the pointer to the color management object for the (1) background, (2) foreground and (3) border
   CColorElement    *GetBackColorControl(void)                 { return &this.m_color_background;                                                  }
   CColorElement    *GetForeColorControl(void)                 { return &this.m_color_foreground;                                                  }
   CColorElement    *GetBorderColorControl(void)               { return &this.m_color_border;                                                      }
   
//--- Return the (1) background, (2) foreground and (3) border color
   color             BackColor(void)                     const { return this.m_color_background.GetCurrent();                                      }
   color             ForeColor(void)                     const { return this.m_color_foreground.GetCurrent();                                      }
   color             BorderColor(void)                   const { return this.m_color_border.GetCurrent();                                          }
   
//--- Set background colors for all states
   void              InitBackColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_background.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitBackColors(const color clr)           { this.m_color_background.InitColors(clr);                                          }

//--- Set foreground colors for all states
   void              InitForeColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_foreground.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitForeColors(const color clr)           { this.m_color_foreground.InitColors(clr);                                          }

//--- Set border colors for all states
   void              InitBorderColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_border.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitBorderColors(const color clr)         { this.m_color_border.InitColors(clr);                                              }

//--- Initialize the color of (1) background, (2) foreground and (3) frame with initial values
   void              InitBackColorDefault(const color clr)     { this.m_color_background.InitDefault(clr);                                         }
   void              InitForeColorDefault(const color clr)     { this.m_color_foreground.InitDefault(clr);                                         }
   void              InitBorderColorDefault(const color clr)   { this.m_color_border.InitDefault(clr);                                             }
   
//--- Initialize the color of (1) background, (2) foreground and (3) frame on hover with initial values
   void              InitBackColorFocused(const color clr)     { this.m_color_background.InitFocused(clr);                                         }
   void              InitForeColorFocused(const color clr)     { this.m_color_foreground.InitFocused(clr);                                         }
   void              InitBorderColorFocused(const color clr)   { this.m_color_border.InitFocused(clr);                                             }
   
//--- Initialize the color of (1) background, (2) foreground and (3) frame on click with initial values
   void              InitBackColorPressed(const color clr)     { this.m_color_background.InitPressed(clr);                                         }
   void              InitForeColorPressed(const color clr)     { this.m_color_foreground.InitPressed(clr);                                         }
   void              InitBorderColorPressed(const color clr)   { this.m_color_border.InitPressed(clr);                                             }
   
//--- Initialize the color of (1) background, (2) foreground and (3) frame of a blocked object with initial values
   void              InitBackColorBlocked(const color clr)     { this.m_color_background.InitBlocked(clr);                                         }
   void              InitForeColorBlocked(const color clr)     { this.m_color_foreground.InitBlocked(clr);                                         }
   void              InitBorderColorBlocked(const color clr)   { this.m_color_border.InitBlocked(clr);                                             }
   
//--- Set the current background color to different states
   bool              BackColorToDefault(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_DEFAULT);                 }
   bool              BackColorToFocused(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_FOCUSED);                 }
   bool              BackColorToPressed(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_PRESSED);                 }
   bool              BackColorToBlocked(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED);                 }
   
//--- Set the current foreground color to different states
   bool              ForeColorToDefault(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_DEFAULT);                 }
   bool              ForeColorToFocused(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_FOCUSED);                 }
   bool              ForeColorToPressed(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_PRESSED);                 }
   bool              ForeColorToBlocked(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_BLOCKED);                 }
   
//--- Set the current frame color to different states
   bool              BorderColorToDefault(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_DEFAULT);                     }
   bool              BorderColorToFocused(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_FOCUSED);                     }
   bool              BorderColorToPressed(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_PRESSED);                     }
   bool              BorderColorToBlocked(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_BLOCKED);                     }
   
//--- Set the current colors of the element to different states
   bool              ColorsToDefault(void);
   bool              ColorsToFocused(void);
   bool              ColorsToPressed(void);
   bool              ColorsToBlocked(void);
   
//--- Set the pointer to the parent container object
   void              SetContainerObj(CCanvasBase *obj);
   
//--- Create OBJ_BITMAP_LABEL
   bool              Create(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);

//--- Return (1) the object's belonging to the program, the flag (2) of a hidden element, (3) a blocked element and (4) the graphical object name
   bool              IsBelongsToThis(void)   const { return(::ObjectGetString(this.m_chart_id,this.NameBG(),OBJPROP_TEXT)==this.m_program_name);   }
   bool              IsHidden(void)          const { return this.m_hidden;                                                                         }
   bool              IsBlocked(void)         const { return this.m_blocked;                                                                        }
   bool              IsFocused(void)         const { return this.m_focused;                                                                        }
   string            NameBG(void)    const { return this.m_background.ChartObjectName();                                                           }
   string            NameFG(void)    const { return this.m_foreground.ChartObjectName();                                                           }
   
//--- (1) Return and (2) set transparency
   uchar             Alpha(void)                         const { return this.m_alpha;                                                              }
   void              SetAlpha(const uchar value)               { this.m_alpha=value;                                                               }
   
//--- (1) Return and (2) set the frame width
    uint             BorderWidth(void)                   const { return this.m_border_width;                                                       } 
    void             SetBorderWidth(const uint width)          { this.m_border_width=width;                                                        }
                      
//--- Returns the object coordinates, dimensions, and boundaries
   int               X(void)                             const { return this.m_bound.X();                                                          }
   int               Y(void)                             const { return this.m_bound.Y();                                                          }
   int               Width(void)                         const { return this.m_bound.Width();                                                      }
   int               Height(void)                        const { return this.m_bound.Height();                                                     }
   int               Right(void)                         const { return this.m_bound.Right();                                                      }
   int               Bottom(void)                        const { return this.m_bound.Bottom();                                                     }
   
//--- Set the new (1) X, (2) Y, (3) XY coordinate for the object
   bool              MoveX(const int x);
   bool              MoveY(const int y);
   bool              Move(const int x,const int y);
   
//--- Shift the object by (1) X, (2) Y, (3) XY xis by the specified offset

   bool              ShiftX(const int dx);
   bool              ShiftY(const int dy);
   bool              Shift(const int dx,const int dy);

//--- Return the object boundaries considering the frame
   int               LimitLeft(void)                     const { return this.X()+(int)this.m_border_width;                                         }
   int               LimitRight(void)                    const { return this.Right()-(int)this.m_border_width;                                     }
   int               LimitTop(void)                      const { return this.Y()+(int)this.m_border_width;                                         }
   int               LimitBottom(void)                   const { return this.Bottom()-(int)this.m_border_width;                                    }

//--- (1) Hide and (2) display the object on all chart periods,
//--- (3) bring the object to the front, (4) block, (5) unblock the element,
//--- (6) fill the object with the specified color with the set transparency
   virtual void      Hide(const bool chart_redraw);
   virtual void      Show(const bool chart_redraw);
   virtual void      BringToTop(const bool chart_redraw);
   virtual void      Block(const bool chart_redraw);
   virtual void      Unblock(const bool chart_redraw);
   void              Fill(const color clr,const bool chart_redraw);
   
//--- (1) Fill the object with a transparent color, (2) update the object to reflect the changes,
//--- (3) draw the appearance and (4) destroy the object
   virtual void      Clear(const bool chart_redraw);
   virtual void      Update(const bool chart_redraw);
   virtual void      Draw(const bool chart_redraw);
   virtual void      Destroy(void);
   
//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_CANVAS_BASE); }
   
//--- Constructors/destructor
                     CCanvasBase(void) :
                        m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_chart_id(::ChartID()), m_wnd(0),
                        m_alpha(0), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0), m_wnd_y(0) { }
                     CCanvasBase(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
                    ~CCanvasBase(void);
  };

A classe representa um conjunto de propriedades do elemento gráfico, uma lista de objetos das classes analisadas acima e um conjunto de métodos para acesso às variáveis e aos métodos das classes auxiliares.

Como resultado, obtemos um objeto bastante flexível, que oferece a possibilidade de gerenciar as propriedades e a aparência do elemento gráfico. Trata-se de um objeto que fornece a todos os seus herdeiros a funcionalidade básica implementada no próprio objeto e expandida nas classes herdadas.

Analisemos os métodos da classe.

Construtor paramétrico

//+------------------------------------------------------------------+
//| CCanvasBase::Constructor                                         |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h) :
   m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_wnd(wnd<0 ? 0 : wnd), m_alpha(0), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0)
  {
//--- Get the adjusted chart ID and the distance in pixels along the vertical Y axis
//--- between the upper frame of the indicator subwindow and the upper frame of the chart main window
   this.m_chart_id=this.CorrectChartID(chart_id);
   this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd);
//--- If the graphical resource and graphical object are created
   if(this.Create(this.m_chart_id,this.m_wnd,name,x,y,w,h))
     {
      //--- Clear the background and foreground canvases and set the initial coordinate values,
      //--- names of graphic objects and properties of text drawn in the foreground
      this.Clear(false);
      this.m_obj_x=x;
      this.m_obj_y=y;
      this.m_color_background.SetName("Background");
      this.m_color_foreground.SetName("Foreground");
      this.m_color_border.SetName("Border");
      this.m_foreground.FontSet("Calibri",12);
      this.m_bound.SetName("Perimeter");
     }
  }

No construtor são passadas as propriedades iniciais do objeto que está sendo criado, são criados os recursos gráficos e os objetos para o desenho do fundo e do primeiro plano, são definidos os valores das coordenadas e os nomes dos objetos gráficos, e são configurados os parâmetros de fonte para a exibição de textos no primeiro plano.

No destrutor da classe o objeto é destruído:

//+------------------------------------------------------------------+
//| CCanvasBase::Destructor                                          |
//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(void)
  {
   this.Destroy();
  }

Método que cria os objetos gráficos de fundo e de primeiro plano

//+------------------------------------------------------------------+
//| CCanvasBase::Create background and foreground graphical objects  |
//+------------------------------------------------------------------+
bool CCanvasBase::Create(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
//--- Get the adjusted chart ID
   long id=this.CorrectChartID(chart_id);
//--- Create a graphical object name for the background and create a canvas
   string obj_name=name+"_BG";
   if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
      return false;
     }
//--- Create a graphical object name for the foreground and create a canvas
   obj_name=name+"_FG";
   if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
      return false;
     }
//--- If created successfully, enter the program name into the OBJPROP_TEXT property of the graphical object
   ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name);
   ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name);
   
//--- Set the dimensions of the rectangular area and return 'true'
   this.m_bound.SetXY(x,y);
   this.m_bound.Resize(w,h);
   return true;
  }

Com o auxílio dos métodos de criação de objetos gráficos OBJ_BITMAP_LABEL da classe CCanvas, são criados os objetos para o desenho do fundo e do primeiro plano, e são definidas as coordenadas e os tamanhos de todo o objeto. Vale destacar que, nas propriedades dos objetos gráficos criados, em OBJPROP_TEXT, é registrado o nome do programa. Isso permitirá identificar a qual programa pertencem os objetos gráficos sem a necessidade de incluir o nome do programa nos nomes dos objetos gráficos.

Método que define o ponteiro para o objeto-contêiner pai

//+------------------------------------------------------------------+
//| CCanvasBase::Set the pointer                                     |
//| to the parent container object                                   |
//+------------------------------------------------------------------+
void CCanvasBase::SetContainerObj(CCanvasBase *obj)
  {
//--- Set the passed pointer to the object
   this.m_container=obj;
//--- If the pointer is empty, leave
   if(this.m_container==NULL)
      return;
//--- If an invalid pointer is passed, zero it in the object and leave
   if(::CheckPointer(this.m_container)==POINTER_INVALID)
     {
      this.m_container=NULL;
      return;
     }
//--- Clip the object along the boundaries of the container assigned to it
   this.ObjectTrim();
  }

Cada elemento gráfico pode fazer parte de seu elemento pai. Por exemplo, em um painel podem estar posicionados botões, listas suspensas e quaisquer outros elementos de controle. Para que os objetos subordinados possam ser recortados pelos limites de seu pai, é necessário passar a eles um ponteiro para esse elemento pai.

Qualquer elemento pai possui métodos que retornam suas coordenadas e limites. Com base nesses limites, os elementos filhos são recortados caso, por algum motivo, ultrapassem as fronteiras do contêiner. Um desses motivos pode ser, por exemplo, a rolagem do conteúdo de uma tabela volumosa.

Método que recorta o objeto gráfico pelo contorno do contêiner

//+-----------------------------------------------------------------------+
//| CCanvasBase::Crop a graphical object to the outline of its container  |
//+-----------------------------------------------------------------------+
void CCanvasBase::ObjectTrim()
  {
//--- Get the container boundaries
   int container_left   = this.ContainerLimitLeft();
   int container_right  = this.ContainerLimitRight();
   int container_top    = this.ContainerLimitTop();
   int container_bottom = this.ContainerLimitBottom();
   
//--- Get the current object boundaries
   int object_left   = this.X();
   int object_right  = this.Right();
   int object_top    = this.Y();
   int object_bottom = this.Bottom();

//--- Check if the object is completely outside the container and hide it if it is
   if(object_right <= container_left || object_left >= container_right ||
      object_bottom <= container_top || object_top >= container_bottom)
     {
      this.Hide(true);
      this.ObjectResize(this.Width(),this.Height());
      return;
     }

//--- Check whether the object extends horizontally and vertically beyond the container boundaries
   bool modified_horizontal=false;     // Horizontal change flag
   bool modified_vertical  =false;     // Vertical change flag
   
//--- Horizontal cropping
   int new_left = object_left;
   int new_width = this.Width();
//--- If the object extends beyond the container left border
   if(object_left<=container_left)
     {
      int crop_left=container_left-object_left;
      new_left=container_left;
      new_width-=crop_left;
      modified_horizontal=true;
     }
//--- If the object extends beyond the container right border
   if(object_right>=container_right)
     {
      int crop_right=object_right-container_right;
      new_width-=crop_right;
      modified_horizontal=true;
     }
//--- If there were changes horizontally
   if(modified_horizontal)
     {
      this.ObjectSetX(new_left);
      this.ObjectResizeW(new_width);
     }

//--- Vertical cropping
   int new_top=object_top;
   int new_height=this.Height();
//--- If the object extends beyond the top edge of the container
   if(object_top<=container_top)
     {
      int crop_top=container_top-object_top;
      new_top=container_top;
      new_height-=crop_top;
      modified_vertical=true;
     }
//--- If the object extends beyond the bottom border of the container
   if(object_bottom>=container_bottom)
     {
      int crop_bottom=object_bottom-container_bottom;
      new_height-=crop_bottom;
      modified_vertical=true;
     }
//--- If there were vertical changes
   if(modified_vertical)
     {
      this.ObjectSetY(new_top);
      this.ObjectResizeH(new_height);
     }

//--- After calculations, the object may be hidden, but is now in the container area - display it
   this.Show(false);

//--- If the object has been changed, redraw it
   if(modified_horizontal || modified_vertical)
     {
      this.Update(false);
      this.Draw(false);
     }
  }

Este é um método virtual, o que significa que ele pode ser sobrescrito nas classes herdadas. A lógica do método está detalhada nos comentários do código. Qualquer elemento gráfico, durante algumas de suas transformações, como movimentação, alteração de tamanho e assim por diante, é sempre verificado quanto à ultrapassagem dos limites de seu contêiner. Caso o objeto não possua um contêiner, o recorte não é realizado.

Método que define a coordenada X do objeto gráfico

//+------------------------------------------------------------------+
//| CCanvasBase::Set the X coordinate of the graphical object        |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetX(const int x)
  {
//--- If an existing coordinate is passed, return 'true'
   if(this.ObjectX()==x)
      return true;
//--- If failed to set a new coordinate in the background and foreground graphical objects, return 'false'
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_XDISTANCE,x) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_XDISTANCE,x))
      return false;
//--- Set the new coordinate to the variable and return 'true'
   this.m_obj_x=x;
   return true;
  }

O método retorna true somente quando a coordenada é definida com sucesso para os dois objetos gráficos, o canvas de fundo e o canvas de primeiro plano.

Método que define a coordenada Y do objeto gráfico

//+------------------------------------------------------------------+
//| CCanvasBase::Set the Y coordinate of the graphical object        |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetY(const int y)
  {
//--- If an existing coordinate is passed, return 'true'
   if(this.ObjectY()==y)
      return true;
//--- If failed to set a new coordinate in the background and foreground graphical objects, return 'false'
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_YDISTANCE,y) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_YDISTANCE,y))
      return false;
//--- Set the new coordinate to the variable and return 'true'
   this.m_obj_y=y;
   return true;
  }

O método é idêntico ao analisado acima.

Método que altera a largura do objeto gráfico

//+------------------------------------------------------------------+
//| CCanvasBase::Change the graphical object width                   |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResizeW(const int size)
  {
//--- If an existing width is passed, return 'true'
   if(this.ObjectWidth()==size)
      return true;
//--- If the passed size is greater than 0, return the result of changing the background and foreground widths, otherwise - 'false'
   return(size>0 ? (this.m_background.Resize(size,this.ObjectHeight()) && this.m_foreground.Resize(size,this.ObjectHeight())) : false);
  }

Apenas larguras com valor maior que zero são aceitas para processamento. O método retorna true somente no caso de alteração bem-sucedida da largura do canvas de fundo e do canvas de primeiro plano.

Método que altera a altura do objeto gráfico

//+------------------------------------------------------------------+
//| CCanvasBase::Change the graphical object height                  |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResizeH(const int size)
  {
//--- If an existing height is passed, return 'true'
   if(this.ObjectHeight()==size)
      return true;
//--- If the passed size is greater than 0, return the result of changing the background and foreground heights, otherwise - 'false'
   return(size>0 ? (this.m_background.Resize(this.ObjectWidth(),size) && this.m_foreground.Resize(this.ObjectWidth(),size)) : false);
  }

O método é idêntico ao analisado acima.

Método que altera o tamanho do objeto gráfico

//+------------------------------------------------------------------+
//| CCanvasBase::Change the graphical object size                    |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResize(const int w,const int h)
  {
   if(!this.ObjectResizeW(w))
      return false;
   return this.ObjectResizeH(h);
  }

Aqui são chamados, de forma sequencial, os métodos de alteração de largura e altura. Retorna true apenas no caso de alteração bem-sucedida tanto da largura quanto da altura.

Método que define novas coordenadas X e Y para o objeto

//+------------------------------------------------------------------+
//| CCanvasBase::Set new X and Y object coordinates                  |
//+------------------------------------------------------------------+
bool CCanvasBase::Move(const int x,const int y)
  {
   if(!this.ObjectMove(x,y))
      return false;
   this.BoundMove(x,y);
   this.ObjectTrim();
   return true;
  }

Inicialmente, os objetos gráficos de fundo e de primeiro plano são deslocados para as coordenadas especificadas. Em caso de sucesso na definição das novas coordenadas para os objetos gráficos, essas mesmas coordenadas são atribuídas ao próprio objeto, após o que é verificado se o objeto não ultrapassou os limites do contêiner, e então é retornado true.

Método que define uma nova coordenada X para o objeto

//+------------------------------------------------------------------+
//| CCanvasBase::Set the object new X coordinate                     |
//+------------------------------------------------------------------+
bool CCanvasBase::MoveX(const int x)
  {
   return this.Move(x,this.ObjectY());
  }

Método auxiliar que define apenas a coordenada horizontal. A coordenada vertical permanece a atual.

Método que define uma nova coordenada Y para o objeto

//+------------------------------------------------------------------+
//| CCanvasBase::Set the object new Y coordinate                     |
//+------------------------------------------------------------------+
bool CCanvasBase::MoveY(const int y)
  {
   return this.Move(this.ObjectX(),y);
  }

Método auxiliar que define apenas a coordenada vertical. A coordenada horizontal permanece a atual.

Método que desloca o objeto nos eixos X e Y pelo deslocamento especificado

//+--------------------------------------------------------------------------------+
//| CCanvasBase::Offset the object along the X and Y axes by the specified offset  |
//+--------------------------------------------------------------------------------+
bool CCanvasBase::Shift(const int dx,const int dy)
  {
   if(!this.ObjectShift(dx,dy))
      return false;
   this.BoundShift(dx,dy);
   this.ObjectTrim();
   return true;
  }

Diferentemente do método Move, que define as coordenadas de tela do objeto, no método Shift é especificado um deslocamento local em quantidade de pixels em relação às coordenadas de tela do objeto. Primeiro são deslocados os objetos gráficos de fundo e de primeiro plano e, em seguida, esses mesmos deslocamentos são atribuídos ao próprio objeto. Depois disso, é verificada a ultrapassagem dos limites do contêiner e é retornado true.

Método que desloca o objeto ao longo do eixo X pelo deslocamento especificado

//+-------------------------------------------------------------------------------+
//| CCanvasBase::Offset the object along the X axis by the specified offset       |
//+-------------------------------------------------------------------------------+
bool CCanvasBase::ShiftX(const int dx)
  {
   return this.Shift(dx,0);
  }

Método auxiliar que desloca o objeto apenas na horizontal. A coordenada vertical permanece a atual.

Método que desloca o objeto ao longo do eixo Y pelo deslocamento especificado

//+-------------------------------------------------------------------------------+
//| CCanvasBase::Offset the object along the Y axis by the specified offset       |
//+-------------------------------------------------------------------------------+
bool CCanvasBase::ShiftY(const int dy)
  {
   return this.Shift(0,dy);
  }

Método auxiliar que desloca o objeto apenas na vertical. A coordenada horizontal permanece a atual.

Método que oculta o objeto em todos os períodos do gráfico

//+------------------------------------------------------------------+
//| CCanvasBase::Hide the object on all chart periods                |
//+------------------------------------------------------------------+
void CCanvasBase::Hide(const bool chart_redraw)
  {
//--- If the object is already hidden, leave
   if(this.m_hidden)
      return;
//--- If the visibility change for background and foreground is successfully set
//--- in the chart command queue - set the hidden object flag
   if(::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS) &&
      ::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS)
      ) this.m_hidden=true;
//--- If specified, redraw the chart
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Para ocultar um objeto gráfico no gráfico, é necessário definir, para a propriedade OBJPROP_TIMEFRAMES, o valor OBJ_NO_PERIODS. Se para o objeto de fundo e para o objeto de primeiro plano essa propriedade for definida com sucesso, isto é, colocada na fila de eventos do gráfico, é definido o sinalizador de objeto oculto e, se especificado, o gráfico é redesenhado.

Método que exibe o objeto em todos os períodos do gráfico

//+------------------------------------------------------------------+
//| CCanvasBase::Display an object on all chart periods              |
//+------------------------------------------------------------------+
void CCanvasBase::Show(const bool chart_redraw)
  {
//--- If the object is already visible, leave
   if(!this.m_hidden)
      return;
//--- If the visibility change for background and foreground is successfully set
//--- in the chart command queue - reset the hidden object flag
   if(::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS) &&
      ::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS)
      ) this.m_hidden=false;
//--- If specified, redraw the chart
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Para exibir um objeto gráfico no gráfico, é necessário definir, para a propriedade OBJPROP_TIMEFRAMES, o valor OBJ_ALL_PERIODS. Se para o objeto de fundo e para o objeto de primeiro plano essa propriedade for definida com sucesso, isto é, colocada na fila de eventos do gráfico, o sinalizador de objeto oculto é removido e, se especificado, o gráfico é redesenhado.

Método que coloca o objeto em primeiro plano

//+------------------------------------------------------------------+
//| CCanvasBase::Bring an object to the foreground                   |
//+------------------------------------------------------------------+
void CCanvasBase::BringToTop(const bool chart_redraw)
  {
   this.Hide(false);
   this.Show(chart_redraw);
  }

Para posicionar um objeto gráfico no gráfico acima de todos os demais, é necessário ocultar o objeto e, em seguida, exibi-lo novamente, exatamente o que este método realiza.

Métodos de gerenciamento das cores do objeto em seus diferentes estados

//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to default state                                                 |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToDefault(void)
  {
   bool res=true;
   res &=this.BackColorToDefault();
   res &=this.ForeColorToDefault();
   res &=this.BorderColorToDefault();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to on-hover state                                                |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToFocused(void)
  {
   bool res=true;
   res &=this.BackColorToFocused();
   res &=this.ForeColorToFocused();
   res &=this.BorderColorToFocused();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to on-click state                                                |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToPressed(void)
  {
   bool res=true;
   res &=this.BackColorToPressed();
   res &=this.ForeColorToPressed();
   res &=this.BorderColorToPressed();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to blocked state                                                 |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToBlocked(void)
  {
   bool res=true;
   res &=this.BackColorToBlocked();
   res &=this.ForeColorToBlocked();
   res &=this.BorderColorToBlocked();
   return res;
  }

O elemento gráfico possui três partes, cujas cores são definidas separadamente:

  • cor de fundo, 
  • cor do texto,
  • cor da borda.

Esses três elementos podem alterar sua cor dependendo do estado do elemento. Esses estados podem ser:

  • estado normal do elemento gráfico,
  • quando o cursor é posicionado sobre o elemento, foco,
  • quando o elemento é pressionado com o mouse, clique,
  • elemento bloqueado.

A cor de cada elemento individual, fundo, texto e borda, pode ser definida e aplicada separadamente. No entanto, normalmente esses três componentes alteram suas cores de forma síncrona, de acordo com o estado do elemento durante a interação com o usuário.

Os métodos analisados acima permitem definir simultaneamente, para os três elementos, as cores do objeto em seus diferentes estados.

Método que bloqueia o elemento

//+------------------------------------------------------------------+
//| CCanvasBase::Block the element                                   |
//+------------------------------------------------------------------+
void CCanvasBase::Block(const bool chart_redraw)
  {
//--- If the element has already been blocked, leave
   if(this.m_blocked)
      return;
//--- Set the current colors as the colors of the blocked element, 
//--- redraw the object and set the block flag
   this.ColorsToBlocked();
   this.Draw(chart_redraw);
   this.m_blocked=true;
  }

Ao bloquear o elemento, são definidas para ele as cores do estado bloqueado, o objeto é redesenhado para exibir as novas cores e o sinalizador de bloqueio é ativado.

Método que desbloqueia o elemento

//+------------------------------------------------------------------+
//| CCanvasBase::Unblock the element                                 |
//+------------------------------------------------------------------+
void CCanvasBase::Unblock(const bool chart_redraw)
  {
//--- If the element has already been unblocked, leave
   if(!this.m_blocked)
      return;
//--- Set the current colors as the colors of the element in its normal state, 
//--- redraw the object and reset the block flag
   this.ColorsToDefault();
   this.Draw(chart_redraw);
   this.m_blocked=false;
  }

Ao desbloquear o elemento, são definidas para ele as cores do estado normal, o objeto é redesenhado para exibir as novas cores e o sinalizador de bloqueio é removido.

Método que preenche o objeto com a cor especificada

//+------------------------------------------------------------------+
//| CCanvasBase::Fill an object with the specified color             |
//| with transparency set to                                         |
//+------------------------------------------------------------------+
void CCanvasBase::Fill(const color clr,const bool chart_redraw)
  {
   this.m_background.Erase(::ColorToARGB(clr,this.m_alpha));
   this.m_background.Update(chart_redraw);
  }

Em alguns casos, é necessário preencher completamente o fundo do objeto com uma determinada cor. O método preenche o fundo do objeto com a cor, sem afetar o primeiro plano. Para o preenchimento, é utilizada a transparência previamente definida na variável de classe m_alpha. Em seguida, o canvas é atualizado para fixar as alterações com o sinalizador de redesenho do gráfico.

Se o sinalizador estiver ativado, as alterações serão exibidas imediatamente após a atualização do canvas. Com o sinalizador de atualização do gráfico desativado, a aparência do objeto será atualizada либо com um novo tick, ou em qualquer chamada subsequente do comando de atualização do gráfico. Isso é necessário quando vários objetos são recoloridos simultaneamente. O sinalizador de redesenho deve estar ativado apenas no último objeto a ser recolorido.

Essa lógica, de modo geral, aplica-se a todos os casos de alteração de objetos gráficos, seja um elemento único, atualizado imediatamente após sua modificação, seja um processamento em lote de vários elementos, em que o redesenho do gráfico é necessário apenas após a alteração do último objeto gráfico.

Método que preenche o objeto com cor transparente

//+------------------------------------------------------------------+
//| CCanvasBase::Fill an object with transparent color               |
//+------------------------------------------------------------------+
void CCanvasBase::Clear(const bool chart_redraw)
  {
   this.m_background.Erase(clrNULL);
   this.m_foreground.Erase(clrNULL);
   this.Update(chart_redraw);
  }

O canvas de fundo e o canvas de primeiro plano são preenchidos com uma cor transparente, e ambos os objetos são atualizados com a indicação do sinalizador de redesenho do gráfico.

Método que atualiza o objeto para exibição das alterações

//+------------------------------------------------------------------+
//| CCanvasBase::Update the object to display the changes            |
//+------------------------------------------------------------------+
void CCanvasBase::Update(const bool chart_redraw)
  {
   this.m_background.Update(false);
   this.m_foreground.Update(chart_redraw);
  }

O canvas de fundo é atualizado sem redesenhar o gráfico, e, ao atualizar o canvas de primeiro plano, é utilizado o valor especificado do sinalizador de redesenho do gráfico. Isso permite atualizar simultaneamente ambos os objetos CCanvas, controlando ao mesmo tempo o redesenho do gráfico para múltiplos objetos, por meio da indicação do sinalizador de redesenho no método.

Método que desenha a aparência externa

//+------------------------------------------------------------------+
//| CCanvasBase::Draw the appearance                                 |
//+------------------------------------------------------------------+
void CCanvasBase::Draw(const bool chart_redraw)
  {
   return;
  }

Este é um método virtual. Sua implementação deve ser realizada nas classes herdadas. Aqui, esse método não faz nada, pois para o objeto base não deve haver qualquer desenho no gráfico, trata-se apenas de um objeto base a partir do qual são criados os elementos de controle.

Método que destrói o objeto

//+------------------------------------------------------------------+
//| CCanvasBase::Destroy the object                                  |
//+------------------------------------------------------------------+
void CCanvasBase::Destroy(void)
  {
   this.m_background.Destroy();
   this.m_foreground.Destroy();
  }

Ambos os canvases são destruídos por meio dos métodos Destroy da classe CCanvas.

Método que retorna a descrição do objeto

//+------------------------------------------------------------------+
//| CCanvasBase::Return the object description                       |
//+------------------------------------------------------------------+
string CCanvasBase::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   string area=::StringFormat("x %d, y %d, w %d, h %d",this.X(),this.Y(),this.Width(),this.Height());
   return ::StringFormat("%s%s (%s, %s): ID %d, %s",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.NameBG(),this.NameFG(),this.ID(),area);
  }

Retorna a descrição do objeto com a descrição definida para ele, o identificador, os nomes dos objetos gráficos de fundo e de primeiro plano, bem como com a indicação das coordenadas e dimensões do objeto no formato:

Canvas Base "Rectangle 1" (TestScr1_BG, TestScr1_FG): ID 1, x 100, y 40, w 100, h 100
Canvas Base "Rectangle 2" (TestScr2_BG, TestScr2_FG): ID 2, x 110, y 50, w 80, h 80

Método que imprime no diário a descrição do objeto

//+------------------------------------------------------------------+
//| CCanvasBase::Display the object description in the journal       |
//+------------------------------------------------------------------+
void CCanvasBase::Print(void)
  {
   ::Print(this.Description());
  }

Imprime no diário a descrição do objeto retornada pelo método Description.

Os métodos de salvamento do elemento gráfico em arquivo e de carregamento a partir de arquivo ainda não estão implementados no momento, foi apenas criada uma estrutura base para eles:

//+------------------------------------------------------------------+
//| CCanvasBase::Save to file                                        |
//+------------------------------------------------------------------+
bool CCanvasBase::Save(const int file_handle)
  {
//--- Method temporarily disabled
   return false;
   
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;

/*
//--- Store the properties
      
*/
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| Load from file                                                   |
//+------------------------------------------------------------------+
bool CCanvasBase::Load(const int file_handle)
  {
//--- Method temporarily disabled
   return false;
   
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;

/*
//--- Load properties
   
*/
//--- All is successful
   return true;
  }

Como esta é a primeira versão do objeto base, e muito provavelmente ele ainda será aprimorado, os métodos de trabalho com arquivos também ainda não foram implementados. Durante o aprimoramento dessa classe, a adição de novas propriedades e a otimização das existentes, será necessário, simultaneamente, introduzir alterações também nos métodos de trabalho com arquivos. Para evitar trabalho desnecessário, deixaremos a implementação dos métodos Save e Load para o momento da conclusão total dos trabalhos sobre o objeto base dos elementos gráficos.

Agora temos tudo pronto para os testes.



Testando o resultado

Para testar o funcionamento da classe, criaremos dois objetos, um sobre o outro. O primeiro objeto atuará como contêiner para o segundo. Já o segundo objeto será deslocado programaticamente dentro do elemento pai em todas as direções possíveis. Isso nos permitirá compreender a correção do funcionamento dos métodos de deslocamento, de alteração de tamanho dos elementos e do recorte do elemento filho pelos limites do contêiner. Antes de concluir o trabalho, definiremos para o segundo objeto o sinalizador de elemento bloqueado, para verificar como isso funciona.

Mas há um "porém". No objeto base, o método Draw não faz nada, e simplesmente não veremos o funcionamento da classe, pois os objetos criados serão completamente transparentes.

Faremos o seguinte: o primeiro objeto simplesmente será preenchido com uma cor e terá uma borda desenhada. Como ele não se desloca nem altera suas dimensões, não há necessidade de redesenhá-lo, basta desenhar algo nele uma única vez após sua criação. Já para o segundo objeto, é necessário atualizar constantemente sua aparência, pois o método ObjectTrim() chama o método de redesenho do objeto. Porém, nesta classe, esse método não faz nada. Portanto, temporariamente faremos uma modificação no método Draw para que algo seja desenhado no objeto:

//+------------------------------------------------------------------+
//| CCanvasBase::Draw the appearance                                 |
//+------------------------------------------------------------------+
void CCanvasBase::Draw(const bool chart_redraw)
  {
   //return;
   Fill(BackColor(),false);
   m_background.Rectangle(this.AdjX(0),this.AdjY(0),AdjX(this.Width()-1),AdjY(this.Height()-1),ColorToARGB(this.BorderColor()));
   
   m_foreground.Erase(clrNULL);
   m_foreground.TextOut(AdjX(6),AdjY(6),StringFormat("%dx%d (%dx%d)",this.Width(),this.Height(),this.ObjectWidth(),this.ObjectHeight()),ColorToARGB(this.ForeColor()));
   m_foreground.TextOut(AdjX(6),AdjY(16),StringFormat("%dx%d (%dx%d)",this.X(),this.Y(),this.ObjectX(),this.ObjectY()),ColorToARGB(this.ForeColor()));
   
   Update(chart_redraw);
  }

Aqui, para o canvas de fundo, preenchimos o fundo com a cor de fundo definida e desenhamos a borda com a cor de borda definida.

Para o canvas de primeiro plano, nós o limpamos e exibimos dois textos, um abaixo do outro, com a cor de texto definida:

  1. com a largura/altura do objeto e, entre parênteses, a largura/altura dos objetos gráficos de fundo e de primeiro plano;
  2. com as coordenadas X/Y do objeto e, entre parênteses, as coordenadas X/Y dos objetos gráficos de fundo e de primeiro plano.

Após os testes, removeremos esse código do método.

Na pasta \MQL5\Scripts\Tables\ criaremos o arquivo do script de teste TestControls.mq5:

//+------------------------------------------------------------------+
//|                                                 TestControls.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Include libraries                                                |
//+------------------------------------------------------------------+
#include "Controls\Base.mqh"
  
CCanvasBase *obj1=NULL;       // Pointer to the first graphical element
CCanvasBase *obj2=NULL;       // Pointer to the second graphical element
  
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Create the first graphical element
   obj1=new CCanvasBase(0,0,"TestScr1",100,40,160,160);
   obj1.SetAlpha(250);        // Transparency
   obj1.SetBorderWidth(6);    // Frame width
//--- Fill the background with color and draw a frame with an indent of one pixel from the set frame width
   obj1.Fill(clrDodgerBlue,false);
   uint wd=obj1.BorderWidth();
   obj1.GetBackground().Rectangle(wd-2,wd-2,obj1.Width()-wd+1,obj1.Height()-wd+1,ColorToARGB(clrWheat));
   obj1.Update(false);
//--- set the name and ID of the element and display its description in the journal
   obj1.SetName("Rectangle 1");
   obj1.SetID(1);
   obj1.Print();

//--- Create a second element inside the first one, set its transparency
//--- and specify the first element as a container for the second one
   int shift=10;
   int x=obj1.X()+shift;
   int y=obj1.Y()+shift;
   int w=obj1.Width()-shift*2;
   int h=obj1.Height()-shift*2;
   obj2=new CCanvasBase(0,0,"TestScr2",x,y,w,h);
   obj2.SetAlpha(250);
   obj2.SetContainerObj(obj1);

//--- Initialize the background color, specify the color for the blocked element
//--- and set the default background color of the element as the current color 
   obj2.InitBackColors(clrLime);
   obj2.InitBackColorBlocked(clrLightGray);
   obj2.BackColorToDefault();

//--- Initialize the foreground color, specify the color for the blocked element
//--- and set the default text color of the element as the current foreground color 
   obj2.InitForeColors(clrBlack);
   obj2.InitForeColorBlocked(clrDimGray);
   obj2.ForeColorToDefault();

//--- Initialize the frame color, specify the color for the blocked element
//--- and set the default frame color of the element as the current color 
   obj2.InitBorderColors(clrBlue);
   obj2.InitBorderColorBlocked(clrSilver);
   obj2.BorderColorToDefault();
//--- Set the element name and ID,
//--- display its description in the journal and draw the element
   obj2.SetName("Rectangle 2");
   obj2.SetID(2);
   obj2.Print();
   obj2.Draw(true);
   
//--- Check if the element is clipped by its container boundaries
   int ms=1;         // Offset delay in milliseconds
   int total=obj1.Width()-shift; // Number of offset loop iterations
   
//--- Wait a second and move the inner object beyond the left edge of the container
   Sleep(1000);
   ShiftHorisontal(-1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftHorisontal(1,total,ms);

//--- Wait a second and move the inner object beyond the right edge of the container
   Sleep(1000);
   ShiftHorisontal(1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftHorisontal(-1,total,ms);

   
//--- Wait a second and move the inner object beyond the top edge of the container
   Sleep(1000);
   ShiftVertical(-1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftVertical(1,total,ms);
     
//--- Wait a second and move the inner object beyond the bottom edge of the container
   Sleep(1000);
   ShiftVertical(1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftVertical(-1,total,ms);

//--- Wait a second and set the blocked element flag for the inside object
   Sleep(1000);
   obj2.Block(true);

//--- Clean up in three seconds before finishing work
   Sleep(3000);
   delete obj1;
   delete obj2;
  }
//+------------------------------------------------------------------+
//| Shift the object horizontally                                    |
//+------------------------------------------------------------------+
void ShiftHorisontal(const int dx, const int total, const int delay)
  {
   for(int i=0;i<total;i++)
     {
      if(obj2.ShiftX(dx))
         ChartRedraw();
      Sleep(delay);
     }
  }
//+------------------------------------------------------------------+
//| Shift the object vertically                                      |
//+------------------------------------------------------------------+
void ShiftVertical(const int dy, const int total, const int delay)
  {
   for(int i=0;i<total;i++)
     {
      if(obj2.ShiftY(dy))
         ChartRedraw();
      Sleep(delay);
     }
  }
//+------------------------------------------------------------------+

O código do script está detalhadamente comentado. Toda a sua lógica pode ser facilmente compreendida a partir dos comentários apresentados.

Compilamos e executamos o script no gráfico:

Vemos que o objeto filho é corretamente recortado pelos limites da área do contêiner, não pelas bordas do objeto, mas com um recuo correspondente à largura da borda, e que o bloqueio do objeto provoca seu redesenho nas cores do elemento bloqueado.

Pequenos "trancos" durante os deslocamentos do objeto aninhado no contêiner são causados por defeitos na gravação da imagem GIF, e não por atrasos na execução dos métodos da classe.

Após a execução do script, duas linhas com a descrição dos dois objetos criados serão exibidas no diário:

Canvas Base "Rectangle 1" (TestScr1_BG, TestScr1_FG): ID 1, x 100, y 40, w 160, h 160
Canvas Base "Rectangle 2" (TestScr2_BG, TestScr2_FG): ID 2, x 110, y 50, w 140, h 140


Considerações finais

Hoje lançamos a base para a criação de quaisquer elementos gráficos, onde cada classe executa uma tarefa claramente definida, o que torna a arquitetura modular e facilmente extensível.

As classes implementadas estabelecem uma base sólida para a criação de elementos gráficos complexos e sua integração com os componentes Model e Controller no paradigma MVC.

A partir do próximo artigo, iniciaremos a criação de todos os elementos necessários para a construção de tabelas e o gerenciamento delas. Como na linguagem MQL o modelo de eventos é integrado aos objetos criados por meio dos eventos do gráfico, em todos os elementos de controle subsequentes será organizada a обработка de eventos para implementar a ligação do componente View com o componente Controller.

Programas utilizados neste artigo:

#
 Nome Tipo
Descrição
 1  Base.mqh  Biblioteca de classes  Classes para a criação do objeto base dos elementos de controle
 2  TestControls.mq5  Script de teste  Script para testar o funcionamento da classe do objeto base
 3  MQL5.zip  Arquivo compactado  Arquivo com os arquivos apresentados acima, para extração no diretório MQL5 do terminal cliente

Classes que compõem a biblioteca Base.mqh:

#
Nome
 Descrição
 1  CBaseObj  Classe base para todos os objetos gráficos
 2  Cor  Classe para gerenciamento de cores
 3  CColorElement  Classe para gerenciamento das cores dos diferentes estados do elemento gráfico
 4  CBound  Classe para gerenciamento de área retangular
 5  CCanvasBase  Classe base para trabalhar com elementos gráficos no canvas
Todos os arquivos criados são anexados ao artigo para estudo individual. O arquivo compactado pode ser extraído diretamente na pasta do terminal, e todos os arquivos serão posicionados corretamente na pasta: MQL5\Scripts\Tables.

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

Arquivos anexados |
Base.mqh (139.67 KB)
TestControls.mq5 (10.86 KB)
MQL5.zip (15.56 KB)
Análise quantitativa de tendências: coletando estatísticas em Python Análise quantitativa de tendências: coletando estatísticas em Python
O que é a análise quantitativa de tendências no mercado Forex. Coletando estatísticas sobre as tendências, sua magnitude e distribuição no par de moedas EURUSD. Como a análise quantitativa de tendências ajuda a criar um EA lucrativo.
Automatizando Estratégias de Trading em MQL5 (Parte 4): Construindo um Sistema de Recuperação por Zonas em Múltiplos Níveis Automatizando Estratégias de Trading em MQL5 (Parte 4): Construindo um Sistema de Recuperação por Zonas em Múltiplos Níveis
Neste artigo, desenvolvemos um Sistema de Recuperação por Zonas em Múltiplos Níveis em MQL5 que utiliza o RSI para gerar sinais de negociação. Cada instância de sinal é adicionada dinamicamente a uma estrutura de array, permitindo que o sistema gerencie múltiplos sinais simultaneamente dentro da lógica de Zone Recovery. Por meio dessa abordagem, demonstramos como lidar de forma eficaz com cenários complexos de gerenciamento de trades, mantendo ao mesmo tempo um design de código escalável e robusto.
Redes neurais em trading: Previsão de séries temporais com o auxílio da decomposição modal adaptativa (Conclusão) Redes neurais em trading: Previsão de séries temporais com o auxílio da decomposição modal adaptativa (Conclusão)
O artigo analisa a adaptação e a implementação prática do framework ACEFormer por meio do MQL5 no contexto do trading algorítmico. São apresentados as principais decisões arquiteturais, as particularidades do treinamento e os resultados dos testes do modelo com dados reais.
Integrar seu próprio LLM em EA (Parte 5): Desenvolver e testar estratégia de trading com LLMs (IV) — Testar estratégia de trading Integrar seu próprio LLM em EA (Parte 5): Desenvolver e testar estratégia de trading com LLMs (IV) — Testar estratégia de trading
Com o rápido desenvolvimento da inteligência artificial atualmente, os modelos de linguagem (LLMs) são uma parte importante da inteligência artificial, portanto devemos pensar em como integrar LLMs poderosos ao nosso trading algorítmico. Para a maioria das pessoas, é difícil ajustar esses modelos poderosos de acordo com suas necessidades, implantá-los localmente e, em seguida, aplicá-los ao trading algorítmico. Esta série de artigos adotará uma abordagem passo a passo para alcançar esse objetivo.