English Русский
preview
Componentes View e Controller para tabelas no paradigma MVC em MQL5: Elementos de controle simples

Componentes View e Controller para tabelas no paradigma MVC em MQL5: Elementos de controle simples

MetaTrader 5Exemplos |
19 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Introdução

No âmbito do desenvolvimento do elemento de controle Table View no paradigma MVC (Model-View-Controller), criamos o modelo da tabela (componente Model) e iniciamos a criação do componente View. Na primeira etapa, foi criado um objeto base, que é o progenitor de todos os demais elementos gráficos.

Hoje iniciaremos o desenvolvimento de elementos de controle simples, a partir dos quais, posteriormente, criaremos elementos compostos. Cada elemento de controle será dotado de funcional para interação interativa com o usuário e com outros elementos. Ou seja, trata-se exatamente do funcional do componente Controller.

Como na linguagem MQL modelo de eventos está integrado aos objetos criados por meio dos eventos do gráfico, em todos os elementos de controle organizaremos o processamento de eventos para implementar a ligação do componente View com o componente Controller. Para isso, faremos ajustes na classe base dos elementos gráficos.

Em seguida, criaremos elementos de controle simples, o rótulo de texto e diferentes botões. Cada um desses elementos terá a possibilidade de desenhar um ícone, o que permitirá criar, a partir de botões simples, elementos de controle completamente diferentes. Se observarmos uma linha de uma lista em árvore, onde à esquerda há um ícone e à direita há um texto, isso parece ser um elemento de controle separado. Porém, teremos a possibilidade de criá-lo facilmente utilizando um botão comum. Ao mesmo tempo, será possível configurar os parâmetros da linha de modo que ela ou reaja com a mudança de cor ao passar o cursor do mouse e ao clicar, ou seja estática, mas responda a cliques.

Tudo isso poderá ser feito facilmente com a ajuda de algumas linhas de configuração do objeto após a sua criação. E, a partir desses elementos, posteriormente criaremos elementos de controle compostos complexos, totalmente interativos e prontos para uso.


Componente Controller. Aperfeiçoamento das classes base

Assim, para implementar o que foi planejado, precisamos aperfeiçoar um pouco as classes, macrossubstituições e enumerações já escritas. A maior parte do funcional necessário ficará localizada no objeto base dos elementos gráficos. Portanto, o principal aperfeiçoamento afetará justamente esse objeto.

Anteriormente, essa classe estava localizada no caminho MQL5\Scripts\Tables\Controls\Base.mqh.
Hoje vamos escrever um indicador de teste, portanto precisamos, no diretório de indicadores \MQL5\Indicators, criar uma nova pasta \Tables\Controls\ e colocar nela o arquivo Base.mqh, que é exatamente o que iremos aperfeiçoar hoje.

Posteriormente, nos objetos serão armazenadas listas de elementos de controle anexados. Tais objetos podem ser, por exemplo, contêineres. Para que essas listas possam funcionar corretamente com arquivos, isto é, criar objetos armazenados nas listas, é necessário declarar previamente todas as classes dos elementos que serão criados. Vamos inserir no arquivo Base.mqh declaração das classes, novas macrossubstituições, enumerações e constantes das enumeraçõ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

//--- Форвард-декларация классов элементов управления
class    CImagePainter;                   // Класс рисования изображений
class    CLabel;                          // Класс текстовой метки
class    CButton;                         // Класс простой кнопки
class    CButtonTriggered;                // Класс двухпозиционной кнопки
class    CButtonArrowUp;                  // Класс кнопки со стрелкой вверх
class    CButtonArrowDown;                // Класс кнопки со стрелкой вниз
class    CButtonArrowLeft;                // Класс кнопки со стрелкой влево
class    CButtonArrowRight;               // Класс кнопки со стрелкой вправо
class    CCheckBox;                       // Класс элемента управления CheckBox
class    CRadioButton;                    // Класс элемента управления RadioButton

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define  clrNULL              0x00FFFFFF  // Прозрачный цвет для CCanvas
#define  MARKER_START_DATA    -1          // Маркер начала данных в файле
#define  DEF_FONTNAME         "Calibri"   // Шрифт по умолчанию
#define  DEF_FONTSIZE         10          // Размер шрифта по умолчанию

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_TYPE                    // Перечисление типов графических элементов
  {
   ELEMENT_TYPE_BASE = 0x10000,           // Базовый объект графических элементов
   ELEMENT_TYPE_COLOR,                    // Объект цвета
   ELEMENT_TYPE_COLORS_ELEMENT,           // Объект цветов элемента графического объекта
   ELEMENT_TYPE_RECTANGLE_AREA,           // Прямоугольная область элемента
   ELEMENT_TYPE_IMAGE_PAINTER,            // Объект для рисования изображений
   ELEMENT_TYPE_CANVAS_BASE,              // Базовый объект холста графических элементов
   ELEMENT_TYPE_LABEL,                    // Текстовая метка
   ELEMENT_TYPE_BUTTON,                   // Простая кнопка
   ELEMENT_TYPE_BUTTON_TRIGGERED,         // Двухпозиционная кнопка
   ELEMENT_TYPE_BUTTON_ARROW_UP,          // Кнопка со стрелкой вверх
   ELEMENT_TYPE_BUTTON_ARROW_DOWN,        // Кнопка со стрелкой вниз
   ELEMENT_TYPE_BUTTON_ARROW_LEFT,        // Кнопка со стрелкой влево
   ELEMENT_TYPE_BUTTON_ARROW_RIGHT,       // Кнопка со стрелкой вправо
   ELEMENT_TYPE_CHECKBOX,                 // Элемент управления CheckBox
   ELEMENT_TYPE_RADIOBUTTON,              // Элемент управления RadioButton
  };
  
enum ENUM_ELEMENT_STATE                   // Состояние элемента
  {
   ELEMENT_STATE_DEF,                     // По умолчанию (напр. кнопка отжата, и т.п.)
   ELEMENT_STATE_ACT,                     // Активирован (напр. кнопка нажата, и т.п.)
  };

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

Ao criar métodos de salvamento e carregamento de objetos em/de arquivos, em cada método existem linhas constantes que se repetem sem alteração de um método para outro:

//--- Проверяем хэндл
   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;

E também existem métodos idênticos Description e Print. Portanto, é razoável transferir essas linhas para os métodos de carregamento/salvamento no objeto base. Assim, não será necessário escrevê-las em cada novo método de carregamento/salvamento em cada nova classe onde esteja prevista a работа com arquivos.

Declararemos esses métodos no objeto base:

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) сохранения в файл, (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_BASE); }
   
//--- (1) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   virtual void      Print(void);
   
//--- Конструктор/деструктор
                     CBaseObj (void) : m_id(-1) { this.SetName(""); }
                    ~CBaseObj (void) {}
  };

E escrever a sua implementação:

//+------------------------------------------------------------------+
//| CBaseObj::Возвращает описание объекта                            |
//+------------------------------------------------------------------+
string CBaseObj::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   return ::StringFormat("%s%s ID %d",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.ID());
  }
//+------------------------------------------------------------------+
//| CBaseObj::Выводит в журнал описание объекта                      |
//+------------------------------------------------------------------+
void CBaseObj::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CBaseObj::Сохранение в файл                                      |
//+------------------------------------------------------------------+
bool CBaseObj::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;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CBaseObj::Загрузка из файла                                      |
//+------------------------------------------------------------------+
bool CBaseObj::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;
   
//--- Всё успешно
   return true;
  }

Agora, para cada nova classe, os métodos Description e Print podem ser declarados e implementados apenas no caso em que a sua lógica seja diferente da lógica definida nessa classe.

E, nos métodos de trabalho com arquivos nas classes herdadas, em vez de escrever constantemente as mesmas linhas em cada método de cada classe, simplesmente chamaremos os métodos de trabalho com arquivos desse objeto base.

De todas as classes subsequentes deste arquivo (Base.mqh), removeremos todos os métodos Print, pois eles já existem no objeto base e o repetem completamente.

Em todos os métodos de trabalho com arquivos removeremos as seguintes linhas:

//+------------------------------------------------------------------+
//| 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;
  }

Agora em vez dessas linhas, temos simplesmente a chamada do método da classe base:

//+------------------------------------------------------------------+
//| CColor::Сохранение в файл                                        |
//+------------------------------------------------------------------+
bool CColor::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CBaseObj::Save(file_handle))
      return false;

//--- Сохраняем цвет
   if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Всё успешно
   return true;
  }

Essas alterações já foram feitas em todos os métodos de trabalho com arquivos deste arquivo. Ao escrever as classes subsequentes, levaremos essas alterações em consideração.

Na classe CColorElement, substituiremos nos construtores das classes linhas idênticas e repetidas

//+------------------------------------------------------------------+
//| 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);
  }

por um único método Init():

public:
//--- Возвращает новый цвет
   color             NewColor(color base_color, int shift_red, int shift_green, int shift_blue);

//--- Инициализация класса
   void              Init(void);

//--- Инициализация цветов различных состояний

...

A sua implementação:

//+------------------------------------------------------------------+
//| CColorControl::Инициализация класса                              |
//+------------------------------------------------------------------+
void CColorElement::Init(void)
  {
   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);
  }
//+------------------------------------------------------------------+

Se no método de inicialização da cor for passado um valor de cor transparente, então não é necessário alterá-lo de forma alguma para quaisquer estados.

Levaremos isso em conta na implementação do método:

//+------------------------------------------------------------------+
//| CColorControl::Устанавливает цвета для всех состояний по текущему|
//+------------------------------------------------------------------+
void CColorElement::InitColors(const color clr)
  {
   this.InitDefault(clr);
   this.InitFocused(clr!=clrNULL ? this.NewColor(clr,-20,-20,-20) : clrNULL);
   this.InitPressed(clr!=clrNULL ? this.NewColor(clr,-40,-40,-40) : clrNULL);
   this.InitBlocked(clrWhiteSmoke);   
  }

Na classe CBound, é necessário adicionar um método que retorne o sinalizador de o cursor estar dentro da área retangular — pois isso será necessário na implementação do componente Controller:

//+------------------------------------------------------------------+
//| Класс прямоугольной области                                      |
//+------------------------------------------------------------------+
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-(this.m_bound.Width()  >0 ? 1 : 0);}
   int               Bottom(void)                        const { return this.m_bound.bottom-(this.m_bound.Height()>0 ? 1 : 0);}

//--- Возвращает флаг нахождения курсора внутри области
   bool              Contains(const int x,const int y)   const { return this.m_bound.Contains(x,y);                           }
   
//--- Возвращает описание объекта
   virtual string    Description(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); }
  };

Agora é necessário adicionar tudo o que for preciso para a implementação do componente Controller na classe base do canvas dos elementos gráficos CCanvasBase.

Na interação dos elementos gráficos com o mouse, é necessário desativar algumas propriedades do gráfico, como a rolagem do gráfico com a rodinha do mouse, o menu do botão direito do mouse, etc. Isso será feito por cada objeto dos elementos gráficos. Porém, na primeira execução, é necessário memorizar o estado das propriedades do gráfico que existia antes da execução do programa. E, após o término do trabalho, restaurar tudo ao estado original.

Para isso, na seção privada da classe CCanvasBase, declararemos variáveis para armazenar os valores das propriedades do gráfico que devem ser memorizadas, bem como um método para definir as restrições dessas propriedades do gráfico.

//+------------------------------------------------------------------+
//| Базовый класс холста графических элементов                       |
//+------------------------------------------------------------------+
class CCanvasBase : public CBaseObj
  {
private:
   bool              m_chart_mouse_wheel_flag;                 // Флаг отправки сообщений о прокрутке колёсика мышки
   bool              m_chart_mouse_move_flag;                  // Флаг отправки сообщений о перемещениях курсора мышки
   bool              m_chart_object_create_flag;               // Флаг отправки сообщений о событии создания графического объекта
   bool              m_chart_mouse_scroll_flag;                // Флаг прокрутки графика левой кнопкой и колёсиком мышки
   bool              m_chart_context_menu_flag;                // Флаг доступа к контекстному меню по нажатию правой клавиши мышки
   bool              m_chart_crosshair_tool_flag;              // Флаг доступа к инструменту "Перекрестие" по нажатию средней клавиши мышки
   bool              m_flags_state;                            // Состояние флагов прокрутки графика колёсиком, контекстного меню и перекрестия
   
//--- Установка запретов для графика (прокрутка колёсиком, контекстное меню и перекрестие)
   void              SetFlags(const bool flag);
   
protected:

Os elementos gráficos podem ter dois estados (podem ser mais, mas por enquanto são dois). Por exemplo, para um botão, pressionado e solto. Isso significa que precisamos controlar os estados das cores do elemento em seus dois estados. Na seção protegida da classe, definiremos uma variável para armazenar o estado do elemento, mais um conjunto de objetos de gerenciamento de cores e separaremos o controle de transparência do canvas de fundo e do primeiro plano de forma independente:

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;                           // Объект управления цветом рамки
   
   CColorElement     m_color_background_act;                   // Объект управления цветом фона активированного элемента
   CColorElement     m_color_foreground_act;                   // Объект управления цветом переднего плана активированного элемента
   CColorElement     m_color_border_act;                       // Объект управления цветом рамки активированного элемента
   
   ENUM_ELEMENT_STATE m_state;                                 // Состояние элемента (напр., кнопки (вкл/выкл))
   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_bg;                               // Прозрачность фона
   uchar             m_alpha_fg;                               // Прозрачность переднего плана
   uint              m_border_width;                           // Ширина рамки
   string            m_program_name;                           // Имя программы
   bool              m_hidden;                                 // Флаг скрытого объекта
   bool              m_blocked;                                // Флаг заблокированного элемента
   bool              m_focused;                                // Флаг элемента в фокусе

Aqui também declararemos métodos para controle do cursor do mouse, gerenciamento de cores e manipuladores virtuais de eventos:

//--- Ограничивает графический объект по размерам контейнера
   virtual void      ObjectTrim(void);
//--- Возвращает флаг нахождения курсора внутри объекта
   bool              Contains(const int x,const int y);

   
//--- Проверяет установленный цвет на равенство указанному
   bool              CheckColor(const ENUM_COLOR_STATE state) const;
//--- Изменяет цвета фона, текста и рамки в зависимости от условия
   void              ColorChange(const ENUM_COLOR_STATE state);
   
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(void);
   virtual void      InitColors(void);

//--- Обработчики событий (1) наведения курсора (Focus), (2) нажатий кнопок мышки (Press), (3) прокрутки колёсика (Wheel),
//--- (4) ухода из фокуса (Release), (5) создания графического объекта (Create). Должны определяться в наследниках
   virtual void      OnFocusEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnPressEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnReleaseEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnCreateEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam)      { return;   }  // обработчик здесь отключен
//--- Обработчики пользовательских событий элемента при наведении курсора, щелчке и прокрутке колёсика в области объекта
   virtual void      MouseMoveHandler(const int id, const long lparam, const double dparam, const string sparam)  { return;   }  // обработчик здесь отключен
   virtual void      MousePressHandler(const int id, const long lparam, const double dparam, const string sparam) { return;   }  // обработчик здесь отключен
   virtual void      MouseWheelHandler(const int id, const long lparam, const double dparam, const string sparam) { return;   }  // обработчик здесь отключен
   
