English Русский
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 |
25 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 <Canvas\Canvas.mqh>              // Класс СБ CCanvas
#include <Arrays\List.mqh>                // Класс СБ CList

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define  clrNULL              0x00FFFFFF  // Прозрачный цвет для CCanvas
#define  MARKER_START_DATA    -1          // Маркер начала данных в файле

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_TYPE                    // Перечисление типов графических элементов
  {
   ELEMENT_TYPE_BASE = 0x10000,           // Базовый объект графических элементов
   ELEMENT_TYPE_COLOR,                    // Объект цвета
   ELEMENT_TYPE_COLORS_ELEMENT,           // Объект цветов элемента графического объекта
   ELEMENT_TYPE_RECTANGLE_AREA,           // Прямоугольная область элемента
   ELEMENT_TYPE_CANVAS_BASE,              // Базовый объект холста графических элементов
  };

enum ENUM_COLOR_STATE                     // Перечисление цветов состояний элемента
  {
   COLOR_STATE_DEFAULT,                   // Цвет обычного состояния
   COLOR_STATE_FOCUSED,                   // Цвет при наведении курсора на элемент
   COLOR_STATE_PRESSED,                   // Цвет при нажатии на элемент
   COLOR_STATE_BLOCKED,                   // Цвет заблокированного элемента
  };
//+------------------------------------------------------------------+ 
//| Функции                                                          |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Возвращает тип элемента как строку                              |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Классы                                                           |
//+------------------------------------------------------------------+

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:

//+------------------------------------------------------------------+
//| Базовый класс графических элементов                              |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   int               m_id;                                     // Идентифткатор
   ushort            m_name[];                                 // Наименование
   
public:
//--- Устанавливает (1) наименование, (2) идентификатор
   void              SetName(const string name)                { ::StringToShortArray(name,this.m_name);    }
   void              SetID(const int id)                       { this.m_id=id;                              }
//--- Возвращает (1) наименование, (2) идентификатор
   string            Name(void)                          const { return ::ShortArrayToString(this.m_name);  }
   int               ID(void)                            const { return this.m_id;                          }

//--- Виртуальные методы (1) сравнения, (2) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BASE); }
   
//--- Конструкторы/деструктор
                     CBaseObj (void) : m_id(-1) {}
                    ~CBaseObj (void) {}
  };
//+------------------------------------------------------------------+
//| CBaseObj::Сравнение двух объектов                                |
//+------------------------------------------------------------------+
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

//+------------------------------------------------------------------+
//| Класс цвета                                                      |
//+------------------------------------------------------------------+
class CColor : public CBaseObj
  {
protected:
   color             m_color;                                  // Цвет
   
public:
//--- Устанавливает цвет
   bool              SetColor(const color clr)
                       {
                        if(this.m_color==clr)
                           return false;
                        this.m_color=clr;
                        return true;
                       }
//--- Возвращает цвет
   color             Get(void)                           const { return this.m_color;              }

//--- (1) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(void);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   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);       }
   
//--- Конструкторы/деструктор
                     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::Возвращает описание объекта                              |
//+------------------------------------------------------------------+
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::Выводит в журнал описание объекта                        |
//+------------------------------------------------------------------+
void CColor::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CColor::Сохранение в файл                                        |
//+------------------------------------------------------------------+
bool CColor::Save(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;

//--- Сохраняем цвет
   if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем идентификатор
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем наименование
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CColor::Загрузка из файла                                        |
//+------------------------------------------------------------------+
bool CColor::Load(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;

//--- Загружаем цвет
   this.m_color=(color)::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем идентификатор
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем наименование
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   
//--- Всё успешно
   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

//+------------------------------------------------------------------+
//| Класс цветов элемента графического объекта                       |
//+------------------------------------------------------------------+
class CColorElement : public CBaseObj
  {
protected:
   CColor            m_current;                                // Текущий цвет. Может быть одним из нижеследующих
   CColor            m_default;                                // Цвет обычного состояния
   CColor            m_focused;                                // Цвет при наведении курсора
   CColor            m_pressed;                                // Цвет при нажатии
   CColor            m_blocked;                                // Цвет заблокированного элемента
   
//--- Преобразует RGB в color
   color             RGBToColor(const double r,const double g,const double b) const;
//--- Записывает в переменные значения компонентов RGB
   void              ColorToRGB(const color clr,double &r,double &g,double &b);
//--- Возвращает составляющую цвета (1) Red, (2) Green, (3) Blue
   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:
//--- Возвращает новый цвет
   color             NewColor(color base_color, int shift_red, int shift_green, int shift_blue);

//--- Инициализация цветов различных состояний
   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);       }
   
//--- Установка цветов для всех состояний
   void              InitColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked);
   void              InitColors(const color clr);
    
//--- Возврат цветов различных состояний
   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();               }
   