public:

Na seção pública da classe, adicionaremos métodos para obter os objetos de gerenciamento de cores do elemento no estado ativado e métodos para obter as cores nos diferentes estados do elemento:

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) рамки активированного элемента
   CColorElement    *GetBackColorActControl(void)              { return &this.m_color_background_act;                                              }
   CColorElement    *GetForeColorActControl(void)              { return &this.m_color_foreground_act;                                              }
   CColorElement    *GetBorderColorActControl(void)            { return &this.m_color_border_act;                                                  }
   
//--- Возврат текущего цвета (1) фона, (2) переднего плана, (3) рамки
   color             BackColor(void)         const { return(!this.State() ? this.m_color_background.GetCurrent() : this.m_color_background_act.GetCurrent());  }
   color             ForeColor(void)         const { return(!this.State() ? this.m_color_foreground.GetCurrent() : this.m_color_foreground_act.GetCurrent());  }
   color             BorderColor(void)       const { return(!this.State() ? this.m_color_border.GetCurrent()     : this.m_color_border_act.GetCurrent());      }
   
//--- Возврат предустановленного цвета DEFAULT (1) фона, (2) переднего плана, (3) рамки
   color             BackColorDefault(void)  const { return(!this.State() ? this.m_color_background.GetDefault() : this.m_color_background_act.GetDefault());  }
   color             ForeColorDefault(void)  const { return(!this.State() ? this.m_color_foreground.GetDefault() : this.m_color_foreground_act.GetDefault());  }
   color             BorderColorDefault(void)const { return(!this.State() ? this.m_color_border.GetDefault()     : this.m_color_border_act.GetDefault());      }
   
//--- Возврат предустановленного цвета FOCUSED (1) фона, (2) переднего плана, (3) рамки
   color             BackColorFocused(void)  const { return(!this.State() ? this.m_color_background.GetFocused() : this.m_color_background_act.GetFocused());  }
   color             ForeColorFocused(void)  const { return(!this.State() ? this.m_color_foreground.GetFocused() : this.m_color_foreground_act.GetFocused());  }
   color             BorderColorFocused(void)const { return(!this.State() ? this.m_color_border.GetFocused()     : this.m_color_border_act.GetFocused());      }
   
//--- Возврат предустановленного цвета PRESSED (1) фона, (2) переднего плана, (3) рамки
   color             BackColorPressed(void)  const { return(!this.State() ? this.m_color_background.GetPressed() : this.m_color_background_act.GetPressed());  }
   color             ForeColorPressed(void)  const { return(!this.State() ? this.m_color_foreground.GetPressed() : this.m_color_foreground_act.GetPressed());  }
   color             BorderColorPressed(void)const { return(!this.State() ? this.m_color_border.GetPressed()     : this.m_color_border_act.GetPressed());      }
   
//--- Возврат предустановленного цвета BLOCKED (1) фона, (2) переднего плана, (3) рамки
   color             BackColorBlocked(void)              const { return this.m_color_background.GetBlocked();                                      }
   color             ForeColorBlocked(void)              const { return this.m_color_foreground.GetBlocked();                                      }
   color             BorderColorBlocked(void)            const { return this.m_color_border.GetBlocked();                                          }
   
//--- Установка цветов фона для всех состояний

Agora, em cada um dos métodos de obtenção de cor, o estado do elemento é verificado, ativado ou desativado, e a cor necessária é retornada de acordo com o estado do elemento.

Adicionaremos métodos para definir as cores do elemento ativado e aperfeiçoaremos os métodos para definir as cores dos estados do elemento em relação ao cursor do mouse, levando em conta o estado do elemento como ativado ou desativado:

//--- Установка цветов фона для всех состояний
   void              InitBackColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_background_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitBackColorsAct(const color clr)        { this.m_color_background_act.InitColors(clr);                                      }

//--- Установка цветов переднего плана для всех состояний
   void              InitForeColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_foreground_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitForeColorsAct(const color clr)        { this.m_color_foreground_act.InitColors(clr);                                      }

//--- Установка цветов рамки для всех состояний
   void              InitBorderColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_border_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitBorderColorsAct(const color clr)      { this.m_color_border_act.InitColors(clr);                                          }

//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки начальными значениями
   void              InitBackColorActDefault(const color clr)  { this.m_color_background_act.InitDefault(clr);                                     }
   void              InitForeColorActDefault(const color clr)  { this.m_color_foreground_act.InitDefault(clr);                                     }
   void              InitBorderColorActDefault(const color clr){ this.m_color_border_act.InitDefault(clr);                                         }
   
//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при наведении курсора начальными значениями
   void              InitBackColorActFocused(const color clr)  { this.m_color_background_act.InitFocused(clr);                                     }
   void              InitForeColorActFocused(const color clr)  { this.m_color_foreground_act.InitFocused(clr);                                     }
   void              InitBorderColorActFocused(const color clr){ this.m_color_border_act.InitFocused(clr);                                         }
   
//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при щелчке по объекту начальными значениями
   void              InitBackColorActPressed(const color clr)  { this.m_color_background_act.InitPressed(clr);                                     }
   void              InitForeColorActPressed(const color clr)  { this.m_color_foreground_act.InitPressed(clr);                                     }
   void              InitBorderColorActPressed(const color clr){ this.m_color_border_act.InitPressed(clr);                                         }
   
//--- Установка текущего цвета фона в различные состояния
   bool              BackColorToDefault(void)
                       {
                        return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_DEFAULT) :
                                               this.m_color_background_act.SetCurrentAs(COLOR_STATE_DEFAULT));
                       }
   bool              BackColorToFocused(void)
                       {
                        return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_FOCUSED) :
                                               this.m_color_background_act.SetCurrentAs(COLOR_STATE_FOCUSED));
                       }
   bool              BackColorToPressed(void)
                       {
                        return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_PRESSED) :
                                               this.m_color_background_act.SetCurrentAs(COLOR_STATE_PRESSED));
                       }
   bool              BackColorToBlocked(void)   { return this.m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED);  }
   
//--- Установка текущего цвета переднего плана в различные состояния
   bool              ForeColorToDefault(void)
                       { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_DEFAULT) :
                                                this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_DEFAULT));
                       }
   bool              ForeColorToFocused(void)
                       { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_FOCUSED) :
                                                this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_FOCUSED));
                       }
   bool              ForeColorToPressed(void)
                       { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_PRESSED) :
                                                this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_PRESSED));
                       }
   bool              ForeColorToBlocked(void)   { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_BLOCKED);  }
   
//--- Установка текущего цвета рамки в различные состояния
   bool              BorderColorToDefault(void)
                       { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_DEFAULT) :
                                                this.m_color_border_act.SetCurrentAs(COLOR_STATE_DEFAULT));
                       }
   bool              BorderColorToFocused(void)
                       { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_FOCUSED) :
                                                this.m_color_border_act.SetCurrentAs(COLOR_STATE_FOCUSED));
                       }
   bool              BorderColorToPressed(void)
                       { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_PRESSED) :
                                                this.m_color_border_act.SetCurrentAs(COLOR_STATE_PRESSED));
                       }
   bool              BorderColorToBlocked(void) { return this.m_color_border.SetCurrentAs(COLOR_STATE_BLOCKED);      }

Adicionaremos métodos para definir e retornar o estado do elemento:

//--- Создаёт OBJ_BITMAP_LABEL
   bool              Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h);

//--- (1) Устанавливает, (2) возвращает состояние
   void              SetState(ENUM_ELEMENT_STATE state)        { this.m_state=state; this.ColorsToDefault();                                       }
   ENUM_ELEMENT_STATE State(void)                        const { return this.m_state;                                                              }

//--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного элемента (4) в фокусе, (5) имя графического объекта (фон, текст)

Ao definir o estado do elemento, é necessário, após definir o sinalizador de estado, gravar todas as cores do elemento como atuais — se o elemento estiver ativado, por exemplo, um botão pressionado, todas as cores atuais são definidas como as cores do botão pressionado. Caso contrário, as cores atuais são obtidas da lista de cores do botão não pressionado.

Como agora separamos a definição e o retorno da transparência para o fundo e o primeiro plano, adicionaremos novos métodos para definir e retornar a transparência:

   string            NameBG(void)                        const { return this.m_background.ChartObjectName();                                       }
   string            NameFG(void)                        const { return this.m_foreground.ChartObjectName();                                       }
   
//--- (1) Возвращает, (2) устанавливает прозрачность фона
   uchar             AlphaBG(void)                       const { return this.m_alpha_bg;                                                           }
   void              SetAlphaBG(const uchar value)             { this.m_alpha_bg=value;                                                            }
//--- (1) Возвращает, (2) устанавливает прозрачность переднего плана
   uchar             AlphaFG(void)                       const { return this.m_alpha_fg;                                                           }
   void              SetAlphaFG(const uchar value)             { this.m_alpha_fg=value;                                                            }

//--- Устанавливает прозрачность для фона и переднего плана
   void              SetAlpha(const uchar value)               { this.m_alpha_fg=this.m_alpha_bg=value;                                            }
   
//--- (1) Возвращает, (2) устанавливает ширину рамки

Declararemos um manipulador de eventos que deverá ser chamado a partir do manipulador de eventos do programa controlador:

//--- Обработчик событий                                               |
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
   
//--- Конструкторы/деструктор
                     CCanvasBase(void) :
                        m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_chart_id(::ChartID()), m_wnd(0),
                        m_alpha_bg(0), m_alpha_fg(255), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0), m_wnd_y(0), m_state(0) { this.Init(); }
                     CCanvasBase(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h);
                    ~CCanvasBase(void);
  };

No construtor, faremos a indicação correta das propriedades da fonte desenhada no canvas e chamaremos o método Init() para memorizar as propriedades do gráfico e do mouse:

//+------------------------------------------------------------------+
//| CCanvasBase::Конструктор                                         |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(const string object_name,const long chart_id,const int wnd,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_bg(0), m_alpha_fg(255), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0), m_state(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,object_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(DEF_FONTNAME,-DEF_FONTSIZE*10,FW_MEDIUM);
      this.m_bound.SetName("Perimeter");
      
      //--- Запоминаем разрешения для мышки и инструментов графика
      this.Init();
     }
  }

No destrutor da classe, destruímos o objeto gráfico criado e restauramos as propriedades do gráfico e as permissões do mouse que foram memorizadas:

//+------------------------------------------------------------------+
//| CCanvasBase::Деструктор                                          |
//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(void)
  {
//--- Уничтожаем объект
   this.Destroy();
//--- Возвращаем разрешения для мышки и инструментов графика
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, this.m_chart_mouse_wheel_flag);
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, this.m_chart_mouse_move_flag);
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, this.m_chart_object_create_flag);
   ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL, this.m_chart_mouse_scroll_flag);
   ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU, this.m_chart_context_menu_flag);
   ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL, this.m_chart_crosshair_tool_flag);
  }

No método de criação do elemento gráfico, o nome do objeto gráfico criado não deve conter espaços. Corrigiremos isso, substituindo os espaços no nome por sublinhados:

//+------------------------------------------------------------------+
//| CCanvasBase::Создаёт графические объекты фона и переднего плана  |
//+------------------------------------------------------------------+
bool CCanvasBase::Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h)
  {
//--- Получаем скорректированный идентификатор графика
   long id=this.CorrectChartID(chart_id);
//--- Корректируем переданное имя для объекта
   string nm=object_name;
   ::StringReplace(nm," ","_");
//--- Создаём имя графического объекта для фона и создаём канвас
   string obj_name=nm+"_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=nm+"_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;
  }

O método que retorna o sinalizador de o cursor estar dentro do objeto:

//+------------------------------------------------------------------+
//| CCanvasBase::Возвращает флаг нахождения курсора внутри объекта   |
//+------------------------------------------------------------------+
bool CCanvasBase::Contains(const int x,const int y)
  {
//--- check and return the result
   int left=::fmax(this.X(),this.ObjectX());
   int right=::fmin(this.Right(),this.ObjectRight());
   int top=::fmax(this.Y(),this.ObjectY());
   int bottom=::fmin(this.Bottom(),this.ObjectBottom());
   return(x>=left && x<=right && y>=top && y<=bottom);
  }

Como o tamanho do objeto e o tamanho do canvas podem diferir (o método ObjectTrim altera o tamanho do canvas sem modificar o tamanho do objeto), então aqui, como limites dentro dos quais o cursor se encontra, é utilizado um dos valores: ou o limite do objeto, ou a borda correspondente do canvas. O método retorna o sinalizador de as coordenadas passadas estarem dentro dos limites obtidos.

No método de bloqueio do elemento, a definição do sinalizador de bloqueio deve estar antesda chamada do método de desenho do elemento, vamos corrigir isso:

//+------------------------------------------------------------------+
//| CCanvasBase::Блокирует элемент                                   |
//+------------------------------------------------------------------+
void CCanvasBase::Block(const bool chart_redraw)
  {
//--- Если элемент уже заблокирован - уходим
   if(this.m_blocked)
      return;
//--- Устанавливаем текущие цвета как цвета заблокированного элемента, 
//--- устанавливаем флаг блокировки и перерисовываем объект
   this.ColorsToBlocked();
   this.m_blocked=true;
   this.Draw(chart_redraw);
  }

Essa correção permite desenhar corretamente o elemento bloqueado. Até essa alteração, ao desenhar o elemento, as cores eram obtidas do estado padrão e não do estado bloqueado, pois o sinalizador era definido apenas após o bloqueio.

Método para definir as restrições do gráfico:

//+------------------------------------------------------------------+
//| CCanvasBase::Установка запретов для графика                      |
//| (прокрутка колёсиком, контекстное меню и перекрестие)            |
//+------------------------------------------------------------------+
void CCanvasBase::SetFlags(const bool flag)
  {
//--- Если нужно установить флаги, и они уже были установлены ранее - уходим
   if(flag && this.m_flags_state)
      return;
//--- Если нужно сбросить флаги, и они уже были сброшены ранее - уходим
   if(!flag && !this.m_flags_state)
      return;
//--- Устанавливаем требуемый флаг для контекстного меню,
//--- инструмента "перекрестие" и прокрутки графика колёсиком мышки.
//--- После установки запоминаем значение установленного флага
   ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU,  flag);
   ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL,flag);
   ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL,  flag);
   this.m_flags_state=flag;
//--- Делаем обновление графика для немедленного применения установленных флагов
   ::ChartRedraw(this.m_chart_id);
  }

Método de inicialização da classe:

//+------------------------------------------------------------------+
//| CCanvasBase::Инициализация класса                                |
//+------------------------------------------------------------------+
void CCanvasBase::Init(void)
  {
//--- Запоминаем разрешения для мышки и инструментов графика
   this.m_chart_mouse_wheel_flag   = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL);
   this.m_chart_mouse_move_flag    = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE);
   this.m_chart_object_create_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE);
   this.m_chart_mouse_scroll_flag  = ::ChartGetInteger(this.m_chart_id, CHART_MOUSE_SCROLL);
   this.m_chart_context_menu_flag  = ::ChartGetInteger(this.m_chart_id, CHART_CONTEXT_MENU);
   this.m_chart_crosshair_tool_flag= ::ChartGetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL);
//--- Устанавливаем разрешения для мышки и графика
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, true);
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, true);
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, true);
   