//--- Устанавливает один из списка цветов как текущий
   bool              SetCurrentAs(const ENUM_COLOR_STATE color_state);

//--- (1) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(void);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   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);       }
   
//--- Конструкторы/деструктор
                     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::Конструктор с установкой прозрачных цветов объекта|
//+------------------------------------------------------------------+
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::Конструктор с указанием цветов объекта            |
//+------------------------------------------------------------------+
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::Конструктор с указанием цвета объекта             |
//+------------------------------------------------------------------+
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::Устанавливает цвета для всех состояний            |
//+------------------------------------------------------------------+
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::Устанавливает цвета для всех состояний по текущему|
//+------------------------------------------------------------------+
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::Устанавливает один цвет из списка цветов как текущий|
//+-------------------------------------------------------------------+
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::Преобразует RGB в 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::Получение значений компонентов RGB                |
//+------------------------------------------------------------------+
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::Возвращает цвет с новой цветовой составляющей     |
//+------------------------------------------------------------------+
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::Возвращает описание объекта                       |
//+------------------------------------------------------------------+
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::Выводит в журнал описание объекта                 |
//+------------------------------------------------------------------+
void CColorElement::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CColorElement::Сохранение в файл                                 |
//+------------------------------------------------------------------+
bool CColorElement::Save(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Сохраняем идентификатор
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем наименование цветов элемента
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
//--- Сохраняем текущий цвет
   if(!this.m_current.Save(file_handle))
      return false;
//--- Сохраняем цвет обычного состояния
   if(!this.m_default.Save(file_handle))
      return false;
//--- Сохраняем цвет при наведении курсора
   if(!this.m_focused.Save(file_handle))
      return false;
//--- Сохраняем цвет при нажатии
   if(!this.m_pressed.Save(file_handle))
      return false;
//--- Сохраняем цвет заблокированного элемента
   if(!this.m_blocked.Save(file_handle))
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CColorElement::Загрузка из файла                                 |
//+------------------------------------------------------------------+
bool CColorElement::Load(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;
   
//--- Загружаем идентификатор
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем наименование цветов элемента
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
//--- Загружаем текущий цвет
   if(!this.m_current.Load(file_handle))
      return false;
//--- Загружаем цвет обычного состояния
   if(!this.m_default.Load(file_handle))
      return false;
//--- Загружаем цвет при наведении курсора
   if(!this.m_focused.Load(file_handle))
      return false;
//--- Загружаем цвет при нажатии
   if(!this.m_pressed.Load(file_handle))
      return false;
//--- Загружаем цвет заблокированного элемента
   if(!this.m_blocked.Load(file_handle))
      return false;
   
//--- Всё успешно
   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.

//+------------------------------------------------------------------+
//| Класс прямоугольной области                                      |
//+------------------------------------------------------------------+
class CBound : public CBaseObj
  {
protected:
   CRect             m_bound;                                  // Структура прямоугольной области

public:
//--- Изменяет (1) ширину, (2) высоту, (3) размер ограничивающего прямоугольника
   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);   }
   
//--- Устанавливает координату (1) X, (2) Y, (3) обе координаты ограничивающего прямоугольника
   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) Устанавливает, (2) смещает ограничивающий прямоугольник на указанные координаты/размер смещения
   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);                       }
   
//--- Возвращает координаты, размеры и границы объекта
   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) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(void);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   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);             }
   
//--- Конструкторы/деструктор
                     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::Возвращает описание объекта                              |