//--- Инициализируем цвета объекта по умолчанию
   this.InitColors();
  }

Método de inicialização das cores do objeto por padrão:

//+------------------------------------------------------------------+
//| CCanvasBase::Инициализация цветов объекта по умолчанию           |
//+------------------------------------------------------------------+
void CCanvasBase::InitColors(void)
  {
//--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона
   this.InitBackColors(clrWhiteSmoke);
   this.InitBackColorsAct(clrWhiteSmoke);
   this.BackColorToDefault();
   
//--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста
   this.InitForeColors(clrBlack);
   this.InitForeColorsAct(clrBlack);
   this.ForeColorToDefault();
   
//--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки
   this.InitBorderColors(clrDarkGray);
   this.InitBorderColorsAct(clrDarkGray);
   this.BorderColorToDefault();
   
//--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента
   this.InitBorderColorBlocked(clrLightGray);
   this.InitForeColorBlocked(clrSilver);
  }

Método que verifica se a cor definida é igual à especificada:

//+------------------------------------------------------------------+
//| CCanvasBase::Проверяет установленный цвет на равенство указанному|
//+------------------------------------------------------------------+
bool CCanvasBase::CheckColor(const ENUM_COLOR_STATE state) const
  {
   bool res=true;
 //--- В зависимости от проверяемого события
   switch(state)
     {
//--- проверяем равенство всех STANDARD цветов фона, текста и рамки предустановленным значениям
      case COLOR_STATE_DEFAULT :
        res &=this.BackColor()==this.BackColorDefault();
        res &=this.ForeColor()==this.ForeColorDefault();
        res &=this.BorderColor()==this.BorderColorDefault();
        break;

//--- проверяем равенство всех FOCUSED цветов фона, текста и рамки предустановленным значениям
      case COLOR_STATE_FOCUSED :
        res &=this.BackColor()==this.BackColorFocused();
        res &=this.ForeColor()==this.ForeColorFocused();
        res &=this.BorderColor()==this.BorderColorFocused();
        break;
     
//--- проверяем равенство всех PRESSED цветов фона, текста и рамки предустановленным значениям
      case COLOR_STATE_PRESSED :
        res &=this.BackColor()==this.BackColorPressed();
        res &=this.ForeColor()==this.ForeColorPressed();
        res &=this.BorderColor()==this.BorderColorPressed();
        break;
     
//--- проверяем равенство всех BLOCKED цветов фона, текста и рамки предустановленным значениям
      case COLOR_STATE_BLOCKED :
        res &=this.BackColor()==this.BackColorBlocked();
        res &=this.ForeColor()==this.ForeColorBlocked();
        res &=this.BorderColor()==this.BorderColorBlocked();
        break;
        
      default: res=false;
        break;
     }
   return res;
  }

Para que as cores do elemento sejam alteradas apenas no momento da troca do estado do elemento, esse método retorna um sinalizador indicando se as cores correspondentes ao estado do elemento já estão definidas. Se as cores atuais do elemento não forem iguais às cores estabelecidas para o estado verificado, o método permite a mudança de cor e o redesenho do elemento gráfico. Se as cores já estiverem definidas de acordo com o estado do elemento, não há necessidade de alterar as cores nem de redesenhar o objeto — o método proíbe a mudança de cor.

Método que altera as cores dos elementos do objeto por evento:

//+------------------------------------------------------------------+
//| CCanvasBase::Смена цвета элементов объекта по событию            |
//+------------------------------------------------------------------+
void CCanvasBase::ColorChange(const ENUM_COLOR_STATE state)
  {
//--- В зависимости от события устанавливаем цвета события как основные
   switch(state)
     {
      case COLOR_STATE_DEFAULT   :  this.ColorsToDefault(); break;
      case COLOR_STATE_FOCUSED   :  this.ColorsToFocused(); break;
      case COLOR_STATE_PRESSED   :  this.ColorsToPressed(); break;
      case COLOR_STATE_BLOCKED   :  this.ColorsToBlocked(); break;
      default                    :  break;
     }
  }

Dependendo do evento pelo qual é necessário alterar a cor, são definidas as cores atuais de acordo com o evento, isto é, com o estado do elemento.

Manipulador de eventos:

//+------------------------------------------------------------------+
//| CCanvasBase::Обработчик событий                                  |
//+------------------------------------------------------------------+
void CCanvasBase::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Если изменение графика
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- скорректируем дистанцию между верхней рамкой подокна индикатора и верхней рамкой главного окна графика
      this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd);
     }

//--- Если элемент заблокирован или скрыт - уходим
   if(this.IsBlocked() || this.IsHidden())
      return;
      
//--- Координаты курсора мышки
   int x=(int)lparam;
   int y=(int)dparam-this.m_wnd_y;  // Корректируем Y по высоте окна индикатора

//--- Событие перемещения курсора, либо щелчка кнопкой мышки
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Если курсор в пределах объекта
      if(this.Contains(x, y))
        {
         //--- Если объект не в составе контейнера - запрещаем прокрутку графика, контекстное меню и инструмент "Перекрестие"
         if(this.m_container==NULL)
            this.SetFlags(false);
         //--- Получаем состояние кнопок мышки, если нажаты - вызываем обработчик нажатий
         if(sparam=="1" || sparam=="2" || sparam=="16")
            this.OnPressEvent(id, lparam, dparam, sparam);
         //--- кнопки не нажаты - обрабатываем перемещение курсора
         else
            this.OnFocusEvent(id, lparam, dparam, sparam);
        }
      //--- Курсор за пределами объекта
      else
        {
         //--- Обрабатываем увод курсора за границы объекта
         this.OnReleaseEvent(id,lparam,dparam,sparam);
         //--- Если объект не в составе контейнера - разрешаем прокрутку графика, контекстное меню и инструмент "Перекрестие"
         if(this.m_container==NULL)
            this.SetFlags(true);
        }
     }
     
//--- Событие прокрутки колёсика мышки
   if(id==CHARTEVENT_MOUSE_WHEEL)
     {
      this.OnWheelEvent(id,lparam,dparam,sparam);
     }

//--- Событие создания графического объекта
   if(id==CHARTEVENT_OBJECT_CREATE)
     {
      this.OnCreateEvent(id,lparam,dparam,sparam);
     }
     
//--- Если пришло пользовательское событие графика
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- собственные события не обрабатываем
      if(sparam==this.NameBG())
         return;

      //--- приводим пользовательское событие в соответствие со стандартными
      ENUM_CHART_EVENT chart_event=ENUM_CHART_EVENT(id-CHARTEVENT_CUSTOM);
      //--- Если щелчок мышки по объекту
      if(chart_event==CHARTEVENT_OBJECT_CLICK)
        {
         this.MousePressHandler(chart_event, lparam, dparam, sparam);
        }
      //--- Если перемещение курсора мышки
      if(chart_event==CHARTEVENT_MOUSE_MOVE)
        {
         this.MouseMoveHandler(chart_event, lparam, dparam, sparam);
        }
      //--- Если прокрутка колёсика мышки
      if(chart_event==CHARTEVENT_MOUSE_WHEEL)
        {
         this.MouseWheelHandler(chart_event, lparam, dparam, sparam);
        }
     }
  }

No objeto base dos elementos gráficos foi organizada a lógica de processamento da interação do cursor do mouse com os elementos gráficos. Para os diversos eventos monitorados, são chamados manipuladores virtuais. Alguns manipuladores são implementados diretamente nesta classe, enquanto outros simplesmente não fazem nada e precisam ser implementados nos objetos que herdam desta classe.

Os manipuladores de eventos cujo nome termina em *Handler são destinados ao processamento da interação interna nos elementos de controle entre os componentes que os compõem. Já os manipuladores que possuem *Event em seu nome processam diretamente os eventos do gráfico e enviam ao gráfico eventos do usuário, pelos quais é possível determinar o tipo de evento e de qual elemento de controle ele foi enviado. Isso permitirá ao usuário tratar tais eventos em seu próprio programa.

Manipulador de perda de foco:

//+------------------------------------------------------------------+
//| CCanvasBase::Обработчик ухода из фокуса                          |
//+------------------------------------------------------------------+
void CCanvasBase::OnReleaseEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Элемент не в фокусе при уводе курсора
   this.m_focused=false;
//--- восстанавливаем исходные цвета, сбрасываем флаг Focused и перерисовываем объект
   if(!this.CheckColor(COLOR_STATE_DEFAULT))
     {
      this.ColorChange(COLOR_STATE_DEFAULT);
      this.Draw(true);
     }
  }

Manipulador de passagem do cursor:

//+------------------------------------------------------------------+
//| CCanvasBase::Обработчик наведения курсора                        |
//+------------------------------------------------------------------+
void CCanvasBase::OnFocusEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Элемент в фокусе
   this.m_focused=true;
//--- Если цвета объекта не для режима Focused
   if(!this.CheckColor(COLOR_STATE_FOCUSED))
     {
      //--- устанавливаем цвета и флаг Focused и перерисовываем объект
      this.ColorChange(COLOR_STATE_FOCUSED);
      this.Draw(true);
     }
  }

Manipulador de clique no objeto:

//+------------------------------------------------------------------+
//| CCanvasBase::Обработчик нажатия на объект                        |
//+------------------------------------------------------------------+
void CCanvasBase::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Элемент в фокусе при щелчке по нему
   this.m_focused=true;
//--- Если цвета объекта не для режима Pressed
   if(!this.CheckColor(COLOR_STATE_PRESSED))
     {
      //--- устанавливаем цвета Pressed и перерисовываем объект
      this.ColorChange(COLOR_STATE_PRESSED);
      this.Draw(true);
     }

   //--- отправляем пользовательское событие на график с передаенными значениями в lparam, dparam, и именем объекта в sparam
   ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_OBJECT_CLICK, lparam, dparam, this.NameBG());
  }

Manipulador do evento de criação do objeto gráfico:

//+------------------------------------------------------------------+
//| CCanvasBase::Обработчик события создания графического объекта    |
//+------------------------------------------------------------------+
void CCanvasBase::OnCreateEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- если это объект, принадлежащий этой программе - уходим
   if(this.IsBelongsToThis(sparam))
      return;
//--- переносим объект на передний план
   this.BringToTop(true);
  }

A lógica de todos os manipuladores está detalhadamente comentada no código. Essencialmente, aqui apenas é organizada a reação ao evento na forma de alteração das cores do elemento gráfico e, onde necessário, o envio de eventos do usuário para o gráfico. O último manipulador reage à criação do objeto gráfico no gráfico e move os elementos gráficos para o primeiro plano. Isso permitirá, por exemplo, que painéis permaneçam sempre no primeiro plano.

Todos esses manipuladores são virtuais e, se necessário, devem ser sobrescritos nas classes herdadas.

Com o aperfeiçoamento do objeto base de todos os elementos gráficos, finalizamos esta etapa. Agora, com base no componente Controller criado no objeto base e no componente View criado anteriormente, iniciaremos a criação dos elementos gráficos mais simples, que também fazem parte do componente View. Eles se tornarão os "blocos de construção" a partir dos quais, ao final, serão criados elementos de controle complexos e, em particular, o elemento de controle Table View, cuja criação estamos desenvolvendo ao longo de vários artigos.


Elementos de controle simples

Na mesma pasta \MQL5\Indicators\Tables\Controls\ criaremos um novo arquivo incluído Controls.mqh.

Ao arquivo criado incluiremos o arquivo do objeto base dos elementos gráficos Base.mqh e adicionaremos algumas macrossubstituições e enumerações:

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

//+------------------------------------------------------------------+
//| Включаемые библиотеки                                            |
//+------------------------------------------------------------------+
#include "Base.mqh"

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define  DEF_LABEL_W          40          // Ширина текстовой метки по умолчанию
#define  DEF_LABEL_H          16          // Высота текстовой метки по умолчанию
#define  DEF_BUTTON_W         50          // Ширина кнопки по умолчанию
#define  DEF_BUTTON_H         16          // Высота кнопки по умолчанию

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_COMPARE_BY              // Сравниваемые свойства
  {
   ELEMENT_SORT_BY_ID   =  0,             // Сравнение по идентификатору элемента
   ELEMENT_SORT_BY_NAME,                  // Сравнение по наименованию элемента
   ELEMENT_SORT_BY_TEXT,                  // Сравнение по тексту элемента
   ELEMENT_SORT_BY_COLOR,                 // Сравнение по цвету элемента
   ELEMENT_SORT_BY_ALPHA,                 // Сравнение по прозрачности элемента
   ELEMENT_SORT_BY_STATE,                 // Сравнение по состоянию элемента
  };
//+------------------------------------------------------------------+ 
//| Функции                                                          |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Классы                                                           |
//+------------------------------------------------------------------+

Nas macrossubstituições definimos os tamanhos padrão para rótulos de texto e botões. Na enumeração indicamos as propriedades existentes do elemento gráfico base. Com base nessas propriedades, será possível buscar objetos, classificá-los e compará-los. Ao adicionar novas propriedades a quaisquer objetos, incluiremos novas constantes nessa enumeração.


Classes auxiliares

Cada elemento gráfico pode ter em sua composição uma imagem. Isso permitirá desenhar ícones para botões, linhas de texto, etc.
Criaremos uma classe especial para o desenho de imagens, que será uma parte integrante dos elementos de controle simples.

Classe para desenho de imagens em uma área especificada

O objeto dessa classe será declarado na classe do elemento de controle e fornecerá a possibilidade de especificar as dimensões da área e suas coordenadas, dentro das quais a imagem será desenhada. A classe será dotada de métodos para desenhar setas para botões com setas, checkboxes e radiobuttons. Posteriormente, adicionaremos métodos para desenhar outros ícones e um método para desenhar imagens personalizadas. Na classe será passado um ponteiro para o canvas no qual o desenho é realizado, dentro das coordenadas e limites definidos na classe do objeto de desenho:
//+------------------------------------------------------------------+
//| Классы                                                           |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Класс рисования изображений                                      |
//+------------------------------------------------------------------+
class CImagePainter : public CBaseObj
  {
protected:
   CCanvas          *m_canvas;                                 // Указатель на канвас, где рисуем
   CBound            m_bound;                                  // Координаты и границы изображения
   uchar             m_alpha;                                  // Прозрачность
   
//--- Проверяет валидность холста и корректность размеров
   bool              CheckBound(void);

public:
//--- (1) Назначает канвас для рисования, (2) устанавливает, (3) возвращает прозрачность
   void              CanvasAssign(CCanvas *canvas)             { this.m_canvas=canvas;                }
   void              SetAlpha(const uchar value)               { this.m_alpha=value;                  }
   uchar             Alpha(void)                         const { return this.m_alpha;                 }
   
//--- (1) Устанавливает координаты, (2) изменяет размеры области
   void              SetXY(const int x,const int y)            { this.m_bound.SetXY(x,y);             }
   void              SetSize(const int w,const int h)          { this.m_bound.Resize(w,h);            }
//--- Устанавливает координаты и размеры области
   void              SetBound(const int x,const int y,const int w,const int h)
                       {
                        this.SetXY(x,y);
                        this.SetSize(w,h);
                       }

//--- Возвращает границы и размеры рисунка
   int               X(void)                             const { return this.m_bound.X();             }
   int               Y(void)                             const { return this.m_bound.Y();             }
   int               Right(void)                         const { return this.m_bound.Right();         }
   int               Bottom(void)                        const { return this.m_bound.Bottom();        }
   int               Width(void)                         const { return this.m_bound.Width();         }
   int               Height(void)                        const { return this.m_bound.Height();        }
   
//--- Очищает область
   bool              Clear(const int x,const int y,const int w,const int h,const bool update=true);
//--- Рисует закрашенную стрелку (1) вверх, (2) вниз, (3) влево, (4) вправо
   bool              ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   
//--- Рисует (1) отмеченный, (2) неотмеченный CheckBox
   bool              CheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              UncheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   
//--- Рисует (1) отмеченный, (2) неотмеченный RadioButton
   bool              CheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              UncheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   
//--- Виртуальные методы (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_IMAGE_PAINTER);  }
   
//--- Конструкторы/деструктор
                     CImagePainter(void) : m_canvas(NULL)               { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter");  }
                     CImagePainter(CCanvas *canvas) : m_canvas(canvas)  { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter");  }
                     CImagePainter(CCanvas *canvas,const int id,const string name) : m_canvas(canvas)
                       {
                        this.m_id=id;
                        this.SetName(name);
                        this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2);
                       }
                     CImagePainter(CCanvas *canvas,const int id,const int dx,const int dy,const int w,const int h,const string name) : m_canvas(canvas)
                       {
                        this.m_id=id;
                        this.SetName(name);
                        this.SetBound(dx,dy,w,h);
                       }
                    ~CImagePainter(void) {}
  };

Analisemos os métodos da classe.

Método para comparar dois objetos de desenho:

//+------------------------------------------------------------------+
//| CImagePainter::Сравнение двух объектов                           |
//+------------------------------------------------------------------+
int CImagePainter::Compare(const CObject *node,const int mode=0) const
  {
   const CImagePainter *obj=node;
   switch(mode)
     {
      case ELEMENT_SORT_BY_NAME  :  return(this.Name() >obj.Name()   ? 1 : this.Name() <obj.Name() ? -1 : 0);
      case ELEMENT_SORT_BY_ALPHA :  return(this.Alpha()>obj.Alpha()  ? 1 : this.Alpha()<obj.Alpha()? -1 : 0);
      default                    :  return(this.ID()   >obj.ID()     ? 1 : this.ID()   <obj.ID()   ? -1 : 0);
     }
  }

O método é necessário para a busca do objeto de desenho desejado. Por padrão, a busca é realizada pelo identificador do objeto. O método será necessário quando, nos objetos dos elementos de controle, houver listas nas quais são armazenados objetos de desenho. No momento, em cada elemento de controle será declarado um único objeto de desenho, destinado ao desenho do ícone principal do elemento.

Método que verifica a validade do canvas e a correção das dimensões da área da imagem:

//+------------------------------------------------------------------+
//|CImagePainter::Проверяет валидность холста и корректность размеров|
//+------------------------------------------------------------------+
bool CImagePainter::CheckBound(void)
  {
   if(this.m_canvas==NULL)
     {
      ::PrintFormat("%s: Error. First you need to assign the canvas using the CanvasAssign() method",__FUNCTION__);
      return false;
     }
   if(this.Width()==0 || this.Height()==0)
     {
      ::PrintFormat("%s: Error. First you need to set the area size using the SetSize() or SetBound() methods",__FUNCTION__);
      return false;
     }
   return true;
  }

Se nenhum ponteiro para o canvas for passado ao objeto, ou se a largura e a altura da área da imagem não estiverem definidas, o método retorna false. Caso contrário, retorna true.

Método que limpa a área da imagem:

//+------------------------------------------------------------------+
//| CImagePainter::Очищает область                                   |
//+------------------------------------------------------------------+
bool CImagePainter::Clear(const int x,const int y,const int w,const int h,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;
//--- Очищаем прозрачным цветом всю область изображения
   this.m_canvas.FillRectangle(x,y,x+w-1,y+h-1,clrNULL);
//--- Если указано - обновляем канвас
   if(update)
      this.m_canvas.Update(false);
//--- Всё успешно
   return true;   
  }

O método limpa completamente toda a área da imagem, preenchendo-a com uma cor transparente.

Método que desenha uma seta preenchida para cima:

//+------------------------------------------------------------------+
//| CImagePainter::Рисует закрашенную стрелку вверх                  |
//+------------------------------------------------------------------+
bool CImagePainter::ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Рассчитываем координаты углов стрелки внутри области изображения
   int hw=(int)::floor(w/2);  // Половина ширины
   if(hw==0)
      hw=1;

   int x1 = x + 1;            // X. Основание (левая точка)
   int y1 = y + h - 4;        // Y. Левая точка основания
   int x2 = x1 + hw;          // X. Вершина (центральная верхняя точка)
   int y2 = y + 3;            // Y. Вершина (верхняя точка)
   int x3 = x1 + w - 1;       // X. Основание (правая точка)
   int y3 = y1;               // Y. Основание (правая точка)

//--- Рисуем треугольник
   this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha));
   if(update)
      this.m_canvas.Update(false);
   return true;   
  }

Método que desenha uma seta preenchida para baixo:

//+------------------------------------------------------------------+
//| CImagePainter::Рисует закрашенную стрелку вниз                   |
//+------------------------------------------------------------------+
bool CImagePainter::ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Рассчитываем координаты углов стрелки внутри области изображения
   int hw=(int)::floor(w/2);  // Половина ширины
   if(hw==0)
      hw=1;

   int x1=x+1;                // X. Основание (левая точка)
   int y1=y+4;                // Y. Левая точка основания
   int x2=x1+hw;              // X. Вершина (центральная нижняя точка)
   int y2=y+h-3;              // Y. Вершина (нижняя точка)
   int x3=x1+w-1;             // X. Основание (правая точка)
   int y3=y1;                 // Y. Основание (правая точка)

//--- Рисуем треугольник
   this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha));
   if(update)
      this.m_canvas.Update(false);
   return true;   
  }

Método que desenha uma seta preenchida para a esquerda:

//+------------------------------------------------------------------+
//| CImagePainter::Рисует закрашенную стрелку влево                  |
//+------------------------------------------------------------------+
bool CImagePainter::ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Рассчитываем координаты углов стрелки внутри области изображения
   int hh=(int)::floor(h/2);  // Половина высоты
   if(hh==0)
      hh=1;

   int x1=x+w-4;              // X. Основание (правая сторона)
   int y1=y+1;                // Y. Верхний угол основания
   int x2=x+3;                // X. Вершина (левая центральная точка)
   int y2=y1+hh;              // Y. Центральная точка (вершина)
   int x3=x1;                 // X. Нижний угол основания
   int y3=y1+h-1;             // Y. Нижний угол основания

//--- Рисуем треугольник
   this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha));

   if(update)
      this.m_canvas.Update(false);
   return true;
  }

Método que desenha uma seta preenchida para a direita:

//+------------------------------------------------------------------+
//| CImagePainter::Рисует закрашенную стрелку вправо                 |
//+------------------------------------------------------------------+
bool CImagePainter::ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Рассчитываем координаты углов стрелки внутри области изображения
   int hh=(int)::floor(h/2);  // Половина высоты
   if(hh==0)
      hh=1;

   int x1=x+4;                // X. Основание треугольника (левая сторона)
   int y1=y+1;                // Y. Верхний угол основания
   int x2=x+w-3;              // X. Вершина (правая центральная точка)
   int y2=y1+hh;              // Y. Центральная точка (вершина)
   int x3=x1;                 // X. Нижний угол основания
   int y3=y1+h-1;             // Y. Нижний угол основания

//--- Рисуем треугольник
   this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha));
   if(update)
      this.m_canvas.Update(false);
   return true;
  }

Dentro da área da imagem é definida uma área para a seta com um recuo de um pixel em cada um dos lados da área retangular, e dentro dela é desenhada a seta preenchida.

Método que desenha um CheckBox marcado:

//+------------------------------------------------------------------+
//| CImagePainter::Рисует отмеченный CheckBox                        |
//+------------------------------------------------------------------+
bool CImagePainter::CheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Координаты прямоугольника
   int x1=x+1;                // Левый верхний угол, X
   int y1=y+1;                // Левый верхний угол, Y
   int x2=x+w-2;              // Правый нижний угол, X
   int y2=y+h-2;              // Правый нижний угол, Y

//--- Рисуем прямоугольник
   this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha));
   
//--- Координаты "галочки"
   int arrx[3], arry[3];
   
   arrx[0]=x1+(x2-x1)/4;      // X. Левая точка
   arrx[1]=x1+w/3;            // X. Центральная точка
   arrx[2]=x2-(x2-x1)/4;      // X. Правая точка
   
   arry[0]=y1+1+(y2-y1)/2;    // Y. Левая точка
   arry[1]=y2-(y2-y1)/3;      // Y. Центральная точка
   arry[2]=y1+(y2-y1)/3;      // Y. Правая точка
   
//--- Рисуем "галочку" линией двойной толщины
   this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha));
   arrx[0]++;
   arrx[1]++;
   arrx[2]++;
   this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha));
   
   if(update)
      this.m_canvas.Update(false);
   return true;
  }

Método que desenha um CheckBox desmarcado:

//+------------------------------------------------------------------+
//| CImagePainter::Рисует неотмеченный CheckBox                      |
//+------------------------------------------------------------------+
bool CImagePainter::UncheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Координаты прямоугольника
   int x1=x+1;                // Левый верхний угол, X
   int y1=y+1;                // Левый верхний угол, Y
   int x2=x+w-2;              // Правый нижний угол, X
   int y2=y+h-2;              // Правый нижний угол, Y

//--- Рисуем прямоугольник
   this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha));
   
   if(update)
      this.m_canvas.Update(false);
   return true;
  }

Método que desenha um RadioButton marcado:

//+------------------------------------------------------------------+
//| CImagePainter::Рисует отмеченный RadioButton                     |
//+------------------------------------------------------------------+
bool CImagePainter::CheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Координаты и радиус окружности
   int x1=x+1;                // Левый верхний угол области окружности, X
   int y1=y+1;                // Левый верхний угол области окружности, Y
   int x2=x+w-2;              // Правый нижний угол области окружности, X
   int y2=y+h-2;              // Правый нижний угол области окружности, Y
   
//--- Координаты и радиус окружности
   int d=::fmin(x2-x1,y2-y1); // Диаметр по меньшей стороне (ширина или высота)
   int r=d/2;                 // Радиус
   int cx=x1+r;               // Координата X центра
   int cy=y1+r;               // Координата Y центра

//--- Рисуем окружность
   this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha));
   
//--- Радиус "метки"
   r/=2;
   if(r<1)
      r=1;
//--- Рисуем метку
   this.m_canvas.FillCircle(cx, cy, r, ::ColorToARGB(clr, alpha));
   
   if(update)
      this.m_canvas.Update(false);
   return true;
  }

Método que desenha um RadioButton desmarcado:

//+------------------------------------------------------------------+
//| CImagePainter::Рисует неотмеченный RadioButton                   |
//+------------------------------------------------------------------+
bool CImagePainter::UncheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Координаты и радиус окружности
   int x1=x+1;                // Левый верхний угол области окружности, X
   int y1=y+1;                // Левый верхний угол области окружности, Y
   int x2=x+w-2;              // Правый нижний угол области окружности, X
   int y2=y+h-2;              // Правый нижний угол области окружности, Y
   
//--- Координаты и радиус окружности
   int d=::fmin(x2-x1,y2-y1); // Диаметр по меньшей стороне (ширина или высота)
   int r=d/2;                 // Радиус
   int cx=x1+r;               // Координата X центра
   int cy=y1+r;               // Координата Y центра

//--- Рисуем окружность
   this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha));
   
   if(update)
      this.m_canvas.Update(false);
   return true;
  }

Esses são métodos simples, que apenas fornecem a possibilidade de desenhar as figuras necessárias sem a necessidade de escrevê-las manualmente. Mais adiante, adicionaremos aqui outros métodos que desenham diferentes ícones para a formatação dos elementos gráficos.

Métodos para salvar a área de desenho em arquivo e carregá-la a partir de arquivo:

//+------------------------------------------------------------------+
//| CImagePainter::Сохранение в файл                                 |
//+------------------------------------------------------------------+
bool CImagePainter::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CBaseObj::Save(file_handle))
      return false;
  