//+------------------------------------------------------------------+
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::Выводит в журнал описание объекта                        |
//+------------------------------------------------------------------+
void CBound::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CBound::Сохранение в файл                                        |
//+------------------------------------------------------------------+
bool CBound::Save(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Сохраняем идентификатор
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем наименование
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   //--- Сохраняем структуру области
   if(::FileWriteStruct(file_handle,this.m_bound)!=sizeof(this.m_bound))
      return(false);
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CBound::Загрузка из файла                                        |
//+------------------------------------------------------------------+
bool CBound::Load(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;
   
//--- Загружаем идентификатор
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем наименование
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   //--- Загружаем структуру области
   if(::FileReadStruct(file_handle,this.m_bound)!=sizeof(this.m_bound))
      return(false);
   
//--- Всё успешно
   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

//+------------------------------------------------------------------+
//| Базовый класс холста графических элементов                       |
//+------------------------------------------------------------------+
class CCanvasBase : public CBaseObj
  {
protected:
   CCanvas           m_background;                             // Канвас для рисования фона
   CCanvas           m_foreground;                             // Канвас для рисования переднего плана
   CBound            m_bound;                                  // Границы объекта
   CCanvasBase      *m_container;                              // Родительский объект-контейнер
   CColorElement     m_color_background;                       // Объект управления цветом фона
   CColorElement     m_color_foreground;                       // Объект управления цветом переднего плана
   CColorElement     m_color_border;                           // Объект управления цветом рамки
   long              m_chart_id;                               // Идентификатор графика
   int               m_wnd;                                    // Номер подокна графика
   int               m_wnd_y;                                  // Смещение координаты Y курсора в подокне
   int               m_obj_x;                                  // Координата X графического объекта
   int               m_obj_y;                                  // Координата Y графического объекта
   uchar             m_alpha;                                  // Прозрачность
   uint              m_border_width;                           // Ширина рамки
   string            m_program_name;                           // Имя программы
   bool              m_hidden;                                 // Флаг скрытого объекта
   bool              m_blocked;                                // Флаг заблокированного элемента
   bool              m_focused;                                // Флаг элемента в фокусе
   
private:
//--- Возврат смещения начальных координат рисования на холсте относительно канваса и координат объекта
   int               CanvasOffsetX(void)                 const { return(this.ObjectX()-this.X());                                                  }
   int               CanvasOffsetY(void)                 const { return(this.ObjectY()-this.Y());                                                  }
//--- Возвращает скорректированную координату точки на холсте с учётом смещения холста относительно объекта
   int               AdjX(const int x)                   const { return(x-this.CanvasOffsetX());                                                   }
   int               AdjY(const int y)                   const { return(y-this.CanvasOffsetY());                                                   }
   
protected:
//--- Возвращает скорректированный идентификатор графика
   long              CorrectChartID(const long chart_id) const { return(chart_id!=0 ? chart_id : ::ChartID());                                     }

//--- Получение границ родительского объекта-контейнера
   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()); }
   
//--- Возврат координат, границ и размеров графического объекта
   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;                                      }
   
//--- Изменяет (1) ширину, (2) высоту, (3) размер ограничивающего прямоугольника
   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);                                                         }
   
//--- Устанавливает координату (1) X, (2) Y, (3) обе координаты ограничивающего прямоугольника
   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) Устанавливает, (2) смещает ограничивающий прямоугольник на указанные координаты/размер смещения
   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);                                                        }
   
//--- Изменяет (1) ширину, (2) высоту, (3) размер графического объекта
   bool              ObjectResizeW(const int size);
   bool              ObjectResizeH(const int size);
   bool              ObjectResize(const int w,const int h);
   
//--- Устанавливает координату (1) X, (2) Y, (3) обе координаты графического объекта
   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) Устанавливает, (2) смещает графический объект на указанные координаты/размер смещения
   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);                     }
   
//--- Ограничивает графический объект по размерам контейнера
   virtual void      ObjectTrim(void);
   
public:
//--- Возвращает указатель на канвас (1) фона, (2) переднего плана
   CCanvas          *GetBackground(void)                       { return &this.m_background;                                                        }
   CCanvas          *GetForeground(void)                       { return &this.m_foreground;                                                        }
   
//--- Возвращает указатель на объект управления цветом (1) фона, (2) переднего плана, (3) рамки
   CColorElement    *GetBackColorControl(void)                 { return &this.m_color_background;                                                  }
   CColorElement    *GetForeColorControl(void)                 { return &this.m_color_foreground;                                                  }
   CColorElement    *GetBorderColorControl(void)               { return &this.m_color_border;                                                      }
   