//--- Сохраняем прозрачность
   if(::FileWriteInteger(file_handle,this.m_alpha,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем данные области
   if(!this.m_bound.Save(file_handle))
      return false;
      
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CImagePainter::Загрузка из файла                                 |
//+------------------------------------------------------------------+
bool CImagePainter::Load(const int file_handle)
  {
//--- Загружаем данные родительского объекта
   if(!CBaseObj::Load(file_handle))
      return false;
      
//--- Загружаем прозрачность
   this.m_alpha=(uchar)::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем данные области
   if(!this.m_bound.Load(file_handle))
      return false;
   
//--- Всё успешно
   return true;
  }

Agora já podemos prosseguir para as classes dos elementos de controle simples. O objeto mínimo desse tipo será a classe de rótulo de texto. A partir desse elemento serão herdadas as classes de outros elementos de controle.
Neste mesmo arquivo Controls.mqh continuaremos a escrever o código das classes.

Classe do elemento de controle "Rótulo de texto"

Nesta classe haverá um conjunto de variáveis e métodos que permitem trabalhar com qualquer elemento de controle, definir e obter os parâmetros do objeto, salvar e carregar as suas propriedades. A interatividade de todos os elementos de controle, o componente Controller, nós adicionamos hoje à classe base de todos os elementos de controle. Analisemos a classe do rótulo de texto:

//+------------------------------------------------------------------+
//| Класс текстовой метки                                            |
//+------------------------------------------------------------------+
class CLabel : public CCanvasBase
  {
protected:
   CImagePainter     m_painter;                                // Класс рисования
   ushort            m_text[];                                 // Текст
   ushort            m_text_prev[];                            // Прошлый текст
   int               m_text_x;                                 // Координата X текста (смещение относительно  левой границы объекта)
   int               m_text_y;                                 // Координата Y текста (смещение относительно  верхней границы объекта)
   
//--- (1) Устанавливает, (2) возвращает прошлый текст
   void              SetTextPrev(const string text)            { ::StringToShortArray(text,this.m_text_prev);  }
   string            TextPrev(void)                      const { return ::ShortArrayToString(this.m_text_prev);}
      
//--- Стирает текст
   void              ClearText(void);

public:
//--- Возвращает указатель на класс рисования
   CImagePainter    *Painter(void)                             { return &this.m_painter;           }
   
//--- (1) Устанавливает, (2) возвращает текст
   void              SetText(const string text)                { ::StringToShortArray(text,this.m_text);       }
   string            Text(void)                          const { return ::ShortArrayToString(this.m_text);     }
   
//--- Возвращает координату (1) X, (2) Y текста
   int               TextX(void)                         const { return this.m_text_x;                         }
   int               TextY(void)                         const { return this.m_text_y;                         }

//--- Устанавливает координату (1) X, (2) Y текста
   void              SetTextShiftH(const int x)                { this.m_text_x=x;                              }
   void              SetTextShiftV(const int y)                { this.m_text_y=y;                              }
   
//--- (1) Устанавливает координаты, (2) изменяет размеры области изображения
   void              SetImageXY(const int x,const int y)       { this.m_painter.SetXY(x,y);                    }
   void              SetImageSize(const int w,const int h)     { this.m_painter.SetSize(w,h);                  }
//--- Устанавливает координаты и размеры области изображения
   void              SetImageBound(const int x,const int y,const int w,const int h)
                       {
                        this.SetImageXY(x,y);
                        this.SetImageSize(w,h);
                       }
//--- Возвращает координату (1) X, (2) Y, (3) ширину, (4) высоту, (5) правую, (6) нижнюю границу области изображения
   int               ImageX(void)                        const { return this.m_painter.X();                    }
   int               ImageY(void)                        const { return this.m_painter.Y();                    }
   int               ImageWidth(void)                    const { return this.m_painter.Width();                }
   int               ImageHeight(void)                   const { return this.m_painter.Height();               }
   int               ImageRight(void)                    const { return this.m_painter.Right();                }
   int               ImageBottom(void)                   const { return this.m_painter.Bottom();               }

//--- Выводит текст
   void              DrawText(const int dx, const int dy, const string text, const bool chart_redraw);
   
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (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_LABEL);                   }
   
//--- Конструкторы/деструктор
                     CLabel(void);
                     CLabel(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CLabel(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CLabel(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h);
                    ~CLabel(void) {}
  };

Na classe estão definidos dois arrays de símbolos ushort, para o texto atual e para o texto anterior do rótulo. Isso permite, durante o desenho, ter acesso às dimensões do texto anterior e apagar corretamente a área coberta pelo texto antes de exibir o novo texto no canvas.

Analisemos os métodos declarados.

A classe possui quatro construtores, que permitem criar o objeto com diferentes conjuntos de parâmetros:

//+------------------------------------------------------------------+
//| CLabel::Конструктор по умолчанию. Строит метку в главном окне    |
//| текущего графика в координатах 0,0 с размерами по умолчанию      |
//+------------------------------------------------------------------+
CLabel::CLabel(void) : CCanvasBase("Label",::ChartID(),0,0,0,DEF_LABEL_W,DEF_LABEL_H), m_text_x(0), m_text_y(0)
  {
//--- Объекту рисования назначаем канвас переднего плана и
//--- обнуляем координаты и размеры, что делает его неактивным
   this.m_painter.CanvasAssign(this.GetForeground());
   this.m_painter.SetXY(0,0);
   this.m_painter.SetSize(0,0);
//--- Устанавливаем текущий и предыдущий текст
   this.SetText("Label");
   this.SetTextPrev("");
//--- Фон - прозрачный, передний план - нет
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
  }
//+------------------------------------------------------------------+
//| CLabel::Конструктор параметрический. Строит метку в главном окне |
//| текущего графика с указанными текстом, координами и размерами    |
//+------------------------------------------------------------------+
CLabel::CLabel(const string object_name, const string text,const int x,const int y,const int w,const int h) :
   CCanvasBase(object_name,::ChartID(),0,x,y,w,h), m_text_x(0), m_text_y(0)
  {
//--- Объекту рисования назначаем канвас переднего плана и
//--- обнуляем координаты и размеры, что делает его неактивным
   this.m_painter.CanvasAssign(this.GetForeground());
   this.m_painter.SetXY(0,0);
   this.m_painter.SetSize(0,0);
//--- Устанавливаем текущий и предыдущий текст
   this.SetText(text);
   this.SetTextPrev("");
//--- Фон - прозрачный, передний план - нет
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
  }
//+-------------------------------------------------------------------+
//| CLabel::Конструктор параметрический. Строит метку в указанном окне|
//| текущего графика с указанными текстом, координами и размерами     |
//+-------------------------------------------------------------------+
CLabel::CLabel(const string object_name, const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CCanvasBase(object_name,::ChartID(),wnd,x,y,w,h), m_text_x(0), m_text_y(0)
  {
//--- Объекту рисования назначаем канвас переднего плана и
//--- обнуляем координаты и размеры, что делает его неактивным
   this.m_painter.CanvasAssign(this.GetForeground());
   this.m_painter.SetXY(0,0);
   this.m_painter.SetSize(0,0);
//--- Устанавливаем текущий и предыдущий текст
   this.SetText(text);
   this.SetTextPrev("");
//--- Фон - прозрачный, передний план - нет
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
  }
//+-------------------------------------------------------------------+
//| CLabel::Конструктор параметрический. Строит метку в указанном окне|
//| указанного графика с указанными текстом, координами и размерами   |
//+-------------------------------------------------------------------+
CLabel::CLabel(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) :
   CCanvasBase(object_name,chart_id,wnd,x,y,w,h), m_text_x(0), m_text_y(0)
  {
//--- Объекту рисования назначаем канвас переднего плана и
//--- обнуляем координаты и размеры, что делает его неактивным
   this.m_painter.CanvasAssign(this.GetForeground());
   this.m_painter.SetXY(0,0);
   this.m_painter.SetSize(0,0);
//--- Устанавливаем текущий и предыдущий текст
   this.SetText(text);
   this.SetTextPrev("");
//--- Фон - прозрачный, передний план - нет
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
  }

A área de desenho, a ícone do elemento, é definida com dimensões zero, o que significa a ausência de ícone no elemento. O texto do elemento é definido e é atribuída transparência total ao fundo e opacidade total ao primeiro plano.

Método para comparação de dois objetos:

//+------------------------------------------------------------------+
//| CLabel::Сравнение двух объектов                                  |
//+------------------------------------------------------------------+
int CLabel::Compare(const CObject *node,const int mode=0) const
  {
   const CLabel *obj=node;
   switch(mode)
     {
      case ELEMENT_SORT_BY_NAME  :  return(this.Name()     >obj.Name()      ? 1 : this.Name()     <obj.Name()      ? -1 : 0);
      case ELEMENT_SORT_BY_TEXT  :  return(this.Text()     >obj.Text()      ? 1 : this.Text()     <obj.Text()      ? -1 : 0);
      case ELEMENT_SORT_BY_COLOR :  return(this.ForeColor()>obj.ForeColor() ? 1 : this.ForeColor()<obj.ForeColor() ? -1 : 0);
      case ELEMENT_SORT_BY_ALPHA :  return(this.AlphaFG()  >obj.AlphaFG()   ? 1 : this.AlphaFG()  <obj.AlphaFG()   ? -1 : 0);
      default                    :  return(this.ID()       >obj.ID()        ? 1 : this.ID()       <obj.ID()        ? -1 : 0);
     }
  }

A comparação é possível pelo nome do objeto, texto do rótulo, cor, transparência e identificador. Por padrão, os objetos são comparados pelo identificador do objeto, pois quando os objetos estão em uma mesma lista, é melhor distingui-los pelos identificadores para acesso rápido ao necessário.

Método que apaga o texto do rótulo:

//+------------------------------------------------------------------+
//| CLabel::Стирает текст                                            |
//+------------------------------------------------------------------+
void CLabel::ClearText(void)
  {
   int w=0, h=0;
   string text=this.TextPrev();
//--- Получаем размеры прошлого текста
   if(text!="")
      this.m_foreground.TextSize(text,w,h);
//--- Если размеры получены - рисуем на месте текста прозрачный прямоугольник, стирая текст
   if(w>0 && h>0)
      this.m_foreground.FillRectangle(this.AdjX(this.m_text_x),this.AdjY(this.m_text_y),this.AdjX(this.m_text_x+w),this.AdjY(this.m_text_y+h),clrNULL);
//--- Иначе - очищаем полностью весь передний план
   else
      this.m_foreground.Erase(clrNULL);
  }

Se anteriormente algum texto foi escrito, ele pode ser apagado preenchendo completamente a sua área com um retângulo totalmente transparente de acordo com o tamanho do texto. Se anteriormente não havia texto, toda a área do canvas do objeto é apagada.

Método que exibe o texto no canvas:

//+------------------------------------------------------------------+
//| CLabel::Выводит текст                                            |
//+------------------------------------------------------------------+
void CLabel::DrawText(const int dx,const int dy,const string text,const bool chart_redraw)
  {
//--- Очищаем прошлый текст и устанавливаем новый
   this.ClearText();
   this.SetText(text);
//--- Выводим установленный текст
   this.m_foreground.TextOut(this.AdjX(dx),this.AdjY(dy),this.Text(),::ColorToARGB(this.ForeColor(),this.AlphaFG()));
   
//--- Если текст выходит за правую границу объекта
   if(this.Width()-dx<this.m_foreground.TextWidth(text))
     {
      //--- Получаем размеры текста "троеточие"
      int w=0,h=0;
      this.m_foreground.TextSize("... ",w,h);
      if(w>0 && h>0)
        {
         //--- Стираем текст у правой границы объекта по размеру текста "троеточие" и заменяем троеточием окончание текста метки
         this.m_foreground.FillRectangle(this.AdjX(this.Width()-w),this.AdjY(this.m_text_y),this.AdjX(this.Width()),this.AdjY(this.m_text_y+h),clrNULL);
         this.m_foreground.TextOut(this.AdjX(this.Width()-w),this.AdjY(dy),"...",::ColorToARGB(this.ForeColor(),this.AlphaFG()));
        }
     }
//--- Обновляем канвас переднего плана и запоминаем новые координаты текста
   this.m_foreground.Update(chart_redraw);
   this.m_text_x=dx;
   this.m_text_y=dy;
//--- Запоминаем нарисованный текст как прошлый
   this.SetTextPrev(text);
  }

Aqui, primeiro o texto anterior é apagado do canvas e, em seguida, o novo texto é exibido. Se o novo texto ultrapassar os limites do objeto, no local em que ele extrapola os limites do elemento à direita é exibida uma reticência, indicando que o texto não coube na área do objeto, aproximadamente assim: "Este texto não cab...".

Método que desenha a aparência externa:

//+------------------------------------------------------------------+
//| CLabel::Рисует внешний вид                                       |
//+------------------------------------------------------------------+
void CLabel::Draw(const bool chart_redraw)
  {
   this.DrawText(this.m_text_x,this.m_text_y,this.Text(),chart_redraw);
  }

Aqui é simplesmente chamado o método de desenho do texto do rótulo.

Métodos de trabalho com arquivos:

//+------------------------------------------------------------------+
//| CLabel::Сохранение в файл                                        |
//+------------------------------------------------------------------+
bool CLabel::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CCanvasBase::Save(file_handle))
      return false;
  
//--- Сохраняем текст
   if(::FileWriteArray(file_handle,this.m_text)!=sizeof(this.m_text))
      return false;
//--- Сохраняем предыдущий текст
   if(::FileWriteArray(file_handle,this.m_text_prev)!=sizeof(this.m_text_prev))
      return false;
//--- Сохраняем координату X текста
   if(::FileWriteInteger(file_handle,this.m_text_x,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем координату Y текста
   if(::FileWriteInteger(file_handle,this.m_text_y,INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CLabel::Загрузка из файла                                        |
//+------------------------------------------------------------------+
bool CLabel::Load(const int file_handle)
  {
//--- Загружаем данные родительского объекта
   if(!CCanvasBase::Load(file_handle))
      return false;
      
//--- Загружаем текст
   if(::FileReadArray(file_handle,this.m_text)!=sizeof(this.m_text))
      return false;
//--- Загружаем предыдущий текст
   if(::FileReadArray(file_handle,this.m_text_prev)!=sizeof(this.m_text_prev))
      return false;
//--- Загружаем координату X текста
   this.m_text_x=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем координату Y текста
   this.m_text_y=::FileReadInteger(file_handle,INT_VALUE);
   
//--- Всё успешно
   return true;
  }

Com base na classe analisada, criaremos a classe de botão simples.

Classe do elemento de controle "Botão simples"

//+------------------------------------------------------------------+
//| Класс простой кнопки                                             |
//+------------------------------------------------------------------+
class CButton : public CLabel
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CLabel::Save(file_handle); }
   virtual bool      Load(const int file_handle)               { return CLabel::Load(file_handle); }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BUTTON);      }
   
//--- Конструкторы/деструктор
                     CButton(void);
                     CButton(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CButton(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CButton(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h);
                    ~CButton (void) {}
  };

A classe do botão simples difere da classe do rótulo de texto apenas pelo método de desenho da aparência externa.

A classe possui quatro construtores, que permitem criar um botão com os parâmetros especificados:

//+------------------------------------------------------------------+
//| CButton::Конструктор по умолчанию. Строит кнопку в главном окне  |
//| текущего графика в координатах 0,0 с размерами по умолчанию      |
//+------------------------------------------------------------------+
CButton::CButton(void) : CLabel("Button",::ChartID(),0,"Button",0,0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.SetState(ELEMENT_STATE_DEF);
   this.SetAlpha(255);
  }
//+-------------------------------------------------------------------+
//| CButton::Конструктор параметрический. Строит кнопку в главном окне|
//| текущего графика с указанными текстом, координами и размерами     |
//+-------------------------------------------------------------------+
CButton::CButton(const string object_name,const string text,const int x,const int y,const int w,const int h) :
   CLabel(object_name,::ChartID(),0,text,x,y,w,h)
  {
   this.SetState(ELEMENT_STATE_DEF);
   this.SetAlpha(255);
  }
//+---------------------------------------------------------------------+
//| CButton::Конструктор параметрический. Строит кнопку в указанном окне|
//| текущего графика с указанными текстом, координами и размерами       |
//+---------------------------------------------------------------------+
CButton::CButton(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CLabel(object_name,::ChartID(),wnd,text,x,y,w,h)
  {
   this.SetState(ELEMENT_STATE_DEF);
   this.SetAlpha(255);
  }
//+---------------------------------------------------------------------+
//| CButton::Конструктор параметрический. Строит кнопку в указанном окне|
//| указанного графика с указанными текстом, координами и размерами     |
//+---------------------------------------------------------------------+
CButton::CButton(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) :
   CLabel(object_name,chart_id,wnd,text,x,y,w,h)
  {
   this.SetState(ELEMENT_STATE_DEF);
   this.SetAlpha(255);
  }

Definimos o estado "botão não pressionado" e atribuímos opacidade total ao fundo e ao primeiro plano.

Método de comparação de dois objetos:

//+------------------------------------------------------------------+
//| CButton::Сравнение двух объектов                                 |
//+------------------------------------------------------------------+
int CButton::Compare(const CObject *node,const int mode=0) const
  {
   const CButton *obj=node;
   switch(mode)
     {
      case ELEMENT_SORT_BY_NAME  :  return(this.Name()     >obj.Name()      ? 1 : this.Name()     <obj.Name()      ? -1 : 0);
      case ELEMENT_SORT_BY_TEXT  :  return(this.Text()     >obj.Text()      ? 1 : this.Text()     <obj.Text()      ? -1 : 0);
      case ELEMENT_SORT_BY_COLOR :  return(this.BackColor()>obj.BackColor() ? 1 : this.BackColor()<obj.BackColor() ? -1 : 0);
      case ELEMENT_SORT_BY_ALPHA :  return(this.AlphaBG()  >obj.AlphaBG()   ? 1 : this.AlphaBG()  <obj.AlphaBG()   ? -1 : 0);
      default                    :  return(this.ID()       >obj.ID()        ? 1 : this.ID()       <obj.ID()        ? -1 : 0);
     }
  }

O método é idêntico ao método da classe do rótulo de texto. Muito provavelmente, se outras propriedades não surgirem para os botões, esse método poderá ser removido da classe e será utilizado o método da classe pai.

Método que desenha a aparência externa do botão:

//+------------------------------------------------------------------+
//| CButton::Рисует внешний вид                                      |
//+------------------------------------------------------------------+
void CButton::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
//--- Выводим текст кнопки
   CLabel::Draw(false);
      
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Primeiro, o fundo é preenchido com a cor definida, em seguida a borda é desenhada e o texto do botão é exibido.

Com base nessa classe, criaremos a classe do botão de duas posições.

Classe do elemento de controle "Botão de duas posições"

//+------------------------------------------------------------------+
//| Класс двухпозиционной кнопки                                     |
//+------------------------------------------------------------------+
class CButtonTriggered : public CButton
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Обработчик событий нажатий кнопок мышки (Press)
   virtual void      OnPressEvent(const int id, const long lparam, const double dparam, const string sparam);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CButton::Save(file_handle);      }
   virtual bool      Load(const int file_handle)               { return CButton::Load(file_handle);      }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BUTTON_TRIGGERED);  }
  
//--- Инициализация цветов объекта по умолчанию
   virtual void      InitColors(void);
   
//--- Конструкторы/деструктор
                     CButtonTriggered(void);
                     CButtonTriggered(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CButtonTriggered(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CButtonTriggered(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h);
                    ~CButtonTriggered (void) {}
  };

O objeto possui quatro construtores, que permitem criar um botão com os parâmetros especificados:

//+------------------------------------------------------------------+
//| CButtonTriggered::Конструктор по умолчанию.                      |
//| Строит кнопку в главном окне текущего графика                    |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CButtonTriggered::CButtonTriggered(void) : CButton("Button",::ChartID(),0,"Button",0,0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.InitColors();
  }
//+------------------------------------------------------------------+
//| CButtonTriggered::Конструктор параметрический.                   |
//| Строит кнопку в главном окне текущего графика                    |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonTriggered::CButtonTriggered(const string object_name,const string text,const int x,const int y,const int w,const int h) :
   CButton(object_name,::ChartID(),0,text,x,y,w,h)
  {
   this.InitColors();
  }
//+------------------------------------------------------------------+
//| CButtonTriggered::Конструктор параметрический.                   |
//| Строит кнопку в указанном окне текущего графика                  |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonTriggered::CButtonTriggered(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,::ChartID(),wnd,text,x,y,w,h)
  {
   this.InitColors();
  }
//+------------------------------------------------------------------+
//| CButtonTriggered::Конструктор параметрический.                   |
//| Строит кнопку в указанном окне указанного графика                |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonTriggered::CButtonTriggered(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) :
   CButton(object_name,chart_id,wnd,text,x,y,w,h)
  {
   this.InitColors();
  }

Em cada construtor é chamado o método de inicialização das cores padrão:

//+------------------------------------------------------------------+
//| CButtonTriggered::Инициализация цветов объекта по умолчанию      |
//+------------------------------------------------------------------+
void CButtonTriggered::InitColors(void)
  {
//--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона
   this.InitBackColors(clrWhiteSmoke);
   this.InitBackColorsAct(clrLightBlue);
   this.BackColorToDefault();
   
//--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста
   this.InitForeColors(clrBlack);
   this.InitForeColorsAct(clrBlack);
   this.ForeColorToDefault();
   
//--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки
   this.InitBorderColors(clrDarkGray);
   this.InitBorderColorsAct(clrGreen);
   this.BorderColorToDefault();
   
//--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента
   this.InitBorderColorBlocked(clrLightGray);
   this.InitForeColorBlocked(clrSilver);
  }

Estas são as cores padrão definidas para um botão recém-criado. Após a criação do objeto, todas as cores podem ser configuradas conforme desejado.

No método de comparaçãofoi adicionada a comparação pelo estado do botão:

//+------------------------------------------------------------------+
//| CButtonTriggered::Сравнение двух объектов                        |
//+------------------------------------------------------------------+
int CButtonTriggered::Compare(const CObject *node,const int mode=0) const
  {
   const CButtonTriggered *obj=node;
   switch(mode)
     {
      case ELEMENT_SORT_BY_NAME  :  return(this.Name()     >obj.Name()      ? 1 : this.Name()     <obj.Name()      ? -1 : 0);
      case ELEMENT_SORT_BY_TEXT  :  return(this.Text()     >obj.Text()      ? 1 : this.Text()     <obj.Text()      ? -1 : 0);
      case ELEMENT_SORT_BY_COLOR :  return(this.BackColor()>obj.BackColor() ? 1 : this.BackColor()<obj.BackColor() ? -1 : 0);
      case ELEMENT_SORT_BY_ALPHA :  return(this.AlphaBG()  >obj.AlphaBG()   ? 1 : this.AlphaBG()  <obj.AlphaBG()   ? -1 : 0);
      case ELEMENT_SORT_BY_STATE :  return(this.State()    >obj.State()     ? 1 : this.State()    <obj.State()     ? -1 : 0);
      default                    :  return(this.ID()       >obj.ID()        ? 1 : this.ID()       <obj.ID()        ? -1 : 0);
     }
  }

Método que desenha a aparência externa do botão:

//+------------------------------------------------------------------+
//| CButtonTriggered::Рисует внешний вид                             |
//+------------------------------------------------------------------+
void CButtonTriggered::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
//--- Выводим текст кнопки
   CLabel::Draw(false);
      
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

O método é idêntico ao método da classe pai e, se nenhuma modificação adicional da classe for realizada posteriormente, esse método poderá ser removido, sendo utilizado o método de desenho da classe pai.

O botão de duas posições possui dois estados:

  1. Pressionado,
  2. Não pressionado.

Para o rastreamento e a alternância de seus estados, aqui foi sobrescrito o manipulador de pressionamento dos botões do mouse OnPressEvent da classe pai:

//+------------------------------------------------------------------+
//| CButtonTriggered::Обработчик событий нажатий кнопок мышки (Press)|
//+------------------------------------------------------------------+
void CButtonTriggered::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Устанавливаем состояние кнопки, обратное уже установленному
   ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF);
   this.SetState(state);
   
//--- Вызываем обработчик родительского объекта с указанием идентификатора в lparam и состояния в dparam
   CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam);
  }