//--- Возврат цвета (1) фона, (2) переднего плана, (3) рамки
   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();                                          }
   
//--- Установка цветов фона для всех состояний
   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);                                          }

//--- Установка цветов переднего плана для всех состояний
   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);                                          }

//--- Установка цветов рамки для всех состояний
   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);                                              }

//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки начальными значениями
   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);                                             }
   
//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при наведении курсора начальными значениями
   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);                                             }
   
//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при щелчке по объекту начальными значениями
   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);                                             }
   
//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки для заблокированного объекта начальными значениями
   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);                                             }
   
//--- Установка текущего цвета фона в различные состояния
   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);                 }
   
//--- Установка текущего цвета переднего плана в различные состояния
   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);                 }
   
//--- Установка текущего цвета рамки в различные состояния
   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);                     }
   
//--- Установка текущих цветов элемента в различные состояния
   bool              ColorsToDefault(void);
   bool              ColorsToFocused(void);
   bool              ColorsToPressed(void);
   bool              ColorsToBlocked(void);
   
//--- Устанавливает указатель на родительский объект-контейнер
   void              SetContainerObj(CCanvasBase *obj);
   
//--- Создаёт 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);

//--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного элемента, (4) имя графического объекта
   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) Возвращает, (2) устанавливает прозрачность
   uchar             Alpha(void)                         const { return this.m_alpha;                                                              }
   void              SetAlpha(const uchar value)               { this.m_alpha=value;                                                               }
   
//--- (1) Возвращает, (2) устанавливает ширину рамки
    uint             BorderWidth(void)                   const { return this.m_border_width;                                                       } 
    void             SetBorderWidth(const uint width)          { this.m_border_width=width;                                                        }
                      
//--- Возвращает координаты, размеры и границы объекта
   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();                                                     }
   
//--- Устанавливает объекту новую координату (1) X, (2) Y, (3) XY
   bool              MoveX(const int x);
   bool              MoveY(const int y);
   bool              Move(const int x,const int y);
   
//--- Смещает объект по оси (1) X, (2) Y, (3) XY на указанное смещение
   bool              ShiftX(const int dx);
   bool              ShiftY(const int dy);
   bool              Shift(const int dx,const int dy);

//--- Возврат границ объекта с учётом рамки
   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) Скрывает (2) отображает объект на всех периодах графика,
//--- (3) помещает объект на передний план, (4) блокирует, (5) разблокирует элемент,
//--- (6) заливает объект указанным цветом с установленной прозрачностью
   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) Заливает объект прозрачным цветом, (2) обновляет объект для отображения изменений,
//--- (3) рисует внешний вид, (4) уничтожает объект
   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) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(void);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   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); }
   
//--- Конструкторы/деструктор
                     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::Конструктор                                         |
//+------------------------------------------------------------------+
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)
  {
//--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y
//--- между верхней рамкой подокна индикатора и верхней рамкой главного окна графика
   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(this.Create(this.m_chart_id,this.m_wnd,name,x,y,w,h))
     {
      //--- Очищаем канвасы фона и переднего плана и устанавливаем начальные значения координат,
      //--- наименования графических объектов и свойства текста, рисуемого на переднем плане
      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::Деструктор                                          |
//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(void)
  {
   this.Destroy();
  }

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

//+------------------------------------------------------------------+
//| CCanvasBase::Создаёт графические объекты фона и переднего плана  |
//+------------------------------------------------------------------+
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)
  {
//--- Получаем скорректированный идентификатор графика
   long id=this.CorrectChartID(chart_id);
//--- Создаём имя графического объекта для фона и создаём канвас
   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;
     }
//--- Создаём имя графического объекта для переднего плана и создаём канвас
   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;
     }
//--- При успешном создании в свойство графического объекта OBJPROP_TEXT вписываем наименование программы
   ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name);
   ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name);
   