Com base na classe CButton criaremos quatro botões com setas: para cima, para baixo, para a esquerda e para a direita. Os objetos utilizarão a classe de desenho de imagens para desenhar as setas.

Classe do elemento de controle "Botão com seta para cima"

//+------------------------------------------------------------------+
//| Класс кнопки со стрелкой вверх                                   |
//+------------------------------------------------------------------+
class CButtonArrowUp : public CButton
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CButton::Save(file_handle);   }
   virtual bool      Load(const int file_handle)               { return CButton::Load(file_handle);   }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BUTTON_ARROW_UP);}
   
//--- Конструкторы/деструктор
                     CButtonArrowUp(void);
                     CButtonArrowUp(const string object_name, const int x, const int y, const int w, const int h);
                     CButtonArrowUp(const string object_name, const int wnd, const int x, const int y, const int w, const int h);
                     CButtonArrowUp(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CButtonArrowUp (void) {}
  };

Quatro construtores permitem criar um objeto com os parâmetros especificados:

//+------------------------------------------------------------------+
//| CButtonArrowUp::Конструктор по умолчанию.                        |
//| Строит кнопку в главном окне текущего графика                    |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CButtonArrowUp::CButtonArrowUp(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowUp::Конструктор параметрический.                     |
//| Строит кнопку в главном окне текущего графика                    |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowUp::CButtonArrowUp(const string object_name,const int x,const int y,const int w,const int h) :
   CButton(object_name,::ChartID(),0,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowUp::Конструктор параметрический.                     |
//| Строит кнопку в указанном окне текущего графика                  |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowUp::CButtonArrowUp(const string object_name,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,::ChartID(),wnd,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowUp::Конструктор параметрический.                     |
//| Строит кнопку в указанном окне указанного графика                |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowUp::CButtonArrowUp(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,chart_id,wnd,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }

Nos construtores são inicializadas as cores padrão e são definidas as coordenadas e os tamanhos da área da imagem.

Método que desenha a aparência externa do botão:

//+------------------------------------------------------------------+
//| CButtonArrowUp::Рисует внешний вид                               |
//+------------------------------------------------------------------+
void CButtonArrowUp::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
//--- Выводим текст кнопки
   CLabel::Draw(false);
//--- Очищаем область рисунка
   this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false);
//--- Задаём цвет стрелки для обычного и заблокированного состояний кнопки и рисуем стрелку вверх 
   color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor());
   this.m_painter.ArrowUp(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true);
      
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

O método é semelhante ao método de desenho do botão, porém adicionalmente é exibido o desenho da seta para cima por meio do método ArrowUp do objeto de desenho.

Todos os demais classes são idênticas à analisada, porém nos métodos de desenho são renderizados os ícones correspondentes à finalidade do botão.

Classe do elemento de controle "Botão com seta para baixo"

//+------------------------------------------------------------------+
//| Класс кнопки со стрелкой вниз                                    |
//+------------------------------------------------------------------+
class CButtonArrowDown : public CButton
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CButton::Save(file_handle);      }
   virtual bool      Load(const int file_handle)               { return CButton::Load(file_handle);      }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); }
   
//--- Конструкторы/деструктор
                     CButtonArrowDown(void);
                     CButtonArrowDown(const string object_name, const int x, const int y, const int w, const int h);
                     CButtonArrowDown(const string object_name, const int wnd, const int x, const int y, const int w, const int h);
                     CButtonArrowDown(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CButtonArrowDown (void) {}
  };
//+------------------------------------------------------------------+
//| CButtonArrowDown::Конструктор по умолчанию.                      |
//| Строит кнопку в главном окне текущего графика                    |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CButtonArrowDown::CButtonArrowDown(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowDown::Конструктор параметрический.                   |
//| Строит кнопку в главном окне текущего графика                    |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowDown::CButtonArrowDown(const string object_name,const int x,const int y,const int w,const int h) :
   CButton(object_name,::ChartID(),0,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowDown::Конструктор параметрический.                   |
//| Строит кнопку в указанном окне текущего графика                  |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowDown::CButtonArrowDown(const string object_name,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,::ChartID(),wnd,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowDown::Конструктор параметрический.                   |
//| Строит кнопку в указанном окне указанного графика                |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowDown::CButtonArrowDown(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,chart_id,wnd,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowDown::Рисует внешний вид                             |
//+------------------------------------------------------------------+
void CButtonArrowDown::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
//--- Выводим текст кнопки
   CLabel::Draw(false);
//--- Очищаем область рисунка
   this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false);
//--- Задаём цвет стрелки для обычного и заблокированного состояний кнопки и рисуем стрелку вниз
   color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor());
   this.m_painter.ArrowDown(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true);
      
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Classe do elemento de controle "Botão com seta para a esquerda"

//+------------------------------------------------------------------+
//| Класс кнопки со стрелкой влево                                   |
//+------------------------------------------------------------------+
class CButtonArrowLeft : public CButton
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CButton::Save(file_handle);      }
   virtual bool      Load(const int file_handle)               { return CButton::Load(file_handle);      }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); }
   
//--- Конструкторы/деструктор
                     CButtonArrowLeft(void);
                     CButtonArrowLeft(const string object_name, const int x, const int y, const int w, const int h);
                     CButtonArrowLeft(const string object_name, const int wnd, const int x, const int y, const int w, const int h);
                     CButtonArrowLeft(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CButtonArrowLeft (void) {}
  };
//+------------------------------------------------------------------+
//| CButtonArrowLeft::Конструктор по умолчанию.                      |
//| Строит кнопку в главном окне текущего графика                    |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CButtonArrowLeft::CButtonArrowLeft(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowLeft::Конструктор параметрический.                   |
//| Строит кнопку в главном окне текущего графика                    |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowLeft::CButtonArrowLeft(const string object_name,const int x,const int y,const int w,const int h) :
   CButton(object_name,::ChartID(),0,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowLeft::Конструктор параметрический.                   |
//| Строит кнопку в указанном окне текущего графика                  |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowLeft::CButtonArrowLeft(const string object_name,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,::ChartID(),wnd,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowLeft::Конструктор параметрический.                   |
//| Строит кнопку в указанном окне указанного графика                |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowLeft::CButtonArrowLeft(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,chart_id,wnd,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowLeft::Рисует внешний вид                             |
//+------------------------------------------------------------------+
void CButtonArrowLeft::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
//--- Выводим текст кнопки
   CLabel::Draw(false);
//--- Очищаем область рисунка
   this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false);
//--- Задаём цвет стрелки для обычного и заблокированного состояний кнопки и рисуем стрелку влево
   color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor());
   this.m_painter.ArrowLeft(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true);
      
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Classe do elemento de controle "Botão com seta para a direita"

//+------------------------------------------------------------------+
//| Класс кнопки со стрелкой вправо                                  |
//+------------------------------------------------------------------+
class CButtonArrowRight : public CButton
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CButton::Save(file_handle);      }
   virtual bool      Load(const int file_handle)               { return CButton::Load(file_handle);      }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); }
   
//--- Конструкторы/деструктор
                     CButtonArrowRight(void);
                     CButtonArrowRight(const string object_name, const int x, const int y, const int w, const int h);
                     CButtonArrowRight(const string object_name, const int wnd, const int x, const int y, const int w, const int h);
                     CButtonArrowRight(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CButtonArrowRight (void) {}
  };
//+------------------------------------------------------------------+
//| CButtonArrowRight::Конструктор по умолчанию.                     |
//| Строит кнопку в главном окне текущего графика                    |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CButtonArrowRight::CButtonArrowRight(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowRight::Конструктор параметрический.                  |
//| Строит кнопку в главном окне текущего графика                    |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowRight::CButtonArrowRight(const string object_name,const int x,const int y,const int w,const int h) :
   CButton(object_name,::ChartID(),0,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowRight::Конструктор параметрический.                  |
//| Строит кнопку в указанном окне текущего графика                  |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowRight::CButtonArrowRight(const string object_name,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,::ChartID(),wnd,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowRight::Конструктор параметрический.                  |
//| Строит кнопку в указанном окне указанного графика                |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowRight::CButtonArrowRight(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,chart_id,wnd,"",x,y,w,h)
  {
   this.InitColors();
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CButtonArrowRight::Рисует внешний вид                            |
//+------------------------------------------------------------------+
void CButtonArrowRight::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
//--- Выводим текст кнопки
   CLabel::Draw(false);
//--- Очищаем область рисунка
   this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false);
//--- Задаём цвет стрелки для обычного и заблокированного состояний кнопки и рисуем стрелку вправо
   color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor());
   this.m_painter.ArrowRight(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true);
      
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Classe do elemento de controle "Checkbox"

A classe do elemento de controle "checkbox" é semelhante às classes dos botões com setas, porém aqui o fundo será totalmente transparente. Ou seja, serão desenhados apenas o texto e o ícone do checkbox. O checkbox possui dois estados, marcado e desmarcado, portanto será herdado da classe de botão de duas posições:

//+------------------------------------------------------------------+
//| Класс элемента управления Checkbox                               |
//+------------------------------------------------------------------+
class CCheckBox : public CButtonTriggered
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CButton::Save(file_handle);   }
   virtual bool      Load(const int file_handle)               { return CButton::Load(file_handle);   }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_CHECKBOX);       }
  