//--- Устанавливаем размеры прямоугольной области и возвращаем 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::Устанавливает указатель                             |
//| на родительский объект-контейнер                                 |
//+------------------------------------------------------------------+
void CCanvasBase::SetContainerObj(CCanvasBase *obj)
  {
//--- Устанавливаем переданный указатель объекту
   this.m_container=obj;
//--- Если указатель пустой - уходим
   if(this.m_container==NULL)
      return;
//--- Если передан невалидный указатель - обнуляем его в объекте и уходим
   if(::CheckPointer(this.m_container)==POINTER_INVALID)
     {
      this.m_container=NULL;
      return;
     }
//--- Обрезаем объект по границам назначенного ему контейнера
   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::Подрезает графический объект по контуру контейнера  |
//+------------------------------------------------------------------+
void CCanvasBase::ObjectTrim()
  {
//--- Получаем границы контейнера
   int container_left   = this.ContainerLimitLeft();
   int container_right  = this.ContainerLimitRight();
   int container_top    = this.ContainerLimitTop();
   int container_bottom = this.ContainerLimitBottom();
   
//--- Получаем текущие границы объекта
   int object_left   = this.X();
   int object_right  = this.Right();
   int object_top    = this.Y();
   int object_bottom = this.Bottom();

//--- Проверяем, полностью ли объект выходит за пределы контейнера и скрываем его, если да
   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;
     }

//--- Проверяем выход объекта по горизонтали и вертикали за пределы контейнера
   bool modified_horizontal=false;     // Флаг изменений по горизонтали
   bool modified_vertical  =false;     // Флаг изменений по вертикали
   
//--- Обрезка по горизонтали
   int new_left = object_left;
   int new_width = this.Width();
//--- Если объект выходит за левую границу контейнера
   if(object_left<=container_left)
     {
      int crop_left=container_left-object_left;
      new_left=container_left;
      new_width-=crop_left;
      modified_horizontal=true;
     }
//--- Если объект выходит за правую границу контейнера
   if(object_right>=container_right)
     {
      int crop_right=object_right-container_right;
      new_width-=crop_right;
      modified_horizontal=true;
     }
//--- Если были изменения по горизонтали
   if(modified_horizontal)
     {
      this.ObjectSetX(new_left);
      this.ObjectResizeW(new_width);
     }

//--- Обрезка по вертикали
   int new_top=object_top;
   int new_height=this.Height();
//--- Если объект выходит за верхнюю границу контейнера
   if(object_top<=container_top)
     {
      int crop_top=container_top-object_top;
      new_top=container_top;
      new_height-=crop_top;
      modified_vertical=true;
     }
//--- Если объект выходит за нижнюю границу контейнера
   if(object_bottom>=container_bottom)
     {
      int crop_bottom=object_bottom-container_bottom;
      new_height-=crop_bottom;
      modified_vertical=true;
     }
//--- Если были изменения по вертикали
   if(modified_vertical)
     {
      this.ObjectSetY(new_top);
      this.ObjectResizeH(new_height);
     }

//--- После рассчётов, объект может быть скрыт, но теперь находится в области контейнера - отображаем его
   this.Show(false);

//--- Если объект был изменен, перерисовываем его
   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::Устанавливает координату X графического объекта     |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetX(const int x)
  {
//--- Если передана существующая координата - возвращаем true
   if(this.ObjectX()==x)
      return true;
//--- Если не удалось установить новую координату в графические объекты фона и переднего плана - возвращаем false
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_XDISTANCE,x) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_XDISTANCE,x))
      return false;
//--- Записываем новую координату в переменную и возвращаем 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::Устанавливает координату Y графического объекта     |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetY(const int y)
  {
//--- Если передана существующая координата - возвращаем true
   if(this.ObjectY()==y)
      return true;
//--- Если не удалось установить новую координату в графические объекты фона и переднего плана - возвращаем false
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_YDISTANCE,y) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_YDISTANCE,y))
      return false;