//--- Инициализация цветов объекта по умолчанию
   virtual void      InitColors(void);
   
//--- Конструкторы/деструктор
                     CCheckBox(void);
                     CCheckBox(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CCheckBox(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CCheckBox(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h);
                    ~CCheckBox (void) {}
  };

Todas as classes dos elementos de controle possuem quatro construtores:

//+------------------------------------------------------------------+
//| CCheckBox::Конструктор по умолчанию.                             |
//| Строит кнопку в главном окне текущего графика                    |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CCheckBox::CCheckBox(void) : CButtonTriggered("CheckBox",::ChartID(),0,"CheckBox",0,0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
//--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана,
//--- и координаты и границы области рисунка значка кнопки
   this.InitColors();
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CCheckBox::Конструктор параметрический.                          |
//| Строит кнопку в главном окне текущего графика                    |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CCheckBox::CCheckBox(const string object_name,const string text,const int x,const int y,const int w,const int h) :
   CButtonTriggered(object_name,::ChartID(),0,text,x,y,w,h)
  {
//--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана,
//--- и координаты и границы области рисунка значка кнопки
   this.InitColors();
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CCheckBox::Конструктор параметрический.                          |
//| Строит кнопку в указанном окне текущего графика                  |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CCheckBox::CCheckBox(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CButtonTriggered(object_name,::ChartID(),wnd,text,x,y,w,h)
  {
//--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана,
//--- и координаты и границы области рисунка значка кнопки
   this.InitColors();
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }
//+------------------------------------------------------------------+
//| CCheckBox::Конструктор параметрический.                          |
//| Строит кнопку в указанном окне указанного графика                |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CCheckBox::CCheckBox(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) :
   CButtonTriggered(object_name,chart_id,wnd,text,x,y,w,h)
  {
//--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана,
//--- и координаты и границы области рисунка значка кнопки
   this.InitColors();
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);
  }

Aqui são inicializadas as cores padrão do objeto, é definido um fundo totalmente transparente e um primeiro plano opaco, e em seguida são configurados os tamanhos e as coordenadas da área da imagem.

O método de comparação retorna o resultado da chamada do método de comparação da classe pai:

//+------------------------------------------------------------------+
//| CCheckBox::Сравнение двух объектов                               |
//+------------------------------------------------------------------+
int CCheckBox::Compare(const CObject *node,const int mode=0) const
  {
   return CButtonTriggered::Compare(node,mode);
  }

Método de inicialização das cores padrão do objeto:

//+------------------------------------------------------------------+
//| CCheckBox::Инициализация цветов объекта по умолчанию             |
//+------------------------------------------------------------------+
void CCheckBox::InitColors(void)
  {
//--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона
   this.InitBackColors(clrNULL);
   this.InitBackColorsAct(clrNULL);
   this.BackColorToDefault();
   
//--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста
   this.InitForeColors(clrBlack);
   this.InitForeColorsAct(clrBlack);
   this.InitForeColorFocused(clrNavy);
   this.InitForeColorActFocused(clrNavy);
   this.ForeColorToDefault();
   
//--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки
   this.InitBorderColors(clrNULL);
   this.InitBorderColorsAct(clrNULL);
   this.BorderColorToDefault();

//--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента
   this.InitBorderColorBlocked(clrNULL);
   this.InitForeColorBlocked(clrSilver);
  }

Método de desenho da aparência externa do checkbox:

//+------------------------------------------------------------------+
//| CCheckBox::Рисует внешний вид                                    |
//+------------------------------------------------------------------+
void CCheckBox::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
//--- Выводим текст кнопки
   CLabel::Draw(false);
   
//--- Очищаем область рисунка
   this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false);
//--- Рисуем отмеченный значок для активного состояния кнопки,
   if(this.m_state)
      this.m_painter.CheckedBox(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
//--- и неотмеченный - для неактивного
   else
      this.m_painter.UncheckedBox(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
      
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Dependendo do estado do elemento, é desenhado um quadrado marcado com um visto ou apenas um quadrado vazio.

Agora, com base neste objeto, criaremos a classe do elemento de controle "radiobutton".

Classe do elemento de controle "Radiobutton"

Como o radiobutton sempre funciona em grupo, ele só pode ser desativado quando outro botão do grupo é ativado, portanto aqui também precisamos sobrescrever o manipulador de clique no objeto da classe pai.

//+------------------------------------------------------------------+
//| Класс элоемента управления Radio Button                          |
//+------------------------------------------------------------------+
class CRadioButton : public CCheckBox
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Обработчик событий нажатий кнопок мышки (Press)
   virtual void      OnPressEvent(const int id, const long lparam, const double dparam, const string sparam);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CButton::Save(file_handle);   }
   virtual bool      Load(const int file_handle)               { return CButton::Load(file_handle);   }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_RADIOBUTTON);    }
  
//--- Конструкторы/деструктор
                     CRadioButton(void);
                     CRadioButton(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CRadioButton(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CRadioButton(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h);
                    ~CRadioButton (void) {}
  };

Construtores:

//+------------------------------------------------------------------+
//| CRadioButton::Конструктор по умолчанию.                          |
//| Строит кнопку в главном окне текущего графика                    |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CRadioButton::CRadioButton(void) : CCheckBox("RadioButton",::ChartID(),0,"",0,0,DEF_BUTTON_H,DEF_BUTTON_H)
  {
  }
//+------------------------------------------------------------------+
//| CRadioButton::Конструктор параметрический.                       |
//| Строит кнопку в главном окне текущего графика                    |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CRadioButton::CRadioButton(const string object_name,const string text,const int x,const int y,const int w,const int h) :
   CCheckBox(object_name,::ChartID(),0,text,x,y,w,h)
  {
  }
//+------------------------------------------------------------------+
//| CRadioButton::Конструктор параметрический.                       |
//| Строит кнопку в указанном окне текущего графика                  |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CRadioButton::CRadioButton(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CCheckBox(object_name,::ChartID(),wnd,text,x,y,w,h)
  {
  }
//+------------------------------------------------------------------+
//| CRadioButton::Конструктор параметрический.                       |
//| Строит кнопку в указанном окне указанного графика                |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CRadioButton::CRadioButton(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) :
   CCheckBox(object_name,chart_id,wnd,text,x,y,w,h)
  {
  }

Nenhuma ação adicional após a chamada do construtor da classe pai é necessária aqui. Portanto, os construtores possuem corpo vazio.

O método de comparação retorna o resultado da chamada do método de comparação da classe pai:

//+------------------------------------------------------------------+
//| CRadioButton::Сравнение двух объектов                            |
//+------------------------------------------------------------------+
int CRadioButton::Compare(const CObject *node,const int mode=0) const
  {
   return CCheckBox::Compare(node,mode);
  }

Método de desenho da aparência externa do botão:

//+------------------------------------------------------------------+
//| CRadioButton::Рисует внешний вид                                 |
//+------------------------------------------------------------------+
void CRadioButton::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
//--- Выводим текст кнопки
   CLabel::Draw(false);
   
//--- Очищаем область рисунка
   this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false);
//--- Рисуем отмеченный значок для активного состояния кнопки,
   if(this.m_state)
      this.m_painter.CheckedRadioButton(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
//--- и неотмеченный - для неактивного
   else
      this.m_painter.UncheckedRadioButton(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
      
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Esse método é idêntico ao método da classe pai, porém aqui são desenhados os ícones do radiobutton, selecionado e não selecionado.

Manipulador de eventos de pressionamento dos botões do mouse:

//+------------------------------------------------------------------+
//| CRadioButton::Обработчик событий нажатий кнопок мышки (Press)    |
//+------------------------------------------------------------------+
void CRadioButton::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Если кнопка уже отмечена - уходим
   if(this.m_state)
      return;
//--- Устанавливаем состояние кнопки, обратное уже установленному
   ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF);
   this.SetState(state);
   
//--- Вызываем обработчик родительского объекта с указанием идентификатора в lparam и состояния в dparam
   CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam);
  }

Aqui, se o botão já estiver em estado ligado, não é necessário executar nenhuma ação — saímos do manipulador. Se o botão estiver desligado, invertemos o seu estado e chamamos o manipulador da classe pai, o objeto base de todos os elementos de controle CCanvasBase.

Por hoje, estes são todos os elementos de controle que minimamente era necessário implementar para a realização de elementos de controle complexos.
Vamos testar o que obtivemos.


Testando o resultado

Na pasta \MQL5\Indicators\Tables\ criaremos um novo indicador com o nome iTestLabel.mq5.

Definiremos o número de buffers de cálculo e de séries gráficas do indicador como zero — não é necessário desenhar nenhum gráfico. Conectaremos a biblioteca criada de elementos gráficos. O indicador irá desenhar elementos gráficos, que, no momento da criação, salvaremos em uma lista, cujo arquivo de classe está conectado ao arquivo do indicador:

//+------------------------------------------------------------------+
//|                                                   iTestLabel.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"
#property indicator_separate_window
#property indicator_buffers 0
#property indicator_plots   0

//+------------------------------------------------------------------+
//| Включаемые библиотеки                                            |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Controls\Controls.mqh"
  
CArrayObj         list;             // Список для хранения тестируемых объектов
CCanvasBase      *base =NULL;       // Указатель на базовый графический элемент
CLabel           *label1=NULL;      // Указатель на графический элемент Label
CLabel           *label2=NULL;      // Указатель на графический элемент Label
CLabel           *label3=NULL;      // Указатель на графический элемент Label
CButton          *button1=NULL;     // Указатель на графический элемент Button
CButtonTriggered *button_t1=NULL;   // Указатель на графический элемент ButtonTriggered
CButtonTriggered *button_t2=NULL;   // Указатель на графический элемент ButtonTriggered
CButtonArrowUp   *button_up=NULL;   // Указатель на графический элемент CButtonArrowUp
CButtonArrowDown *button_dn=NULL;   // Указатель на графический элемент CButtonArrowDown
CButtonArrowLeft *button_lt=NULL;   // Указатель на графический элемент CButtonArrowLeft
CButtonArrowRight*button_rt=NULL;   // Указатель на графический элемент CButtonArrowRight
CCheckBox        *checkbox_lt=NULL; // Указатель на графический элемент CCheckBox
CCheckBox        *checkbox_rt=NULL; // Указатель на графический элемент CCheckBox
CRadioButton     *radio_bt_lt=NULL; // Указатель на графический элемент CRadioButton
CRadioButton     *radio_bt_rt=NULL; // Указатель на графический элемент CRadioButton

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

Aqui, para simplificação, já são criados ponteiros para os elementos gráficos que serão criados, aos quais iremos nos referir após a criação do elemento para trabalhar com os objetos.

Todos os objetos serão criados no manipulador OnInit() do indicador. Faremos o seguinte: criaremos um objeto base e o coloriremos de modo que lembre algum tipo de painel.

Dentro dessa "base" criaremos todos os elementos gráficos e indicaremos esse objeto base como contêiner para eles.

Escreveremos em OnInit() o seguinte código:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Ищем подокно графика
   int wnd=ChartWindowFind();
//--- Создаём базовый графический элемент
   list.Add(base=new CCanvasBase("Rectangle",0,wnd,100,40,260,160));
   base.SetAlphaBG(250);      // Прозрачность
   base.SetBorderWidth(6);    // Ширина рамки
   
//--- Инициализируем цвет фона, указываем цвет для заблокированного элемента
//--- и делаем текущим цветом фона элемента цвет фона, заданный по умолчанию 
   base.InitBackColors(clrWhiteSmoke);
   base.InitBackColorBlocked(clrLightGray);
   base.BackColorToDefault();
   
//--- Заливаем цветом фон и рисуем рамку с отступом в один пиксель от установленной ширины рамки
   base.Fill(base.BackColor(),false);
   uint wd=base.BorderWidth();
   base.GetBackground().Rectangle(0,0,base.Width()-1,base.Height()-1,ColorToARGB(clrDimGray));
   base.GetBackground().Rectangle(wd-2,wd-2,base.Width()-wd+1,base.Height()-wd+1,ColorToARGB(clrLightGray));
   base.Update(false);
//--- Устанавливаем наименование и идентификатор элемента и выводим в журнал его описание
   base.SetName("Rectangle 1");
   base.SetID(1);
   base.Print();
   

//--- Внутри базового объекта создаём текстовую метку
//--- и указываем для метки в качестве контейнера базовый элемент
   string text="Simple button:";
   int shift_x=20;
   int shift_y=8;
   int x=base.X()+shift_x-10;
   int y=base.Y()+shift_y+2;
   int w=base.GetForeground().TextWidth(text);
   int h=DEF_LABEL_H;
   list.Add(label1=new CLabel("Label 1",0,wnd,text,x,y,w,h));
   label1.SetContainerObj(base);
//--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный
//--- (это изменение стандартных параметров текстовой метки после её создания).
   label1.InitForeColorFocused(clrRed);   
   label1.InitForeColorPressed(clrRed);
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   label1.SetID(2);
   label1.Draw(false);
   label1.Print();
   
   
//--- Внутри базового объекта создаём простую кнопку
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=label1.Right()+shift_x;
   y=label1.Y();
   w=DEF_BUTTON_W;
   h=DEF_BUTTON_H;
   list.Add(button1=new CButton("Simple Button",0,wnd,"Button 1",x,y,w,h));
   button1.SetContainerObj(base);
//--- Задаём смещение текста кнопки по оси X
   button1.SetTextShiftH(2);
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   button1.SetID(3);
   button1.Draw(false);
   button1.Print();
   
   
//--- Внутри базового объекта создаём текстовую метку
//--- и указываем для метки в качестве контейнера базовый элемент
   text="Triggered button:";
   x=label1.X();
   y=label1.Bottom()+shift_y;
   w=base.GetForeground().TextWidth(text);
   h=DEF_LABEL_H;
   list.Add(label2=new CLabel("Label 2",0,wnd,text,x,y,w,h));
   label2.SetContainerObj(base);
//--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный
//--- (это изменение стандартных параметров текстовой метки после её создания).
   label2.InitForeColorFocused(clrRed);
   label2.InitForeColorPressed(clrRed);
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   label2.SetID(4);
   label2.Draw(false);
   label2.Print();
   
   
//--- Внутри базового объекта создаём двухпозиционную кнопку
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=button1.X();
   y=button1.Bottom()+shift_y;
   w=DEF_BUTTON_W;
   h=DEF_BUTTON_H;
   list.Add(button_t1=new CButtonTriggered("Triggered Button 1",0,wnd,"Button 2",x,y,w,h));
   button_t1.SetContainerObj(base);

//--- Задаём смещение текста кнопки по оси X
   button_t1.SetTextShiftH(2);
//--- Устанавливаем идентификатор и активированное состояние элемента,
//--- рисуем элемент и выводим в журнал его описание.
   button_t1.SetID(5);
   button_t1.SetState(true);
   button_t1.Draw(false);
   button_t1.Print();
   
   
//--- Внутри базового объекта создаём двухпозиционную кнопку
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=button_t1.Right()+4;
   y=button_t1.Y();
   w=DEF_BUTTON_W;
   h=DEF_BUTTON_H;
   list.Add(button_t2=new CButtonTriggered("Triggered Button 2",0,wnd,"Button 3",x,y,w,h));
   button_t2.SetContainerObj(base);

//--- Задаём смещение текста кнопки по оси X
   button_t2.SetTextShiftH(2);
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   button_t2.SetID(6);
   button_t2.Draw(false);
   button_t2.Print();
   
   
//--- Внутри базового объекта создаём текстовую метку
//--- и указываем для метки в качестве контейнера базовый элемент
   text="Arrowed buttons:";
   x=label1.X();
   y=label2.Bottom()+shift_y;
   w=base.GetForeground().TextWidth(text);
   h=DEF_LABEL_H;
   list.Add(label3=new CLabel("Label 3",0,wnd,text,x,y,w,h));
   label3.SetContainerObj(base);
//--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный
//--- (это изменение стандартных параметров текстовой метки после её создания).
   label3.InitForeColorFocused(clrRed);
   label3.InitForeColorPressed(clrRed);
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   label3.SetID(7);
   label3.Draw(false);
   label3.Print();
   
   
//--- Внутри базового объекта создаём кнопку со стрелкой вверх
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=button1.X();
   y=button_t1.Bottom()+shift_y;
   w=DEF_BUTTON_H-1;
   h=DEF_BUTTON_H-1;
   list.Add(button_up=new CButtonArrowUp("Arrow Up Button",0,wnd,x,y,w,h));
   button_up.SetContainerObj(base);
//--- Задаём размеры и смещение изображения по оси X
   button_up.SetImageBound(1,1,w-4,h-3);
//--- Здесь можно настроить внешний вид кнопки, например, убрать рамку
   //button_up.InitBorderColors(button_up.BackColor(),button_up.BackColorFocused(),button_up.BackColorPressed(),button_up.BackColorBlocked());
   //button_up.ColorsToDefault();
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   button_up.SetID(8);
   button_up.Draw(false);
   button_up.Print();
   
   
//--- Внутри базового объекта создаём кнопку со стрелкой вниз
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=button_up.Right()+4;
   y=button_up.Y();
   w=DEF_BUTTON_H-1;
   h=DEF_BUTTON_H-1;
   list.Add(button_dn=new CButtonArrowDown("Arrow Down Button",0,wnd,x,y,w,h));
   button_dn.SetContainerObj(base);
//--- Задаём размеры и смещение изображения по оси X
   button_dn.SetImageBound(1,1,w-4,h-3);
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   button_dn.SetID(9);
   button_dn.Draw(false);
   button_dn.Print();
   
   
//--- Внутри базового объекта создаём кнопку со стрелкой влево
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=button_dn.Right()+4;
   y=button_up.Y();
   w=DEF_BUTTON_H-1;
   h=DEF_BUTTON_H-1;
   list.Add(button_lt=new CButtonArrowLeft("Arrow Left Button",0,wnd,x,y,w,h));
   button_lt.SetContainerObj(base);
//--- Задаём размеры и смещение изображения по оси X
   button_lt.SetImageBound(1,1,w-3,h-4);
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   button_lt.SetID(10);
   button_lt.Draw(false);
   button_lt.Print();
   
   
//--- Внутри базового объекта создаём кнопку со стрелкой вправо
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=button_lt.Right()+4;
   y=button_up.Y();
   w=DEF_BUTTON_H-1;
   h=DEF_BUTTON_H-1;
   list.Add(button_rt=new CButtonArrowRight("Arrow Right Button",0,wnd,x,y,w,h));
   button_rt.SetContainerObj(base);
//--- Задаём размеры и смещение изображения по оси X
   button_rt.SetImageBound(1,1,w-3,h-4);
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   button_rt.SetID(11);
   button_rt.Draw(false);
   button_rt.Print();
   
   
//--- Внутри базового объекта создаём чекбокс с заголовком справа (левый чекбокс)
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=label1.X();
   y=label3.Bottom()+shift_y;
   w=DEF_BUTTON_W+30;
   h=DEF_BUTTON_H;
   list.Add(checkbox_lt=new CCheckBox("CheckBoxL",0,wnd,"CheckBox L",x,y,w,h));
   checkbox_lt.SetContainerObj(base);
//--- Задаём координаты и размеры области изображения
   checkbox_lt.SetImageBound(2,1,h-2,h-2);
//--- Задаём смещение текста кнопки по оси X
   checkbox_lt.SetTextShiftH(checkbox_lt.ImageRight()+2);
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   checkbox_lt.SetID(12);
   checkbox_lt.Draw(false);
   checkbox_lt.Print();
   
   
//--- Внутри базового объекта создаём чекбокс с заголовком слева (правый чекбокс)
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=checkbox_lt.Right()+4;
   y=checkbox_lt.Y();
   w=DEF_BUTTON_W+30;
   h=DEF_BUTTON_H;
   list.Add(checkbox_rt=new CCheckBox("CheckBoxR",0,wnd,"CheckBox R",x,y,w,h));
   checkbox_rt.SetContainerObj(base);
//--- Задаём координаты и размеры области изображения
   checkbox_rt.SetTextShiftH(2);
//--- Задаём смещение текста кнопки по оси X
   checkbox_rt.SetImageBound(checkbox_rt.Width()-h+2,1,h-2,h-2);
//--- Устанавливаем идентификатор и активированное состояние элемента,
//--- рисуем элемент и выводим в журнал его описание.
   checkbox_rt.SetID(13);
   checkbox_rt.SetState(true);
   checkbox_rt.Draw(false);
   checkbox_rt.Print();
   
   
//--- Внутри базового объекта создаём радиокнопку с заголовком справа (левый RadioButton)
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=checkbox_lt.X();
   y=checkbox_lt.Bottom()+shift_y;
   w=DEF_BUTTON_W+46;
   h=DEF_BUTTON_H;
   list.Add(radio_bt_lt=new CRadioButton("RadioButtonL",0,wnd,"RadioButton L",x,y,w,h));
   radio_bt_lt.SetContainerObj(base);
//--- Задаём координаты и размеры области изображения
   radio_bt_lt.SetImageBound(2,1,h-2,h-2);
//--- Задаём смещение текста кнопки по оси X
   radio_bt_lt.SetTextShiftH(radio_bt_lt.ImageRight()+2);
//--- Устанавливаем идентификатор и активированное состояние элемента,
//--- рисуем элемент и выводим в журнал его описание.
   radio_bt_lt.SetID(14);
   radio_bt_lt.SetState(true);
   radio_bt_lt.Draw(false);
   radio_bt_lt.Print();
   
   
//--- Внутри базового объекта создаём радиокнопку с заголовком слева (правый RadioButton)
//--- и указываем для кнопки в качестве контейнера базовый элемент
   x=radio_bt_lt.Right()+4;
   y=radio_bt_lt.Y();
   w=DEF_BUTTON_W+46;
   h=DEF_BUTTON_H;
   list.Add(radio_bt_rt=new CRadioButton("RadioButtonR",0,wnd,"RadioButton R",x,y,w,h));
   radio_bt_rt.SetContainerObj(base);
//--- Задаём смещение текста кнопки по оси X
   radio_bt_rt.SetTextShiftH(2);
//--- Задаём координаты и размеры области изображения
   radio_bt_rt.SetImageBound(radio_bt_rt.Width()-h+2,1,h-2,h-2);
//--- Устанавливаем идентификатор элемента, рисуем элемент
//--- и выводим в журнал его описание.
   radio_bt_rt.SetID(15);
   radio_bt_rt.Draw(true);
   radio_bt_rt.Print();

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }

Estude atentamente todos os comentários do código — aqui todos os passos de criação dos objetos estão descritos de forma bastante detalhada.

No manipulador OnDeinit() do indicador destruímos todos os objetos da lista:

//+------------------------------------------------------------------+
//| Custom deindicator initialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   list.Clear();
  }

O manipulador OnCalculate() está vazio — não calculamos nem exibimos nada no gráfico:

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

Para dar vida aos elementos gráficos criados, é necessário, no manipulador OnChartEvent()percorrer a lista de objetos criados e chamar o manipulador homônimo para cada elemento. Como, por enquanto, as radiobotões não estão de nenhuma forma ligadas em grupos — isso será feito nos próximos artigos — emularemos a comutação das radiobotões como deveria ocorrer em um grupo de elementos:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Вызываем обработчик событий каждого из созданных объектов
   for(int i=0;i<list.Total();i++)
     {
      CCanvasBase *obj=list.At(i);
      if(obj!=NULL)
         obj.OnChartEvent(id,lparam,dparam,sparam);
     }
     
//--- Эмулируем работу радиокнопок в группе ---
//--- Если получено пользовательское событие
   if(id>=CHARTEVENT_CUSTOM)
     {
      //--- Если нажата левая радиокнопка
      if(sparam==radio_bt_lt.NameBG())
        {
         //--- Если состояние кнопки изменено (было не выбрано)
         if(radio_bt_lt.State())
           {
            //--- делаем правую радиокнопку невыбранной и перерисовываем её
            radio_bt_rt.SetState(false);
            radio_bt_rt.Draw(true);
           }
        }
      //--- Если нажата правая радиокнопка
      if(sparam==radio_bt_rt.NameBG())
        {
         //--- Если состояние кнопки изменено (было не выбрано)
         if(radio_bt_rt.State())
           {
            //--- делаем левую радиокнопку невыбранной и перерисовываем её
            radio_bt_lt.SetState(false);
            radio_bt_lt.Draw(true);
           }
        }
     }
  }

Vamos compilar o indicador e executá-lo no gráfico:

Todos os elementos de controle reagem à interação com o mouse, as radiobotões alternam como se estivessem agrupadas. As rótulos de texto foram feitas para mudar de cor ao passar o cursor, para uma apresentação visual clara de que os elementos de controle podem ser configurados conforme desejado. No estado normal, os textos dos rótulos são estáticos.

Mas aqui há uma omissão — ao passar o cursor do mouse sobre um elemento de controle, aparece um tooltip desnecessário com o nome do indicador. Para eliminar esse comportamento, é necessário escrever o valor "\n" na propriedade OBJPROP_TOOLTIP de cada objeto gráfico. Vamos corrigir isso.

Na classe CCanvasBase, no método Create adicionaremos duas linhas configurando os tooltips para os objetos gráficos de fundo e de primeiro plano:

//+------------------------------------------------------------------+
//| CCanvasBase::Создаёт графические объекты фона и переднего плана  |
//+------------------------------------------------------------------+
bool CCanvasBase::Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h)
  {
//--- Получаем скорректированный идентификатор графика
   long id=this.CorrectChartID(chart_id);
//--- Корректируем переданное имя для объекта
   string nm=object_name;
   ::StringReplace(nm," ","_");
//--- Создаём имя графического объекта для фона и создаём канвас
   string obj_name=nm+"_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=nm+"_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);
   ::ObjectSetString(id,this.NameBG(),OBJPROP_TOOLTIP,"\n");
   ::ObjectSetString(id,this.NameFG(),OBJPROP_TOOLTIP,"\n");

//--- Устанавливаем размеры прямоугольной области и возвращаем true
   this.m_bound.SetXY(x,y);
   this.m_bound.Resize(w,h);
   return true;
  }

Recompilaremos o indicador e verificaremos:

Agora tudo está correto.


Considerações finais

Hoje demos mais um passo na criação do elemento de controle Table Control. Todos os elementos de controle complexos serão montados a partir de objetos simples como estes, porém bastante funcionais.

Hoje adicionamos a todos os objetos o componente Controller, que permite ao usuário interagir de forma interativa com os elementos de controle e aos próprios elementos interagirem entre si.

No próximo artigo, prepararemos os elementos painel e contêiner, que são os principais componentes para a colocação de outros elementos dentro deles. Ao mesmo tempo, o contêiner oferece a possibilidade de rolagem dos elementos filhos em seu interior.

Programas utilizados no artigo:

#
 Nome Tipo
Descrição
 1  Base.mqh  Biblioteca de classes  Classes para a criação do objeto base dos elementos de controle
 2  Controls.mqh  Biblioteca de classes  Classes dos elementos de controle
 3  iTestLabel.mq5  Indicador de teste  Indicador para testar o trabalho com as classes de elementos de controle
 4  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  CColor  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

Classes que compõem a biblioteca Controls.mqh:

#
Nome
 Descrição
 1  CImagePainter  Classe para desenho de imagens em uma área definida por coordenadas e dimensões
 2  CLabel  Classe do elemento de controle "rótulo de texto"
 3  CButton  Classe do elemento de controle "botão simples"
 4  CButtonTriggered  Classe do elemento de controle "botão de duas posições"
 5  CButtonArrowUp  Classe do elemento de controle "botão com seta para cima"
 6
 CButtonArrowDown  Classe do elemento de controle "botão com seta para baixo"
 7  CButtonArrowLeft  Classe do elemento de controle "botão com seta para a esquerda"
 8  CButtonArrowRight  Classe do elemento de controle "botão com seta para a direita"
 9  CCheckBox  Classe do elemento de controle "checkbox"
 10  CRadioButton  Classe do elemento de controle "radiobutton"

Todos os arquivos criados são anexados ao artigo para estudo individual. O arquivo compactado pode ser extraído para a pasta do terminal, e todos os arquivos estarão localizados na pasta correta: \MQL5\Indicators\Tables.

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

Arquivos anexados |
Base.mqh (190.42 KB)
Controls.mqh (162.11 KB)
iTestLabel.mq5 (32.08 KB)
MQL5.zip (37.07 KB)
Redes neurais em trading: Extração eficiente de características para classificação precisa (Construção de objetos) Redes neurais em trading: Extração eficiente de características para classificação precisa (Construção de objetos)
Mantis é uma ferramenta universal para análise profunda de séries temporais, escalável de forma flexível para quaisquer cenários financeiros. Saiba como a combinação de patching, convoluções locais e atenção cruzada permite obter uma interpretação de alta precisão dos padrões de mercado.
Negociamos opções sem opções (Parte 1): Fundamentos da teoria e emulação por meio de ativos subjacentes Negociamos opções sem opções (Parte 1): Fundamentos da teoria e emulação por meio de ativos subjacentes
O artigo descreve uma variante de emulação de opções por meio do ativo subjacente, implementada na linguagem de programação MQL5. São comparadas as vantagens e desvantagens da abordagem escolhida em relação às opções reais negociadas em bolsa, usando como exemplo o mercado futuro FORTS da bolsa de Moscou MOEX e a corretora de criptomoedas Bybit.
Indicador do modelo CAPM no mercado Forex Indicador do modelo CAPM no mercado Forex
Adaptação do modelo clássico CAPM para o mercado cambial Forex em MQL5. O indicador calcula a rentabilidade esperada e o prêmio de risco com base na volatilidade histórica. Os indicadores aumentam nos picos e nas depressões, refletindo os princípios fundamentais de precificação. Aplicação prática para estratégias contra a tendência e de seguimento de tendência, levando em conta a dinâmica da relação entre risco e rentabilidade em tempo real. Inclui o aparato matemático e a implementação técnica.
Redes neurais em trading: Extração eficiente de características para classificação precisa (Conclusão) Redes neurais em trading: Extração eficiente de características para classificação precisa (Conclusão)
O framework Mantis transforma séries temporais complexas em tokens informativos e serve como uma base confiável para um Agente de trading inteligente, pronto para operar em tempo real.