//--- Записываем новую координату в переменную и возвращаем 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::Изменяет ширину графического объекта                |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResizeW(const int size)
  {
//--- Если передана существующая ширина - возвращаем true
   if(this.ObjectWidth()==size)
      return true;
//--- Если передан размер больше 0, возвращаем результат изменения ширины фона и переднего плана, иначе - 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::Изменяет высоту графического объекта                |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResizeH(const int size)
  {
//--- Если передана существующая высота - возвращаем true
   if(this.ObjectHeight()==size)
      return true;
//--- Если передан размер больше 0, возвращаем результат изменения высоты фона и переднего плана, иначе - 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::Изменяет размер графического объекта                |
//+------------------------------------------------------------------+
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::Устанавливает объекту новые координаты X и Y        |
//+------------------------------------------------------------------+
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::Устанавливает объекту новую координату X            |
//+------------------------------------------------------------------+
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::Устанавливает объекту новую координату Y            |
//+------------------------------------------------------------------+
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::Смещает объект по осям X и Y на указанное смещение  |
//+------------------------------------------------------------------+
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::Смещает объект по оси X на указанное смещение       |
//+------------------------------------------------------------------+
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::Смещает объект по оси Y на указанное смещение       |
//+------------------------------------------------------------------+
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::Скрывает объект на всех периодах графика            |
//+------------------------------------------------------------------+
void CCanvasBase::Hide(const bool chart_redraw)
  {
//--- Если объект уже скрыт - уходим
   if(this.m_hidden)
      return;
//--- Если изменение видимости для фона и переднего плана успешно поставлено
//--- в очередь команд графика - устанавливаем флаг скрытого объекта
   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(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::Отображает объект на всех периодах графика          |
//+------------------------------------------------------------------+
void CCanvasBase::Show(const bool chart_redraw)
  {
//--- Если объект уже видимый - уходим
   if(!this.m_hidden)
      return;
//--- Если изменение видимости для фона и переднего плана успешно поставлено
//--- в очередь команд графика - сбрасываем флаг скрытого объекта
   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(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::Помещает объект на передний план                    |
//+------------------------------------------------------------------+
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::Устанавливает текущие цвета                         |
//| элемента в состояние по умолчанию                                |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToDefault(void)
  {
   bool res=true;
   res &=this.BackColorToDefault();
   res &=this.ForeColorToDefault();
   res &=this.BorderColorToDefault();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает текущие цвета                         |
//| элемента в состояние при наведении курсора                       |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToFocused(void)
  {
   bool res=true;
   res &=this.BackColorToFocused();
   res &=this.ForeColorToFocused();
   res &=this.BorderColorToFocused();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает текущие цвета                         |
//| элемента в состояние при нажатии курсора                         |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToPressed(void)
  {
   bool res=true;
   res &=this.BackColorToPressed();
   res &=this.ForeColorToPressed();
   res &=this.BorderColorToPressed();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает текущие цвета                         |
//| элемента в заблокированное состояние                             |
//+------------------------------------------------------------------+
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::Блокирует элемент                                   |
//+------------------------------------------------------------------+
void CCanvasBase::Block(const bool chart_redraw)
  {
//--- Если элемент уже заблокирован - уходим
   if(this.m_blocked)
      return;
//--- Устанавливаем текущие цвета как цвета заблокированного элемента, 
//--- перерисовываем объект и устанавливаем флаг блокировки
   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::Разблокирует элемент                                |
//+------------------------------------------------------------------+
void CCanvasBase::Unblock(const bool chart_redraw)
  {
//--- Если элемент уже разблокирован - уходим
   if(!this.m_blocked)
      return;
//--- Устанавливаем текущие цвета как цвета элемента в обычном состоянии, 
//--- перерисовываем объект и сбрасываем флаг блокировки
   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::Заливает объект указанным цветом                    |
//| с установленной в m_alpha прозрачностью                          |
//+------------------------------------------------------------------+
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::Заливает объект прозрачным цветом                   |
//+------------------------------------------------------------------+
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::Обновляет объект для отображения изменений          |
//+------------------------------------------------------------------+
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::Рисует внешний вид                                  |
//+------------------------------------------------------------------+
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::Уничтожает объект                                   |
//+------------------------------------------------------------------+
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::Возвращает описание объекта                         |
//+------------------------------------------------------------------+
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::Выводит в журнал описание объекта                   |
//+------------------------------------------------------------------+
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::Сохранение в файл                                   |
//+------------------------------------------------------------------+
bool CCanvasBase::Save(const int file_handle)
  {
//--- Метод временно отключен
   return false;
   
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;

/*
//--- Сохранение свойств
      
*/
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| Загрузка из файла                                                |
//+------------------------------------------------------------------+
bool CCanvasBase::Load(const int file_handle)
  {
//--- Метод временно отключен
   return false;
   
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;

/*
//--- Загрузка свойств
   
*/
//--- Всё успешно
   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::Рисует внешний вид                                  |
//+------------------------------------------------------------------+
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 "Controls\Base.mqh"
  
CCanvasBase *obj1=NULL;       // Указатель на первый графический элемент
CCanvasBase *obj2=NULL;       // Указатель на второй графический элемент
  
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Создаём первый графический элемент
   obj1=new CCanvasBase(0,0,"TestScr1",100,40,160,160);
   obj1.SetAlpha(250);        // Прозрачность
   obj1.SetBorderWidth(6);    // Ширина рамки
//--- Заливаем цветом фон и рисуем рамку с отступом в один пиксель от установленной ширины рамки
   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);
//--- Устанавливаем наименование и идентификатор элемента и выводим в журнал его описание
   obj1.SetName("Rectangle 1");
   obj1.SetID(1);
   obj1.Print();

//--- Внутри первого создаём второй элемент, устанавливаем для него прозрачность
//--- и указываем для второго элемента в качестве контейнера первый элемент
   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);

//--- Инициализируем цвет фона, указываем цвет для заблокированного элемента
//--- и делаем текущим цветом фона элемента цвет фона, заданный по умолчанию 
   obj2.InitBackColors(clrLime);
   obj2.InitBackColorBlocked(clrLightGray);
   obj2.BackColorToDefault();

//--- Инициализируем цвет переднего плана, указываем цвет для заблокированного элемента
//--- и делаем текущим цветом переднего плана элемента цвет текста, заданный по умолчанию 
   obj2.InitForeColors(clrBlack);
   obj2.InitForeColorBlocked(clrDimGray);
   obj2.ForeColorToDefault();

//--- Инициализируем цвет рамки, указываем цвет для заблокированного элемента
//--- и делаем текущим цветом рамки элемента цвет рамки, заданный по умолчанию 
   obj2.InitBorderColors(clrBlue);
   obj2.InitBorderColorBlocked(clrSilver);
   obj2.BorderColorToDefault();
//--- Устанавливаем наименование и идентификатор элемента,
//--- выводим в журнал его описание, и рисуем элемент
   obj2.SetName("Rectangle 2");
   obj2.SetID(2);
   obj2.Print();
   obj2.Draw(true);
   
//--- Проверим обрезку элемента по границам его контейнера
   int ms=1;         // Задержка при смещении в миллисекундах
   int total=obj1.Width()-shift; // Количество итераций цикла смещения
   
//--- Ждём секунду и смещаем внутренний объект за левый край контейнера
   Sleep(1000);
   ShiftHorisontal(-1,total,ms);
//--- Ждём секунду и возвращаем внутренний объект на исходное место
   Sleep(1000);
   ShiftHorisontal(1,total,ms);

//--- Ждём секунду и смещаем внутренний объект за правый край контейнера
   Sleep(1000);
   ShiftHorisontal(1,total,ms);
//--- Ждём секунду и возвращаем внутренний объект на исходное место
   Sleep(1000);
   ShiftHorisontal(-1,total,ms);

   
//--- Ждём секунду и смещаем внутренний объект за верхний край контейнера
   Sleep(1000);
   ShiftVertical(-1,total,ms);
//--- Ждём секунду и возвращаем внутренний объект на исходное место
   Sleep(1000);
   ShiftVertical(1,total,ms);
     
//--- Ждём секунду и смещаем внутренний объект за нижний край контейнера
   Sleep(1000);
   ShiftVertical(1,total,ms);
//--- Ждём секунду и возвращаем внутренний объект на исходное место
   Sleep(1000);
   ShiftVertical(-1,total,ms);

//--- Ожидаем секунду и ставим внутреннему объекту флаг заблокированного элемента
   Sleep(1000);
   obj2.Block(true);

//--- Через три секунды, перед завершением работы, чистим за собой
   Sleep(3000);
   delete obj1;
   delete obj2;
  }
//+------------------------------------------------------------------+
//| Смещает объект по горизонтали                                    |
//+------------------------------------------------------------------+
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);
     }
  }
//+------------------------------------------------------------------+
//| Смещает объект по вертикали                                      |
//+------------------------------------------------------------------+
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.