English Русский
preview
Tabelas no paradigma MVC em MQL5: Integramos o componente Model ao componente View

Tabelas no paradigma MVC em MQL5: Integramos o componente Model ao componente View

MetaTrader 5Exemplos |
19 2
Artyom Trishkin
Artyom Trishkin

Sumário



Introdução

No artigo "Classes de tabela e cabeçalho baseadas no modelo da tabela em MQL5: aplicação do conceito MVC", concluímos a criação da classe do modelo da tabela (o componente Model segundo o MVC). Em seguida, trabalhamos no desenvolvimento de uma biblioteca de elementos de controle simples, a partir dos quais é possível criar elementos de controle muito diferentes em termos de finalidade e complexidade. Em particular, desenvolvemos o componente View para criar o elemento de controle TableView.

Este artigo será dedicado a implementar a interação entre os componentes Model e View. Em outras palavras, hoje vincularemos os dados tabulares à sua representação gráfica em um único elemento de controle.

O elemento de controle será criado com base na classe do objeto Panel e terá os seguintes elementos:

  1. Panel: base à qual anexamos o cabeçalho e a área de dados da tabela;
  2. Panel: cabeçalho da tabela, composto por uma série de elementos, como cabeçalhos de colunas criados com base na classe do objeto Button;
  3. Container: contêiner de dados tabulares com possibilidade de rolagem do conteúdo;
  4. Panel: painel para dispor as linhas da tabela, anexado ao contêiner do item 3. Quando esse painel ultrapassa os limites do contêiner, as barras de rolagem do contêiner permitem rolá-lo;
  5. Panel: linha da tabela, um painel usado para desenhar as células da tabela, anexado ao painel do item 4; criamos tantos objetos desse tipo quanto o número de linhas da tabela;
  6. Classe da célula da tabela: uma nova classe que permite desenhar em coordenadas especificadas em um canvas especificado (CCanvas). Anexamos um objeto dessa classe ao objeto de linha da tabela (item 5). Indicamos os canvases da linha da tabela como superfícies de desenho e desenhamos a célula nesse painel nas coordenadas especificadas. No objeto de linha da tabela (item 5), definimos a área de cada célula com uma instância da classe CBound e anexamos a ela o objeto da classe de célula da tabela.

Esse algoritmo de montagem das linhas e células da tabela divide cada linha em áreas horizontais. Como os objetos da classe de célula ficam anexados a essas áreas, podemos ordenar as células e alterar sua posição com facilidade, apenas reatribuindo-as às áreas necessárias da linha. 

Com base no que foi descrito acima, fica claro que precisamos de uma nova classe de objeto. Essa classe receberá um ponteiro para o elemento de controle, e desenharemos no canvas desse elemento. Atualmente, todos os objetos da biblioteca, quando instanciados com o operador new, criam seus próprios canvases de fundo e de primeiro plano. Precisamos, porém, de um objeto que permita definir e obter seu próprio tamanho, além de armazenar um ponteiro para o elemento de controle no qual vamos desenhar.

A classe CBound permite definir e obter as dimensões do objeto criado. Ela faz parte do objeto base de todos os elementos de controle. Esse mesmo objeto também cria os canvases de fundo e de primeiro plano. Portanto, precisamos criar outro objeto intermediário. Esse objeto herdará do objeto base e conterá uma instância da classe CBound para definir e obter dimensões. A classe que cria os objetos CCanvas herdará dele. A partir dessa classe intermediária, poderemos derivar a classe que receberá o ponteiro para o elemento de controle no qual vamos desenhar.

Primeiro, vamos aprimorar os arquivos das classes já criadas e, depois, escreveremos as novas.


Aprimorando as classes da biblioteca

Todos os arquivos da biblioteca em desenvolvimento estão localizados em \MQL5\Indicators\Tables\Controls. Caso eles ainda não existam, a versão anterior de todos os arquivos pode ser obtida no artigo anterior. Além disso, o projeto precisará do arquivo das classes de tabela (componente Model), que pode ser obtido no artigo em que concluímos seu desenvolvimento. O arquivo Tables.mqh deve ser salvo na pasta \MQL5\Indicators\Tables. Os arquivos Base.mqh e Control.mqh serão modificados.

Logo no início do arquivo Tables.mqh, na seção de macros, adicionaremos o identificador do arquivo:

//+------------------------------------------------------------------+
//| Макросы                                                          |
//+------------------------------------------------------------------+
#define  __TABLES__                 // Идентификатор данного файла
#define  MARKER_START_DATA    -1    // Маркер начала данных в файле
#define  MAX_STRING_LENGTH    128   // Максимальная длина строки в ячейке
#define  CELL_WIDTH_IN_CHARS  19    // Ширина ячейки таблицы в символах

Isso é necessário para que a macro MARKER_START_DATA possa ser declarada em dois arquivos de inclusão distintos e compilada separadamente em cada um deles.

Vamos abrir o arquivo Base.mqh. Incluiremos nele o arquivo das classes do modelo da tabela e adicionaremos a declaração prévia das novas classes:

//+------------------------------------------------------------------+
//|                                                         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
#include "..\Tables.mqh"

//--- Форвард-декларация классов элементов управления
class    CBoundedObj;                     // Базовый класс, хранящий размеры объекта
class    CCanvasBase;                     // Базовый класс холста графических элементов
class    CCounter;                        // Класс счётчика задержки
class    CAutoRepeat;                     // Класс автоповтора событий
class    CImagePainter;                   // Класс рисования изображений
class    CVisualHint;                     // Класс подсказки
class    CLabel;                          // Класс текстовой метки
class    CButton;                         // Класс простой кнопки
class    CButtonTriggered;                // Класс двухпозиционной кнопки
class    CButtonArrowUp;                  // Класс кнопки со стрелкой вверх
class    CButtonArrowDown;                // Класс кнопки со стрелкой вниз
class    CButtonArrowLeft;                // Класс кнопки со стрелкой влево
class    CButtonArrowRight;               // Класс кнопки со стрелкой вправо
class    CCheckBox;                       // Класс элемента управления CheckBox
class    CRadioButton;                    // Класс элемента управления RadioButton
class    CScrollBarThumbH;                // Класс ползунка горизонтальной полосы прокрутки
class    CScrollBarThumbV;                // Класс ползунка вертикальной полосы прокрутки
class    CScrollBarH;                     // Класс горизонтальной полосы прокрутки
class    CScrollBarV;                     // Класс вертикальной полосы прокрутки
class    CTableRowView;                   // Класс визуального представления строки таблицы
class    CPanel;                          // Класс элемента управления Panel
class    CGroupBox;                       // Класс элемента управления GroupBox
class    CContainer;                      // Класс элемента управления Container

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+

Na seção de macros, protegeremos a declaração da macro MARKER_START_DATA verificando se ela já foi definida:

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

Agora, a declaração da mesma macro em dois arquivos diferentes não causará erros de compilação, nem na compilação separada dos arquivos nem na compilação conjunta.

Na enumeração dos tipos de elementos gráficos, adicionaremos novos valores e definiremos o novo limite máximo do elemento ativo:

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
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_COUNTER,                  // Объект счётчика
   ELEMENT_TYPE_AUTOREPEAT_CONTROL,       // Объект автоповтора событий
   ELEMENT_TYPE_BOUNDED_BASE,             // Базовый объект размеров графических элементов
   ELEMENT_TYPE_CANVAS_BASE,              // Базовый объект холста графических элементов
   ELEMENT_TYPE_ELEMENT_BASE,             // Базовый объект графических элементов
   ELEMENT_TYPE_HINT,                     // Подсказка
   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
   ELEMENT_TYPE_SCROLLBAR_THUMB_H,        // Ползунок горизонтальной полосы прокрутки
   ELEMENT_TYPE_SCROLLBAR_THUMB_V,        // Ползунок вертикальной полосы прокрутки
   ELEMENT_TYPE_SCROLLBAR_H,              // Элемент управления ScrollBarHorisontal
   ELEMENT_TYPE_SCROLLBAR_V,              // Элемент управления ScrollBarVertical
   ELEMENT_TYPE_TABLE_CELL,               // Ячейка таблицы (View)
   ELEMENT_TYPE_TABLE_ROW,                // Строка таблицы (View)
   ELEMENT_TYPE_TABLE_COLUMN_CAPTION,     // Заголовок столбца таблицы (View)
   ELEMENT_TYPE_TABLE_HEADER,             // Заголовок таблицы (View)
   ELEMENT_TYPE_TABLE,                    // Таблица (View)
   ELEMENT_TYPE_PANEL,                    // Элемент управления Panel
   ELEMENT_TYPE_GROUPBOX,                 // Элемент управления GroupBox
   ELEMENT_TYPE_CONTAINER,                // Элемент управления Container
  };
#define  ACTIVE_ELEMENT_MIN   ELEMENT_TYPE_LABEL         // Минимальное значение списка активных элементов
#define  ACTIVE_ELEMENT_MAX   ELEMENT_TYPE_TABLE_HEADER  // Максимальное значение списка активных элементов

Adicionaremos novos valores à função que retorna o nome curto do elemento pelo tipo:

//+------------------------------------------------------------------+
//|  Возвращает короткое имя элемента по типу                        |
//+------------------------------------------------------------------+
string ElementShortName(const ENUM_ELEMENT_TYPE type)
  {
   switch(type)
     {
      case ELEMENT_TYPE_ELEMENT_BASE         :  return "BASE";    // Базовый объект графических элементов
      case ELEMENT_TYPE_HINT                 :  return "HNT";     // Подсказка
      case ELEMENT_TYPE_LABEL                :  return "LBL";     // Текстовая метка
      case ELEMENT_TYPE_BUTTON               :  return "SBTN";    // Простая кнопка
      case ELEMENT_TYPE_BUTTON_TRIGGERED     :  return "TBTN";    // Двухпозиционная кнопка
      case ELEMENT_TYPE_BUTTON_ARROW_UP      :  return "BTARU";   // Кнопка со стрелкой вверх
      case ELEMENT_TYPE_BUTTON_ARROW_DOWN    :  return "BTARD";   // Кнопка со стрелкой вниз
      case ELEMENT_TYPE_BUTTON_ARROW_LEFT    :  return "BTARL";   // Кнопка со стрелкой влево
      case ELEMENT_TYPE_BUTTON_ARROW_RIGHT   :  return "BTARR";   // Кнопка со стрелкой вправо
      case ELEMENT_TYPE_CHECKBOX             :  return "CHKB";    // Элемент управления CheckBox
      case ELEMENT_TYPE_RADIOBUTTON          :  return "RBTN";    // Элемент управления RadioButton
      case ELEMENT_TYPE_SCROLLBAR_THUMB_H    :  return "THMBH";   // Ползунок горизонтальной полосы прокрутки
      case ELEMENT_TYPE_SCROLLBAR_THUMB_V    :  return "THMBV";   // Ползунок вертикальной полосы прокрутки
      case ELEMENT_TYPE_SCROLLBAR_H          :  return "SCBH";    // Элемент управления ScrollBarHorisontal
      case ELEMENT_TYPE_SCROLLBAR_V          :  return "SCBV";    // Элемент управления ScrollBarVertical
      case ELEMENT_TYPE_TABLE_CELL           :  return "TCELL";   // Ячейка таблицы (View)
      case ELEMENT_TYPE_TABLE_ROW            :  return "TROW";    // Строка таблицы (View)
      case ELEMENT_TYPE_TABLE_COLUMN_CAPTION :  return "TCAPT";   // Заголовок столбца таблицы (View)
      case ELEMENT_TYPE_TABLE_HEADER         :  return "THDR";    // Заголовок таблицы (View)
      case ELEMENT_TYPE_TABLE                :  return "TABLE";   // Таблица (View)
      case ELEMENT_TYPE_PANEL                :  return "PNL";     // Элемент управления Panel
      case ELEMENT_TYPE_GROUPBOX             :  return "GRBX";    // Элемент управления GroupBox
      case ELEMENT_TYPE_CONTAINER            :  return "CNTR";    // Элемент управления Container
      default                                :  return "Unknown"; // Unknown
     }
  }

Na classe base dos elementos gráficos, tornaremos virtual o método de definição do identificador, pois nas novas classes será necessário redefini-lo:

//+------------------------------------------------------------------+
//| Базовый класс графических элементов                              |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   int               m_id;                                     // Идентифткатор
   ushort            m_name[];                                 // Наименование
   
public:
//--- Устанавливает (1) наименование, (2) идентификатор
   void              SetName(const string name)                { ::StringToShortArray(name,this.m_name);    }
   virtual 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) {}
  };

Cada objeto contém uma instância da classe de área retangular CBound, que retorna o tamanho do elemento de controle. Além disso, cada elemento de controle possui uma lista de objetos CBound. Essa lista armazena vários objetos CBound, e cada um deles permite definir uma área na superfície do elemento gráfico.

Cada uma dessas áreas pode ser usada em diferentes cenários. Por exemplo, é possível definir uma zona dentro do elemento gráfico, rastrear se o cursor está dentro dela e reagir à interação do cursor do mouse com essa área. Também é possível colocar um elemento de controle em uma área específica para exibi-lo ali. Para isso, é necessário permitir que o objeto CBound armazene um ponteiro para o elemento de controle.

Implementaremos esse recurso na classe CBound:

//+------------------------------------------------------------------+
//| Класс прямоугольной области                                      |
//+------------------------------------------------------------------+
class CBound : public CBaseObj
  {
protected:
   CBaseObj         *m_assigned_obj;                           // Назначенный на область объект
   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);                           }
   
//--- (1) Назначает, (2) возвращает указатель на назначенный элемент
   void              AssignObject(CBaseObj *obj)               { this.m_assigned_obj=obj;                                     }
   CBaseObj         *GetAssignedObj(void)                      { return this.m_assigned_obj;                                  }
   
//--- Возвращает описание объекта
   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); }
  };

No momento, o objeto da área retangular CBound está declarado na classe CCanvasBase. Essa classe cria dois objetos CCanvas para desenhar o fundo e o primeiro plano do elemento gráfico. Ou seja, todos os elementos gráficos contêm dois canvases. Mas precisamos criar uma classe que permita informar em quais canvases de um elemento de controle o desenho será feito. Essa classe também não deve criar canvases próprios nem romper com o conceito geral de criação dos elementos gráficos.

Portanto, precisamos mover o objeto da classe CBound de CCanvasBase para outra classe: uma classe pai que não criará canvases e da qual CCanvasBase herdará. Já em CCanvasBase, em vez de declarar duas instâncias de CCanvas, declararemos ponteiros para os canvases criados. Também criaremos um método para instanciar esses dois objetos CCanvas. Além disso, é necessário declarar um sinalizador que indique se o elemento gráfico gerencia os canvases de fundo e de primeiro plano. Esse sinalizador controla a destruição dos canvases criados.

No arquivo Base.mqh, logo após a classe CAutoRepeat e antes da classe CCanvasBase, declararemos a nova classe CBoundedObj, derivada de CBaseObj. Para essa nova classe, moveremos todos os métodos de manipulação do objeto CBound que estavam em CCanvasBase:

//+------------------------------------------------------------------+
//| Базовый класс, хранящий размеры объекта                          |
//+------------------------------------------------------------------+
class CBoundedObj : public CBaseObj
  {
protected:
   CBound            m_bound;                                  // Границы объекта
   bool              m_canvas_owner;                           // Флаг владения канвасами
public:
//--- Возвращает координаты, размеры и границы объекта
   int               X(void)                             const { return this.m_bound.X();                                                          }
   int               Y(void)                             const { return this.m_bound.Y();                                                          }
   int               Width(void)                         const { return this.m_bound.Width();                                                      }
   int               Height(void)                        const { return this.m_bound.Height();                                                     }
   int               Right(void)                         const { return this.m_bound.Right();                                                      }
   int               Bottom(void)                        const { return this.m_bound.Bottom();                                                     }

//--- Изменяет (1) ширину, (2) высоту, (3) размер ограничивающего прямоугольника
   void              BoundResizeW(const int size)              { this.m_bound.ResizeW(size);                                                       }
   void              BoundResizeH(const int size)              { this.m_bound.ResizeH(size);                                                       }
   void              BoundResize(const int w,const int h)      { this.m_bound.Resize(w,h);                                                         }
   
//--- Устанавливает координату (1) X, (2) Y, (3) обе координаты ограничивающего прямоугольника
   void              BoundSetX(const int x)                    { this.m_bound.SetX(x);                                                             }
   void              BoundSetY(const int y)                    { this.m_bound.SetY(y);                                                             }
   void              BoundSetXY(const int x,const int y)       { this.m_bound.SetXY(x,y);                                                          }
   
//--- (1) Устанавливает, (2) смещает ограничивающий прямоугольник на указанные координаты/размер смещения
   void              BoundMove(const int x,const int y)        { this.m_bound.Move(x,y);                                                           }
   void              BoundShift(const int dx,const int dy)     { this.m_bound.Shift(dx,dy);                                                        }
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (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_BOUNDED_BASE); }
                     
                     CBoundedObj (void) : m_canvas_owner(true) {}
                     CBoundedObj (const string user_name,const int id,const int x,const int y,const int w,const int h);
                    ~CBoundedObj (void){}
  };
//+------------------------------------------------------------------+
//| CBoundedObj::Конструктор                                         |
//+------------------------------------------------------------------+
CBoundedObj::CBoundedObj(const string user_name,const int id,const int x,const int y,const int w,const int h) : m_canvas_owner(true)
  {
//--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y
   this.m_bound.SetName(user_name);
   this.m_bound.SetID(id);
   this.m_bound.SetXY(x,y);
   this.m_bound.Resize(w,h);
  }
//+------------------------------------------------------------------+
//| CBoundedObj::Сохранение в файл                                   |
//+------------------------------------------------------------------+
bool CBoundedObj::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CBaseObj::Save(file_handle))
      return false;
 
//--- Сохраняем флаг владения канвасами
   if(::FileWriteInteger(file_handle,this.m_canvas_owner,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем размеры
   return this.m_bound.Save(file_handle);
  }
//+------------------------------------------------------------------+
//| CBoundedObj::Загрузка из файла                                   |
//+------------------------------------------------------------------+
bool CBoundedObj::Load(const int file_handle)
  {
//--- Загружаем данные родительского объекта
   if(!CBaseObj::Load(file_handle))
      return false;
   
//--- Загружаем флаг владения канвасами
   this.m_canvas_owner=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем размеры
   return this.m_bound.Load(file_handle);
  }

A classe apresentada herda as propriedades da classe base CBaseObj, permite especificar os limites do objeto (classe CBound) e possui um sinalizador de gerenciamento dos canvases. 

A classe de canvas base dos elementos gráficos agora deve herdar da nova CBoundedObj, e não da classe CBaseObj, como acontecia antes. Já transferimos para CBoundedObj tudo o que estava relacionado à manipulação do objeto da classe CBound, usada para definir e obter as dimensões do elemento. Também removemos esse código de CCanvasBase. Agora, os canvases não são declarados como instâncias da classe, mas como ponteiros para os objetos CCanvas criados:

//+------------------------------------------------------------------+
//| Базовый класс холста графических элементов                       |
//+------------------------------------------------------------------+
class CCanvasBase : public CBoundedObj
  {
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:
   CCanvas          *m_background;                             // Канвас для рисования фона
   CCanvas          *m_foreground;                             // Канвас для рисования переднего плана

   CCanvasBase      *m_container;                              // Родительский объект-контейнер
   CColorElement     m_color_background;                       // Объект управления цветом фона
   CColorElement     m_color_foreground;                       // Объект управления цветом переднего плана
   CColorElement     m_color_border;                           // Объект управления цветом рамки

As rotinas para obter as coordenadas e dimensões dos objetos gráficos dos canvases passaram a ser públicas. Já os métodos de redimensionamento dos canvases passaram a ser virtuais:

//--- Возврат координат, границ и размеров графического объекта
public:
   int               ObjectX(void)                       const { return this.m_obj_x;                                                              }
   int               ObjectY(void)                       const { return this.m_obj_y;                                                              }
   int               ObjectWidth(void)                   const { return this.m_background.Width();                                                 }
   int               ObjectHeight(void)                  const { return this.m_background.Height();                                                }
   int               ObjectRight(void)                   const { return this.ObjectX()+this.ObjectWidth()-1;                                       }
   int               ObjectBottom(void)                  const { return this.ObjectY()+this.ObjectHeight()-1;                                      }
   
//--- Изменяет (1) ширину, (2) высоту, (3) размер графического объекта
protected:
   virtual bool      ObjectResizeW(const int size);
   virtual bool      ObjectResizeH(const int size);
   bool              ObjectResize(const int w,const int h);
   
//--- Устанавливает координату (1) X, (2) Y, (3) обе координаты графического объекта
   virtual bool      ObjectSetX(const int x);
   virtual bool      ObjectSetY(const int y);
   bool              ObjectSetXY(const int x,const int y)      { return(this.ObjectSetX(x) && this.ObjectSetY(y));                                 }

Na seção protegida da classe, declaramos o método que cria os canvases:

//--- Устанавливает указатель на родительский объект-контейнер
   void              SetContainerObj(CCanvasBase *obj);
   
protected:
//--- Создаёт канвасы фона и переднего плана
   bool              CreateCanvasObjects(void);
//--- Создаёт 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); 
public:
//--- (1) Устанавливает, (2) возвращает состояние
   void              SetState(ENUM_ELEMENT_STATE state)        { this.m_state=state; this.ColorsToDefault();                                       }
   ENUM_ELEMENT_STATE State(void)                        const { return this.m_state;                                                              }

Tornamos virtual o método de definição do sinalizador que permite recortar o elemento pelos limites do contêiner:

//--- Устанавливает объекту флаг (1) перемещаемости, (2) главного объекта, (3) возможности изменения размеров
//--- обрезки по границам контейнера
   void              SetMovable(const bool flag)               { this.m_movable=flag;                                                              }
   void              SetAsMain(void)                           { this.m_main=true;                                                                 }
   virtual void      SetResizable(const bool flag)             { this.m_resizable=flag;                                                            }
   void              SetAutorepeat(const bool flag)            { this.m_autorepeat_flag=flag;                                                      }
   void              SetScrollable(const bool flag)            { this.m_scroll_flag=flag;                                                          }
   virtual void      SetTrimmered(const bool flag)             { this.m_trim_flag=flag;                                                            }

O construtor da classe agora chama o método que cria os canvases de fundo e de primeiro plano:

//+------------------------------------------------------------------+
//| 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_movable(false), m_resizable(false), m_main(false), m_autorepeat_flag(false), m_trim_flag(true), m_scroll_flag(false),
   m_border_width_lt(0), m_border_width_rt(0), m_border_width_up(0), m_border_width_dn(0), m_z_order(0),
   m_state(0), m_cursor_delta_x(0), m_cursor_delta_y(0)
  {
//--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y
//--- между верхней рамкой подокна индикатора и верхней рамкой главного окна графика
   this.m_chart_id=this.CorrectChartID(chart_id);
   
//--- Если не удалось создать канвасы - уходим
   if(!this.CreateCanvasObjects())
      return;
      
//--- Если графический ресурс и графический объект созданы
   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();
     }
  }

Método que cria os canvases de fundo e de primeiro plano:

//+------------------------------------------------------------------+
//| CCanvasBase::Создаёт канвасы фона и переднего плана              |
//+------------------------------------------------------------------+
bool CCanvasBase::CreateCanvasObjects(void)
  {
//--- Если оба канваса уже созданы, или класс не управляет канвасами - возвращаем true
   if((this.m_background!=NULL && this.m_foreground!=NULL) || !this.m_canvas_owner)
      return true;
//--- Создаём канвас фона
   this.m_background=new CCanvas();
   if(this.m_background==NULL)
     {
      ::PrintFormat("%s: Error! Failed to create background canvas",__FUNCTION__);
      return false;
     }
//--- Создаём канвас переднего плана
   this.m_foreground=new CCanvas();
   if(this.m_foreground==NULL)
     {
      ::PrintFormat("%s: Error! Failed to create foreground canvas",__FUNCTION__);
      return false;
     }
//--- Всё успешно
   return true;
  }

No método que destrói o objeto, agora verificamos o sinalizador de gerenciamento dos canvases:

//+------------------------------------------------------------------+
//| CCanvasBase::Уничтожает объект                                   |
//+------------------------------------------------------------------+
void CCanvasBase::Destroy(void)
  {
   if(this.m_canvas_owner)
     {
      this.m_background.Destroy();
      this.m_foreground.Destroy();
      delete this.m_background;
      delete this.m_foreground;
      this.m_background=NULL;
      this.m_foreground=NULL;
     }
  }

O objeto só destrói os canvases quando os gerencia. Se receber canvases de outro elemento de controle para desenhar, ele não os destruirá. Apenas o objeto proprietário deve destruir esses canvases.

No método padrão de inicialização da classe, definimos no objeto o sinalizador de gerenciamento dos canvases:

//+------------------------------------------------------------------+
//| 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();
//--- Инициализируем миллисекундный таймер
   ::EventSetMillisecondTimer(16);
   
//--- Флаг владения канвасами
   this.m_canvas_owner=true;
  }

Esse sinalizador só deverá ser redefinido como false nos objetos sem canvas próprios que herdarão da classe CBoundedObj.

Agora vamos abrir o arquivo Controls.mqh e fazer os ajustes necessários.

Na seção de macros, declararemos duas novas macros para especificar a altura padrão da linha e do cabeçalho da tabela:

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define  DEF_LABEL_W                50          // Ширина текстовой метки по умолчанию
#define  DEF_LABEL_H                16          // Высота текстовой метки по умолчанию
#define  DEF_BUTTON_W               60          // Ширина кнопки по умолчанию
#define  DEF_BUTTON_H               16          // Высота кнопки по умолчанию
#define  DEF_TABLE_ROW_H            16          // Высота строки таблицы по умолчанию
#define  DEF_TABLE_HEADER_H         20          // Высота заголовка таблицы по умолчанию
#define  DEF_PANEL_W                80          // Ширина панели по умолчанию
#define  DEF_PANEL_H                80          // Высота панели по умолчанию
#define  DEF_PANEL_MIN_W            60          // Минимальная ширина панели
#define  DEF_PANEL_MIN_H            60          // Минимальная высота панели
#define  DEF_SCROLLBAR_TH           13          // Толщина полосы прокрутки по умолчанию
#define  DEF_THUMB_MIN_SIZE         8           // Минимальная толщина ползунка полосы прокрутки
#define  DEF_AUTOREPEAT_DELAY       500         // Задержка перед запуском автоповтора
#define  DEF_AUTOREPEAT_INTERVAL    100         // Частота автоповторов

O arquivo contém a classe CListObj, uma lista encadeada de objetos usada para armazenar elementos gráficos. Nós a copiamos para este arquivo a partir do arquivo das classes do modelo da tabela. Se mantivermos o mesmo nome da classe, haverá conflito de nomes, pois as duas listas armazenam objetos completamente distintos em arquivos diferentes. Portanto, aqui manteremos a classe sem alterações, mas a renomearemos. Essa será a lista encadeada de elementos gráficos:

//+------------------------------------------------------------------+
//| Класс связанного списка графических элементов                    |
//+------------------------------------------------------------------+
class CListElm : public CList
  {
protected:
   ENUM_ELEMENT_TYPE m_element_type;   // Тип создаваемого объекта в CreateElement()
public:
//--- Установка типа элемента
   void              SetElementType(const ENUM_ELEMENT_TYPE type) { this.m_element_type=type;   }
   
//--- Виртуальный метод (1) загрузки списка из файла, (2) создания элемента списка
   virtual bool      Load(const int file_handle);
   virtual CObject  *CreateElement(void);
  };

Em todo o arquivo, substituiremos todas as ocorrências de "CListObj" por "CListElm".

No método de criação do elemento da lista, acrescentaremos os novos tipos de elementos gráficos:

//+------------------------------------------------------------------+
//| Метод создания элемента списка                                   |
//+------------------------------------------------------------------+
CObject *CListElm::CreateElement(void)
  {
//--- В зависимости от типа объекта в m_element_type, создаём новый объект
   switch(this.m_element_type)
     {
      case ELEMENT_TYPE_BASE                 :  return new CBaseObj();           // Базовый объект графических элементов
      case ELEMENT_TYPE_COLOR                :  return new CColor();             // Объект цвета
      case ELEMENT_TYPE_COLORS_ELEMENT       :  return new CColorElement();      // Объект цветов элемента графического объекта
      case ELEMENT_TYPE_RECTANGLE_AREA       :  return new CBound();             // Прямоугольная область элемента
      case ELEMENT_TYPE_IMAGE_PAINTER        :  return new CImagePainter();      // Объект для рисования изображений
      case ELEMENT_TYPE_CANVAS_BASE          :  return new CCanvasBase();        // Базовый объект холста графических элементов
      case ELEMENT_TYPE_ELEMENT_BASE         :  return new CElementBase();       // Базовый объект графических элементов
      case ELEMENT_TYPE_HINT                 :  return new CVisualHint();        // Подсказка
      case ELEMENT_TYPE_LABEL                :  return new CLabel();             // Текстовая метка
      case ELEMENT_TYPE_BUTTON               :  return new CButton();            // Простая кнопка
      case ELEMENT_TYPE_BUTTON_TRIGGERED     :  return new CButtonTriggered();   // Двухпозиционная кнопка
      case ELEMENT_TYPE_BUTTON_ARROW_UP      :  return new CButtonArrowUp();     // Кнопка со стрелкой вверх
      case ELEMENT_TYPE_BUTTON_ARROW_DOWN    :  return new CButtonArrowDown();   // Кнопка со стрелкой вниз
      case ELEMENT_TYPE_BUTTON_ARROW_LEFT    :  return new CButtonArrowLeft();   // Кнопка со стрелкой влево
      case ELEMENT_TYPE_BUTTON_ARROW_RIGHT   :  return new CButtonArrowRight();  // Кнопка со стрелкой вправо
      case ELEMENT_TYPE_CHECKBOX             :  return new CCheckBox();          // Элемент управления CheckBox
      case ELEMENT_TYPE_RADIOBUTTON          :  return new CRadioButton();       // Элемент управления RadioButton
      case ELEMENT_TYPE_TABLE_CELL           :  return new CTableCellView();     // Ячейка таблицы (View)
      case ELEMENT_TYPE_TABLE_ROW            :  return new CTableRowView();      // Строка таблицы (View)
      case ELEMENT_TYPE_TABLE_COLUMN_CAPTION :  return new CColumnCaptionView(); // Заголовок столбца таблицы (View)
      case ELEMENT_TYPE_TABLE_HEADER         :  return new CTableHeaderView();   // Заголовок таблицы (View)
      case ELEMENT_TYPE_TABLE                :  return new CTableView();         // Таблица (View)
      case ELEMENT_TYPE_PANEL                :  return new CPanel();             // Элемент управления Panel
      case ELEMENT_TYPE_GROUPBOX             :  return new CGroupBox();          // Элемент управления GroupBox
      case ELEMENT_TYPE_CONTAINER            :  return new CContainer();         // Элемент управления GroupBox
      default                                :  return NULL;
     }
  }

Na classe base do elemento gráfico, adicionaremos um método que retorna um ponteiro para o objeto:

public:
//--- Возвращает себя
   CElementBase     *GetObject(void)                           { return &this;                     }
//--- Возвращает указатель на (1) класс рисования, (2) список подсказок
   CImagePainter    *Painter(void)                             { return &this.m_painter;           }
   CListElm         *GetListHints(void)                        { return &this.m_list_hints;        }

Na classe de rótulo de texto, tornaremos virtual o método de exibição do texto no canvas:

//--- Устанавливает координату (1) X, (2) Y текста
   void              SetTextShiftH(const int x)                { this.ClearText(); this.m_text_x=x;            }
   void              SetTextShiftV(const int y)                { this.ClearText(); this.m_text_y=y;            }
   
//--- Выводит текст
   virtual void      DrawText(const int dx, const int dy, const string text, const bool chart_redraw);
   
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

Na classe do painel CPanel, ajustaremos os métodos de redimensionamento:

//+------------------------------------------------------------------+
//| CPanel::Изменяет ширину объекта                                  |
//+------------------------------------------------------------------+
bool CPanel::ResizeW(const int w)
  {
   if(!this.ObjectResizeW(w))
      return false;
   this.BoundResizeW(w);
   this.SetImageSize(w,this.Height());
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
//--- Получаем указатель на базовый элемент и, если он есть, и его тип - контейнер,
//--- проверяем отношение размеров текущего элемента относительно размеров контейнера
//--- для отображения полос прокрутки в контейнере при необходимости
   CContainer *base=this.GetContainer();
   if(base!=NULL && base.Type()==ELEMENT_TYPE_CONTAINER)
      base.CheckElementSizes(&this);
      
//--- В цикле по присоединённым элементам обрезаем каждый элемент по границам контейнера
   int total=this.m_list_elm.Total();
   for(int i=0;i<total;i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.ObjectTrim();
     }
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CPanel::Изменяет высоту объекта                                  |
//+------------------------------------------------------------------+
bool CPanel::ResizeH(const int h)
  {
   if(!this.ObjectResizeH(h))
      return false;
   this.BoundResizeH(h);
   this.SetImageSize(this.Width(),h);
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
//--- Получаем указатель на базовый элемент и, если он есть, и его тип - контейнер,
//--- проверяем отношение размеров текущего элемента относительно размеров контейнера
//--- для отображения полос прокрутки в контейнере при необходимости
   CContainer *base=this.GetContainer();
   if(base!=NULL && base.Type()==ELEMENT_TYPE_CONTAINER)
      base.CheckElementSizes(&this);
      
//--- В цикле по присоединённым элементам обрезаем каждый элемент по границам контейнера
   int total=this.m_list_elm.Total();
   for(int i=0;i<total;i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.ObjectTrim();
     }
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CPanel::Изменяет размеры объекта                                 |
//+------------------------------------------------------------------+
bool CPanel::Resize(const int w,const int h)
  {
   if(!this.ObjectResize(w,h))
      return false;
   this.BoundResize(w,h);
   this.SetImageSize(w,h);
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
//--- Получаем указатель на базовый элемент и, если он есть, и его тип - контейнер,
//--- проверяем отношение размеров текущего элемента относительно размеров контейнера
//--- для отображения полос прокрутки в контейнере при необходимости
   CContainer *base=this.GetContainer();
   if(base!=NULL && base.Type()==ELEMENT_TYPE_CONTAINER)
      base.CheckElementSizes(&this);
      
//--- В цикле по присоединённым элементам обрезаем каждый элемент по границам контейнера
   int total=this.m_list_elm.Total();
   for(int i=0;i<total;i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.ObjectTrim();
     }
//--- Всё успешно
   return true;
  }

A lógica desses ajustes é a seguinte: quando o elemento faz parte de um contêiner e suas dimensões mudam, é preciso compará-las com os limites do contêiner. Se o elemento ficar maior que esses limites, ele será recortado de acordo com as dimensões do contêiner, como já havia sido implementado. Além disso, as barras de rolagem devem aparecer no contêiner. Adicionamos essa verificação às rotinas de redimensionamento do elemento gráfico usando CheckElementSizes(), do objeto contêiner.

No método de desenho da aparência do objeto de painel na classe CPanel, desenhamos a borda somente quando pelo menos um dos quatro lados tem BorderWidth diferente de zero:

//+------------------------------------------------------------------+
//| CPanel::Рисует внешний вид                                       |
//+------------------------------------------------------------------+
void CPanel::Draw(const bool chart_redraw)
  {
//--- Заливаем объект цветом фона
   this.Fill(this.BackColor(),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_dark =(this.BackColor()==clrNULL ? this.BackColor() : this.GetBackColorControl().NewColor(this.BackColor(),-20,-20,-20));
   color clr_light=(this.BackColor()==clrNULL ? this.BackColor() : this.GetBackColorControl().NewColor(this.BackColor(),  6,  6,  6));
   if(this.BorderWidthBottom()+this.BorderWidthLeft()+this.BorderWidthRight()+this.BorderWidthTop()!=0)
      this.m_painter.FrameGroupElements(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),
                                        this.m_painter.Width(),this.m_painter.Height(),this.Text(),
                                        this.ForeColor(),clr_dark,clr_light,this.AlphaFG(),true);
   
//--- Обновляем канвас фона без перерисовки графика
   this.m_background.Update(false);
   
//--- Рисуем элементы списка
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL && elm.Type()!=ELEMENT_TYPE_SCROLLBAR_H && elm.Type()!=ELEMENT_TYPE_SCROLLBAR_V)
         elm.Draw(false);
     }
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

No método que cria e adiciona um novo elemento à lista da classe do objeto de painel, adicionaremos a criação dos novos elementos de controle:

//+------------------------------------------------------------------+
//| CPanel::Создаёт и добавляет новый элемент в список               |
//+------------------------------------------------------------------+
CElementBase *CPanel::InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h)
  {
//--- Создаём имя графического объекта
   int elm_total=this.m_list_elm.Total();
   string obj_name=this.NameFG()+"_"+ElementShortName(type)+(string)elm_total;
//--- Рассчитываем координаты
   int x=this.X()+dx;
   int y=this.Y()+dy;
//--- В зависимости от типа объекта, создаём новый объект
   CElementBase *element=NULL;
   switch(type)
     {
      case ELEMENT_TYPE_LABEL                :  element = new CLabel(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);             break;   // Текстовая метка
      case ELEMENT_TYPE_BUTTON               :  element = new CButton(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);            break;   // Простая кнопка
      case ELEMENT_TYPE_BUTTON_TRIGGERED     :  element = new CButtonTriggered(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Двухпозиционная кнопка
      case ELEMENT_TYPE_BUTTON_ARROW_UP      :  element = new CButtonArrowUp(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);     break;   // Кнопка со стрелкой вверх
      case ELEMENT_TYPE_BUTTON_ARROW_DOWN    :  element = new CButtonArrowDown(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Кнопка со стрелкой вниз
      case ELEMENT_TYPE_BUTTON_ARROW_LEFT    :  element = new CButtonArrowLeft(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Кнопка со стрелкой влево
      case ELEMENT_TYPE_BUTTON_ARROW_RIGHT   :  element = new CButtonArrowRight(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);  break;   // Кнопка со стрелкой вправо
      case ELEMENT_TYPE_CHECKBOX             :  element = new CCheckBox(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);          break;   // Элемент управления CheckBox
      case ELEMENT_TYPE_RADIOBUTTON          :  element = new CRadioButton(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);       break;   // Элемент управления RadioButton
      case ELEMENT_TYPE_SCROLLBAR_THUMB_H    :  element = new CScrollBarThumbH(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Полоса прокрутки горизонтального ScrollBar
      case ELEMENT_TYPE_SCROLLBAR_THUMB_V    :  element = new CScrollBarThumbV(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Полоса прокрутки вертикального ScrollBar
      case ELEMENT_TYPE_SCROLLBAR_H          :  element = new CScrollBarH(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);        break;   // Элемент управления горизонтальный ScrollBar
      case ELEMENT_TYPE_SCROLLBAR_V          :  element = new CScrollBarV(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);        break;   // Элемент управления вертикальный ScrollBar
      case ELEMENT_TYPE_TABLE_ROW            :  element = new CTableRowView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);      break;   // Объект визуального представления строки таблицы
      case ELEMENT_TYPE_TABLE_COLUMN_CAPTION :  element = new CColumnCaptionView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break;   // Объект визуального представления заголовка столбца таблицы
      case ELEMENT_TYPE_TABLE_HEADER         :  element = new CTableHeaderView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Объект визуального представления заголовка таблицы
      case ELEMENT_TYPE_TABLE                :  element = new CTableView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);         break;   // Объект визуального представления таблицы
      case ELEMENT_TYPE_PANEL                :  element = new CPanel(obj_name,"",this.m_chart_id,this.m_wnd,x,y,w,h);               break;   // Элемент управления Panel
      case ELEMENT_TYPE_GROUPBOX             :  element = new CGroupBox(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);          break;   // Элемент управления GroupBox
      case ELEMENT_TYPE_CONTAINER            :  element = new CContainer(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);         break;   // Элемент управления Container
      default                                :  element = NULL;
     }

//--- Если новый элемент не создан - сообщаем об этом и возвращаем NULL
   if(element==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create graphic element %s",__FUNCTION__,ElementDescription(type));
      return NULL;
     }
//--- Устанавливаем идентификатор, имя, контейнер и z-order элемента
   element.SetID(elm_total);
   element.SetName(user_name);
   element.SetContainerObj(&this);
   element.ObjectSetZOrder(this.ObjectZOrder()+1);
   
//--- Если созданный элемент не добавлен в список - сообщаем об этом, удаляем созданный элемент и возвращаем NULL
   if(!this.AddNewElement(element))
     {
      ::PrintFormat("%s: Error. Failed to add %s element with ID %d to list",__FUNCTION__,ElementDescription(type),element.ID());
      delete element;
      return NULL;
     }
//--- Получаем родительский элемент, к которому привязаны дочерние
   CElementBase *elm=this.GetContainer();
//--- Если родительский элемент имеет тип "Контейнер", значит, у него есть полосы прокрутки
   if(elm!=NULL && elm.Type()==ELEMENT_TYPE_CONTAINER)
     {
      //--- Преобразуем CElementBase в CContainer
      CContainer *container_obj=elm;
      //--- Если горизонтальная полоса прокрутки видима,
      if(container_obj.ScrollBarHorzIsVisible())
        {
         //--- получаем указатель на горизонтальный скроллбар и переносим его на передний план
         CScrollBarH *sbh=container_obj.GetScrollBarH();
         if(sbh!=NULL)
            sbh.BringToTop(false);
        }
      //--- Если вертикальная полоса прокрутки видима,
      if(container_obj.ScrollBarVertIsVisible())
        {
         //--- получаем указатель на вертикальный скроллбар и переносим его на передний план
         CScrollBarV *sbv=container_obj.GetScrollBarV();
         if(sbv!=NULL)
            sbv.BringToTop(false);
        }
     }
//--- Возвращаем указатель на созданный и присоединённый элемент
   return element;
  }

No método que coloca o objeto no primeiro plano, adicionaremos o recorte dos objetos anexados de acordo com as dimensões do contêiner:

//+------------------------------------------------------------------+
//| CPanel::Помещает объект на передний план                         |
//+------------------------------------------------------------------+
void CPanel::BringToTop(const bool chart_redraw)
  {
//--- Помещаем панель на передний план
   CCanvasBase::BringToTop(false);
//--- Помещаем на передний план прикреплённые объекты
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
        {
         if(elm.Type()==ELEMENT_TYPE_SCROLLBAR_H || elm.Type()==ELEMENT_TYPE_SCROLLBAR_V)
            continue;
         elm.BringToTop(false);
         elm.ObjectTrim();
        }
     }
//--- Если указано - перерисовываем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Na classe da barra de rolagem horizontal, há um sinalizador que indica se o recorte do elemento pelos limites do contêiner está habilitado. A barra de rolagem é composta por vários objetos, e todos eles devem receber o mesmo valor desse sinalizador. Faremos isso em uma única rotina. Na classe, redefiniremos o método que define o sinalizador de recorte pelos limites do contêiner:

//+------------------------------------------------------------------+
//| Класс горизонтальной полосы прокрутки                            |
//+------------------------------------------------------------------+
class CScrollBarH : public CPanel
  {
protected:
   CButtonArrowLeft *m_butt_left;                              // Кнопка со стрелкой влево
   CButtonArrowRight*m_butt_right;                             // Кнопка со стрелкой вправо
   CScrollBarThumbH *m_thumb;                                  // Ползунок скроллбара

public:
//--- Возвращает указатель на (1) левую, (2) правую кнопку, (3) ползунок
   CButtonArrowLeft *GetButtonLeft(void)                       { return this.m_butt_left;                                              }
   CButtonArrowRight*GetButtonRight(void)                      { return this.m_butt_right;                                             }
   CScrollBarThumbH *GetThumb(void)                            { return this.m_thumb;                                                  }

//--- (1) Устанавливает, (2) возвращает флаг обновления графика
   void              SetChartRedrawFlag(const bool flag)       { if(this.m_thumb!=NULL) this.m_thumb.SetChartRedrawFlag(flag);         }
   bool              ChartRedrawFlag(void)               const { return(this.m_thumb!=NULL ? this.m_thumb.ChartRedrawFlag() : false);  }

//--- Возвращает (1) длину (2) начало трека, (3) позицию ползунка
   int               TrackLength(void)    const;
   int               TrackBegin(void)     const;
   int               ThumbPosition(void)  const;
   
//--- Устанавливает позицию ползунка
   bool              SetThumbPosition(const int pos)     const { return(this.m_thumb!=NULL ? this.m_thumb.MoveX(pos) : false);         }
//--- Изменяет размер ползунка
   bool              SetThumbSize(const uint size)       const { return(this.m_thumb!=NULL ? this.m_thumb.ResizeW(size) : false);      }

//--- Изменяет ширину объекта
   virtual bool      ResizeW(const int size);
   
//--- Устанавливает флаг видимости в контейнере
   virtual void      SetVisibleInContainer(const bool flag);
   
//--- Устанавливает флаг обрезки по границам контейнера
   virtual void      SetTrimmered(const bool flag);

//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);
   
//--- Тип объекта
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_SCROLLBAR_H);                                     }
   
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(void);
   virtual void      InitColors(void);
   
//--- Обработчик прокрутки колёсика (Wheel)
   virtual void      OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam);

//--- Конструкторы/деструктор
                     CScrollBarH(void);
                     CScrollBarH(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CScrollBarH(void) {}
  };

Implementação do método:

//+------------------------------------------------------------------+
//| CScrollBarH::Устанавливает флаг обрезки по границам контейнера   |
//+------------------------------------------------------------------+
void CScrollBarH::SetTrimmered(const bool flag)
  {
   this.m_trim_flag=flag;
   if(this.m_butt_left!=NULL)
      this.m_butt_left.SetTrimmered(flag);
   if(this.m_butt_right!=NULL)
      this.m_butt_right.SetTrimmered(flag);
   if(this.m_thumb!=NULL)
      this.m_thumb.SetTrimmered(flag);
  }

O método define o sinalizador recebido em cada objeto que compõe a barra de rolagem.

No método de inicialização do objeto, agora não é mais necessário definir o sinalizador de recorte pelos limites do contêiner em cada elemento. Basta chamar SetTrimmered() ao final.

//+------------------------------------------------------------------+
//| CScrollBarH::Инициализация                                       |
//+------------------------------------------------------------------+
void CScrollBarH::Init(void)
  {
//--- Инициализация родительского класса
   CPanel::Init();
//--- Фон - непрозрачный
   this.SetAlphaBG(255);
//--- Ширина рамки и текст
   this.SetBorderWidth(0);
   this.SetText("");

//--- Создаём кнопки прокрутки
   int w=this.Height();
   int h=this.Height();
   this.m_butt_left = this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_LEFT, "","ButtL",0,0,w,h);
   this.m_butt_right= this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_RIGHT,"","ButtR",this.Width()-w,0,w,h);
   if(this.m_butt_left==NULL || this.m_butt_right==NULL)
     {
      ::PrintFormat("%s: Init failed",__FUNCTION__);
      return;
     }
//--- Настраиваем цвета и вид кнопки со стрелкой влево
   this.m_butt_left.SetImageBound(1,1,w-2,h-4);
   this.m_butt_left.InitBackColors(this.m_butt_left.BackColorFocused());
   this.m_butt_left.ColorsToDefault();
   this.m_butt_left.InitBorderColors(this.BorderColor(),this.m_butt_left.BackColorFocused(),this.m_butt_left.BackColorPressed(),this.m_butt_left.BackColorBlocked());
   this.m_butt_left.ColorsToDefault();

//--- Настраиваем цвета и вид кнопки со стрелкой вправо
   this.m_butt_right.SetImageBound(1,1,w-2,h-4);
   this.m_butt_right.InitBackColors(this.m_butt_right.BackColorFocused());
   this.m_butt_right.ColorsToDefault();
   this.m_butt_right.InitBorderColors(this.BorderColor(),this.m_butt_right.BackColorFocused(),this.m_butt_right.BackColorPressed(),this.m_butt_right.BackColorBlocked());
   this.m_butt_right.ColorsToDefault();
 
//--- Создаём ползунок
   int tsz=this.Width()-w*2;
   this.m_thumb=this.InsertNewElement(ELEMENT_TYPE_SCROLLBAR_THUMB_H,"","ThumbH",w,1,tsz-w*4,h-2);
   if(this.m_thumb==NULL)
     {
      ::PrintFormat("%s: Init failed",__FUNCTION__);
      return;
     }
//--- Настраиваем цвета ползунка и устанавливаем ему флаг перемещаемости
   this.m_thumb.InitBackColors(this.m_thumb.BackColorFocused());
   this.m_thumb.ColorsToDefault();
   this.m_thumb.InitBorderColors(this.m_thumb.BackColor(),this.m_thumb.BackColorFocused(),this.m_thumb.BackColorPressed(),this.m_thumb.BackColorBlocked());
   this.m_thumb.ColorsToDefault();
   this.m_thumb.SetMovable(true);

//--- Запрещаем самостоятельную перерисовку графика
   this.m_thumb.SetChartRedrawFlag(false);
   
//--- Изначально в контейнере не отображается и не обрезается по его границам
   this.SetVisibleInContainer(false);
   this.SetTrimmered(false);
  }

Exatamente os mesmos ajustes foram feitos também para a classe da barra de rolagem vertical. Eles são idênticos aos descritos acima, portanto não os repetiremos.

Na classe do objeto contêiner, tornaremos o método CheckElementSizes público:

//+------------------------------------------------------------------+
//| Класс Контейнер                                                  |
//+------------------------------------------------------------------+
class CContainer : public CPanel
  {
private:
   bool              m_visible_scrollbar_h;                    // Флаг видимости горизонтальной полосы прокрутки
   bool              m_visible_scrollbar_v;                    // Флаг видимости вертикальной полосы прокрутки
   int               m_init_border_size_top;                   // Изначальный размер рамки сверху
   int               m_init_border_size_bottom;                // Изначальный размер рамки снизу
   int               m_init_border_size_left;                  // Изначальный размер рамки слева
   int               m_init_border_size_right;                 // Изначальный размер рамки справа
   
//--- Возвращает тип элемента, отправившего событие
   ENUM_ELEMENT_TYPE GetEventElementType(const string name);
   
protected:
   CScrollBarH      *m_scrollbar_h;                            // Указатель на горизонтальную полосу прокрутки
   CScrollBarV      *m_scrollbar_v;                            // Указатель на вертикальную полосу прокрутки
 
//--- Обработчик перетаскивания граней и углов элемента
   virtual void      ResizeActionDragHandler(const int x, const int y);
   
public:
//--- Проверяет размеры элемента для отображения полос прокрутки
   void              CheckElementSizes(CElementBase *element);
protected:
//--- Рассчитывает и возвращает размер (1) ползунка, (2) полный, (3) рабочий размер трека горизонтального скроллбара
   int               ThumbSizeHorz(void);
   int               TrackLengthHorz(void)               const { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.TrackLength() : 0);       }
   int               TrackEffectiveLengthHorz(void)            { return(this.TrackLengthHorz()-this.ThumbSizeHorz());                           }

Os elementos vinculados ao contêiner chamam esse método; por isso ele precisa ser público.

Já examinamos os principais ajustes nas classes da biblioteca. Alguns ajustes e correções menores não foram abordados. Os códigos completos podem ser consultados nos arquivos anexados ao artigo.

O modelo da tabela é composto por uma célula da tabela, uma linha da tabela e uma lista de linhas que, no fim, formam a tabela. Além disso, a tabela possui um cabeçalho composto pelos cabeçalhos das colunas.

  • Célula da tabela: é um elemento independente que armazena o número da linha, o número da coluna e o conteúdo;
  • Linha da tabela: é uma lista de células e possui seu próprio número;
  • Tabela: é uma lista de linhas e pode ter um cabeçalho, que por sua vez é uma lista de cabeçalhos de coluna.

O componente View, responsável por exibir os dados do modelo da tabela, terá aproximadamente a mesma estrutura. Por exemplo, criaremos o objeto da tabela a partir de um array de dados e de um array com os respectivos cabeçalhos. Em seguida, passaremos um ponteiro para o modelo da tabela (componente Model) ao objeto recém-criado de representação visual da tabela (componente View). Esse objeto preencherá todos os dados nas células da tabela.

Nesta etapa, criaremos uma tabela estática simples, ainda sem controle de exibição pelo cursor do mouse.


Classe de célula da tabela (View)

A tabela é composta por uma lista de linhas. Cada linha, por sua vez, contém objetos com dois canvases para desenhar o fundo e o primeiro plano. As células da tabela ficam dispostas horizontalmente em suas respectivas linhas. Não faz sentido dar canvases próprios às células. Podemos simplesmente particionar cada linha em áreas, cada uma correspondente à posição de uma célula. Em seguida, desenhamos os dados da célula no canvas do objeto de linha da tabela.

Foi exatamente para isso que, logo no início, criamos uma classe intermediária. Ela contém apenas o tamanho do objeto, suas coordenadas e um ponteiro para o elemento de controle no qual vamos desenhar. Esse objeto será a célula da tabela. Passaremos à célula um ponteiro para o objeto de linha da tabela, para que ela desenhe nos canvases dessa linha.

No arquivo Controls.mqh, declararemos a nova classe:

//+------------------------------------------------------------------+
//| Класс визуального представления ячейки таблицы                   |
//+------------------------------------------------------------------+
class CTableCellView : public CBoundedObj
  {
protected:
   CTableCell       *m_table_cell_model;                       // Указатель на модель ячейки
   CImagePainter    *m_painter;                                // Указатель на объект рисования
   CTableRowView    *m_element_base;                           // Указатель на базовый элемент (строка таблицы)
   CCanvas          *m_background;                             // Указатель на канвас фона
   CCanvas          *m_foreground;                             // Указатель на канвас переднего плана
   int               m_index;                                  // Индекс в списке ячеек
   ENUM_ANCHOR_POINT m_text_anchor;                            // Точка привязки текста (выравнивание в ячейке)
   int               m_text_x;                                 // Координата X текста (смещение относительно левой границы области объекта)
   int               m_text_y;                                 // Координата Y текста (смещение относительно верхней границы области объекта)
   ushort            m_text[];                                 // Текст
   
//--- Возвращает смещения начальных координат рисования на холсте относительно канваса и координат базового элемента
   int               CanvasOffsetX(void)     const { return(this.m_element_base.ObjectX()-this.m_element_base.X());  }
   int               CanvasOffsetY(void)     const { return(this.m_element_base.ObjectY()-this.m_element_base.Y());  }
   
//--- Возвращает скорректированную координату точки на холсте с учётом смещения холста относительно базового элемента
   int               AdjX(const int x)                            const { return(x-this.CanvasOffsetX());            }
   int               AdjY(const int y)                            const { return(y-this.CanvasOffsetY());            }

//--- Возвращает координаты X и Y текста в зависимости от точки привязки
   bool              GetTextCoordsByAnchor(int &x, int&y);
   
public:
//--- (1) Устанавливает, (2) возвращает текст ячейки
   void              SetText(const string text)                         { ::StringToShortArray(text,this.m_text);    }
   string            Text(void)                                   const { return ::ShortArrayToString(this.m_text);  }
   
//--- Устанавливает идентификатор
   virtual void      SetID(const int id)                                { this.m_index=this.m_id=id;                 }
//--- (1) Устанавливает, (2) возвращает индекс ячейки
   void              SetIndex(const int index)                          { this.SetID(index);                         }
   int               Index(void)                                  const { return this.m_index;                       }

//--- (1) Устанавливает, (2) возвращает смещение текста по оси X
   void              SetTextShiftX(const int shift)                     { this.m_text_x=shift;                       }
   int               TextShiftX(void)                             const { return this.m_text_x;                      }
   
//--- (1) Устанавливает, (2) возвращает смещение текста по оси Y
   void              SetTextShiftY(const int shift)                     { this.m_text_y=shift;                       }
   int               TextShiftY(void)                             const { return this.m_text_y;                      }
   
//--- (1) Устанавливает, (2) возвращает точку привязки текста
   void              SetTextAnchor(const ENUM_ANCHOR_POINT anchor,const bool cell_redraw,const bool chart_redraw);
   int               TextAnchor(void)                             const { return this.m_text_anchor;                 }
   
//--- Устанавливает точку привязки и смещения текста
   void              SetTextPosition(const ENUM_ANCHOR_POINT anchor,const int shift_x,const int shift_y,const bool cell_redraw,const bool chart_redraw);

//--- Назначает базовый элемент (строку таблицы)
   void              RowAssign(CTableRowView *base_element);
   
//--- (1) Назначает, (2) возвращает модель ячейки
   bool              TableCellModelAssign(CTableCell *cell_model,int dx,int dy,int w,int h);
   CTableCell       *GetTableCellModel(void)                            { return this.m_table_cell_model;            }

//--- Распечатывает в журнале назначенную модель ячейки
   void              TableCellModelPrint(void);
   
//--- (1) Заливает объект цветом фона, (2) обновляет объект для отображения изменений, (3) рисует внешний вид
   virtual void      Clear(const bool chart_redraw);
   virtual void      Update(const bool chart_redraw);
   virtual void      Draw(const bool chart_redraw);
   
//--- Выводит текст
   virtual void      DrawText(const int dx, const int dy, const string text, const bool chart_redraw);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0)const { return CBaseObj::Compare(node,mode);       }
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                                   const { return(ELEMENT_TYPE_TABLE_CELL);           }
  
//--- Инициализация объекта класса
   void              Init(const string text);
   
//--- Возвращает описание объекта
   virtual string    Description(void);
   
//--- Конструкторы/деструктор
                     CTableCellView(void);
                     CTableCellView(const int id, const string user_name, const string text, const int x, const int y, const int w, const int h);
                    ~CTableCellView (void){}
  };

Todas as variáveis e rotinas estão comentadas aqui no código. Neste caso, o índice da célula e seu identificador coincidem. Por isso, redefinimos SetID() para atribuir o mesmo valor ao índice e ao identificador. Vamos analisar a implementação dos métodos declarados.

Os construtores da classe chamam o método de inicialização do objeto e definem o identificador e o nome da célula:

//+------------------------------------------------------------------+
//| CTableCellView::Конструктор по умолчанию. Строит объект в главном|
//| окне текущего графика в координатах 0,0 с размерами по умолчанию |
//+------------------------------------------------------------------+
CTableCellView::CTableCellView(void) : CBoundedObj("TableCell",-1,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H), m_index(-1),m_text_anchor(ANCHOR_LEFT)
  {
//--- Инициализация
   this.Init("");
   this.SetID(-1);
   this.SetName("TableCell");
  }
//+------------------------------------------------------------------+
//| CTableCellView::Конструктор параметрический. Строит объект       |
//| в указанном окне указанного графика с указанными текстом,        |
//| координатами и размерами                                         |
//+------------------------------------------------------------------+
CTableCellView::CTableCellView(const int id, const string user_name, const string text, const int x, const int y, const int w, const int h) :
   CBoundedObj(user_name,id,x,y,w,h), m_index(-1),m_text_anchor(ANCHOR_LEFT)
  {
//--- Инициализация
   this.Init(text);
   this.SetID(id);
   this.SetName(user_name);
  }

Método de inicialização do objeto da classe:

//+------------------------------------------------------------------+
//| CTableCellView::Инициализация                                    |
//+------------------------------------------------------------------+
void CTableCellView::Init(const string text)
  {
//--- Класс не управляет канвасами
   this.m_canvas_owner=false;
//--- Текст ячейки
   this.SetText(text);
//--- Смещения текста по умолчанию
   this.m_text_x=2;
   this.m_text_y=0;
  }

Definimos obrigatoriamente no objeto o sinalizador de que ele não gerencia canvases. Assim, ao ser destruído, ele não removerá canvases pertencentes a outro objeto.

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

//+------------------------------------------------------------------+
//| CTableCellView::Возвращает описание объекта                      |
//+------------------------------------------------------------------+
string CTableCellView::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   return ::StringFormat("%s%s ID %d, X %d, Y %d, W %d, H %d",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.ID(),this.X(),this.Y(),this.Width(),this.Height());
  }

O método exibe no log uma linha com o nome do tipo do objeto, o identificador, as coordenadas e as dimensões da célula.

Método que atribui à célula a linha da tabela e os canvases de fundo e de primeiro plano:

//+------------------------------------------------------------------+
//| CTableCellView::Назначает строку, канвасы фона и переднего плана |
//+------------------------------------------------------------------+
void CTableCellView::RowAssign(CTableRowView *base_element)
  {
   if(base_element==NULL)
     {
      ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__);
      return;
     }
   this.m_element_base=base_element;
   this.m_background=this.m_element_base.GetBackground();
   this.m_foreground=this.m_element_base.GetForeground();
   this.m_painter=this.m_element_base.Painter();
  }

O método recebe um ponteiro para o objeto de linha da tabela (a descrição da classe virá adiante). Se o ponteiro não for válido, exibimos uma mensagem e retornamos NULL. Em seguida, gravamos o ponteiro para a linha da tabela nas variáveis da classe. A partir dessa linha, obtemos também os ponteiros para os canvases de fundo e de primeiro plano, além do objeto de desenho.

O objeto do modelo da tabela também possui suas próprias linhas e células. Atribuímos o modelo da célula da tabela a essa classe, que desenha os dados do modelo da célula nos canvases do objeto de linha.

Método que atribui o modelo da célula:

//+------------------------------------------------------------------+
//| CTableCellView::Назначает модель ячейки                          |
//+------------------------------------------------------------------+
bool CTableCellView::TableCellModelAssign(CTableCell *cell_model,int dx,int dy,int w,int h)
  {
//--- Если передан невалидный объект модели ячейки - сообщаем об этом и возвращаем false
   if(cell_model==NULL)
     {
      ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__);
      return false;
     }
//--- Если базовый элемент (строка таблицы) не назначен - сообщаем об этом и возвращаем false
   if(this.m_element_base==NULL)
     {
      ::PrintFormat("%s: Error. Base element not assigned. Please use RowAssign() method first",__FUNCTION__);
      return false;
     }
//--- Сохраняем модель ячейки
   this.m_table_cell_model=cell_model;
//--- Устанавливаем координаты и размеры визуального представления ячейки
   this.BoundSetXY(dx,dy);
   this.BoundResize(w,h);
//--- Устанавливаем размеры области рисования визуального представления ячейки
   this.m_painter.SetBound(dx,dy,w,h);
//--- Всё успешно
   return true;
  }

Primeiro, verificamos se o ponteiro passado para o modelo da célula é válido. Depois, confirmamos se esse objeto de célula já recebeu a linha da tabela. Se alguma dessas condições não for satisfeita, o método exibe uma mensagem no log e retorna false. Em seguida, armazenamos o ponteiro para o modelo da célula em uma variável da classe e definimos as coordenadas e dimensões da área da célula na linha correspondente.

Ao ordenar as linhas e colunas da tabela, bastará reatribuir o ponteiro para o objeto de célula. Esse objeto, que possui suas coordenadas exatas na tabela, desenhará os dados do novo modelo de célula atribuído a ele.

Método que retorna as coordenadas X e Y do texto conforme o ponto de ancoragem:

//+------------------------------------------------------------------+
//| CTableCellView::Возвращает координаты X и Y текста               |
//| в зависимости от точки привязки                                  |
//+------------------------------------------------------------------+
bool CTableCellView::GetTextCoordsByAnchor(int &x,int &y)
  {
//--- Получаем размеры текста в ячейке
   int text_w=0, text_h=0;
   this.m_foreground.TextSize(this.Text(),text_w,text_h);
   if(text_w==0 || text_h==0)
      return false;
//--- В зависимости от точки привязки текста в ячейке
//--- рассчитываем его начальные координаты (верхний левый угол)
   switch(this.m_text_anchor)
     {
      //--- Точка привязки слева по центру
      case ANCHOR_LEFT :
        x=0;
        y=(this.Height()-text_h)/2;
        break;
      //--- Точка привязки в левом нижнем углу
      case ANCHOR_LEFT_LOWER :
        x=0;
        y=this.Height()-text_h;
        break;
      //--- Точка привязки снизу по центру
      case ANCHOR_LOWER :
        x=(this.Width()-text_w)/2;
        y=this.Height()-text_h;
        break;
      //--- Точка привязки в правом нижнем углу
      case ANCHOR_RIGHT_LOWER :
        x=this.Width()-text_w;
        y=this.Height()-text_h;
        break;
      //--- Точка привязки справа по центру
      case ANCHOR_RIGHT :
        x=this.Width()-text_w;
        y=(this.Height()-text_h)/2;
        break;
      //--- Точка привязки в правом верхнем углу
      case ANCHOR_RIGHT_UPPER :
        x=this.Width()-text_w;
        y=0;
        break;
      //--- Точка привязки сверху по центру
      case ANCHOR_UPPER :
        x=(this.Width()-text_w)/2;
        y=0;
        break;
      //--- Точка привязки строго по центру объекта
      case ANCHOR_CENTER :
        x=(this.Width()-text_w)/2;
        y=(this.Height()-text_h)/2;
        break;
      //--- Точка привязки в левом верхнем углу
      //---ANCHOR_LEFT_UPPER
      default:
        x=0;
        y=0;
        break;
     }
   return true;
  }

O método recebe, por referência, as variáveis que armazenarão as coordenadas calculadas do canto superior esquerdo do texto exibido. Se não for possível obter as dimensões do texto da célula, ele retorna false

Após calcular as coordenadas do canto superior esquerdo, o método grava nas variáveis as coordenadas calculadas do texto e retorna true.

Método que define o ponto de ancoragem do texto:

//+------------------------------------------------------------------+
//| CTableCellView::Устанавливает точку привязки текста              |
//+------------------------------------------------------------------+
void CTableCellView::SetTextAnchor(const ENUM_ANCHOR_POINT anchor,const bool cell_redraw,const bool chart_redraw)
  {
   if(this.m_text_anchor==anchor)
      return;
   this.m_text_anchor=anchor;
   if(cell_redraw)
      this.Draw(chart_redraw);
  }

Primeiro, gravamos o novo ponto de ancoragem na variável. Depois, se o sinalizador de redesenho desta célula estiver definido, chamamos o método de desenho da célula com o sinalizador especificado de redesenho do gráfico.

Método que define o ponto de ancoragem e os deslocamentos do texto:

//+------------------------------------------------------------------+
//| CTableCellView::Устанавливает точку привязки и смещения текста   |
//+------------------------------------------------------------------+
void CTableCellView::SetTextPosition(const ENUM_ANCHOR_POINT anchor,const int shift_x,const int shift_y,const bool cell_redraw,const bool chart_redraw)
  {
   this.SetTextShiftX(shift_x);
   this.SetTextShiftY(shift_y);
   this.SetTextAnchor(anchor,cell_redraw,chart_redraw);
  }

Neste método, além do ponto de ancoragem, também definimos os deslocamentos iniciais do texto nos eixos X e Y.

Método que preenche o objeto com uma cor:

//+------------------------------------------------------------------+
//| CTableCellView::Заливает объект цветом                           |
//+------------------------------------------------------------------+
void CTableCellView::Clear(const bool chart_redraw)
  {
//--- Устанавливаем корректные координаты углов ячейки
   int x1=this.AdjX(this.m_bound.X());
   int y1=this.AdjY(this.m_bound.Y());
   int x2=this.AdjX(this.m_bound.Right());
   int y2=this.AdjY(this.m_bound.Bottom());
//--- Стираем фон и передний план внутри прямоугольной области расположения ячейки
   if(this.m_background!=NULL)
      this.m_background.FillRectangle(x1,y1,x2,y2-1,::ColorToARGB(this.m_element_base.BackColor(),this.m_element_base.AlphaBG()));
   if(this.m_foreground!=NULL)
      this.m_foreground.FillRectangle(x1,y1,x2,y2-1,clrNULL);
  }

Obtemos as coordenadas corretas dos quatro cantos da área retangular e, em seguida, preenchemos o fundo com a cor de fundo da linha da tabela e o primeiro plano com uma cor transparente.

Método que atualiza o objeto para exibir as alterações:

//+------------------------------------------------------------------+
//| CTableCellView::Обновляет объект для отображения изменений       |
//+------------------------------------------------------------------+
void CTableCellView::Update(const bool chart_redraw)
  {
   if(this.m_background!=NULL)
      this.m_background.Update(false);
   if(this.m_foreground!=NULL)
      this.m_foreground.Update(chart_redraw);
  }

Se os canvases de fundo e de primeiro plano forem válidos, chamamos seus métodos de atualização com o sinalizador especificado de redesenho do gráfico.

Método que desenha a aparência da célula:

//+------------------------------------------------------------------+
//| CTableCellView::Рисует внешний вид                               |
//+------------------------------------------------------------------+
void CTableCellView::Draw(const bool chart_redraw)
  {
//--- Получаем координаты текста в зависимости от точки привязки
   int text_x=0, text_y=0;
   if(!this.GetTextCoordsByAnchor(text_x,text_y))
      return;
//--- Корректируем координаты текста
   int x=this.AdjX(this.X()+text_x);
   int y=this.AdjY(this.Y()+text_y);
   
//--- Устанавливаем координаты разделительной линии
   int x1=this.AdjX(this.X());
   int x2=this.AdjX(this.X());
   int y1=this.AdjY(this.Y());
   int y2=this.AdjY(this.Bottom());
   
//--- Выводим текст на канвасе переднего плана без обновления графика
   this.DrawText(x+this.m_text_x,y+this.m_text_y,this.Text(),false);
   
//--- Если это не крайняя справа ячейка - рисуем у ячейки справа вертикальную разделительную полосу
   if(this.m_element_base!=NULL && this.Index()<this.m_element_base.CellsTotal()-1)
     {
      int line_x=this.AdjX(this.Right());
      this.m_background.Line(line_x,y1,line_x,y2,::ColorToARGB(this.m_element_base.BorderColor(),this.m_element_base.AlphaBG()));
     }
//--- Обновляем канвас фона с указанным флагом перерисовки графика
   this.m_background.Update(chart_redraw);
  }

A lógica do método está descrita nos comentários. O ajuste das coordenadas é necessário quando o objeto está recortado pelos limites do contêiner. Nesse caso, não calculamos as coordenadas iniciais a partir das coordenadas reais do objeto gráfico do canvas. Usamos as coordenadas do elemento gráfico, que fica virtualmente posicionado fora dos limites do contêiner.

Desenhamos as linhas divisórias somente no lado direito da célula. Se ela não for a célula mais à direita, o contêiner já a limita; portanto não faz sentido desenhar ali uma linha adicional.

Método que exibe o texto da célula:

//+------------------------------------------------------------------+
//| CTableCellView::Выводит текст                                    |
//+------------------------------------------------------------------+
void CTableCellView::DrawText(const int dx,const int dy,const string text,const bool chart_redraw)
  {
//--- Проверяем базовый элемент
   if(this.m_element_base==NULL)
      return;
      
//--- Очищаем ячейку и устанавливаем текст
   this.Clear(false);
   this.SetText(text);
   
//--- Выводим установленный текст на канвасе переднего плана
   this.m_foreground.TextOut(dx,dy,this.Text(),::ColorToARGB(this.m_element_base.ForeColor(),this.m_element_base.AlphaFG()));
   
//--- Если текст выходит за правую границу области ячейки
   if(this.Right()-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.Right())-w,this.AdjY(this.Y()),this.AdjX(this.Right()),this.AdjY(this.Y())+h,clrNULL);
         this.m_foreground.TextOut(this.AdjX(this.Right())-w,this.AdjY(dy),"...",::ColorToARGB(this.m_element_base.ForeColor(),this.m_element_base.AlphaFG()));
        }
     }
//--- Обновляем канвас переднего плана с указанным флагом перерисовки графика
   this.m_foreground.Update(chart_redraw);
  }

A lógica do método está totalmente descrita nos comentários do código. Caso o texto exibido ultrapasse a área da célula, nós o recortamos conforme os limites dessa área e substituímos sua parte final pela sequência de reticências ("..."), indicando que nem todo o texto coube na largura disponível da célula.

Método que imprime no log o modelo atribuído à célula:

//+------------------------------------------------------------------+
//| CTableCellView::Распечатывает в журнале назначенную модель строки|
//+------------------------------------------------------------------+
void CTableCellView::TableCellModelPrint(void)
  {
   if(this.m_table_cell_model!=NULL)
      this.m_table_cell_model.Print();
  }

O método permite verificar qual célula do componente Model está associada à célula do componente View.

Métodos para operações com arquivos:

//+------------------------------------------------------------------+
//| CTableCellView::Сохранение в файл                                |
//+------------------------------------------------------------------+
bool CTableCellView::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CBaseObj::Save(file_handle))
      return false;
  
//--- Сохраняем номер ячейки
   if(::FileWriteInteger(file_handle,this.m_index,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем точку привязки текста
   if(::FileWriteInteger(file_handle,this.m_text_anchor,INT_VALUE)!=INT_VALUE)
      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;
//--- Сохраняем текст
   if(::FileWriteArray(file_handle,this.m_text)!=sizeof(this.m_text))
      return false;
      
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CTableCellView::Загрузка из файла                                |
//+------------------------------------------------------------------+
bool CTableCellView::Load(const int file_handle)
  {
//--- Загружаем данные родительского объекта
   if(!CBaseObj::Load(file_handle))
      return false;
      
//--- Загружаем номер ячейки
   this.m_id=this.m_index=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем точку привязки текста
   this.m_text_anchor=(ENUM_ANCHOR_POINT)::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем координату X текста
   this.m_text_x=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем координату Y текста
   this.m_text_y=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем текст
   if(::FileReadArray(file_handle,this.m_text)!=sizeof(this.m_text))
      return false;
   
//--- Всё успешно
   return true;
  }

Os métodos permitem salvar os dados da célula da tabela em um arquivo e carregá-los de um arquivo.

Agora vamos analisar a classe de representação visual da linha da tabela.


Classe da linha da tabela (View)

A linha da tabela é um objeto derivado da classe CPanel. Ela possui uma lista de objetos de células da classe CTableCellView.

Continuaremos escrevendo o código no arquivo Controls.mqh:

//+------------------------------------------------------------------+
//| Класс визуального представления строки таблицы                   |
//+------------------------------------------------------------------+
class CTableRowView : public CPanel
  {
protected:
   CTableCellView    m_temp_cell;                                    // Временный объект ячейки для поиска
   CTableRow        *m_table_row_model;                              // Указатель на модель строки
   CListElm          m_list_cells;                                   // Список ячеек
   int               m_index;                                        // Индекс в списке строк
//--- Создаёт и добавляет в список новый объект представления ячейки
   CTableCellView   *InsertNewCellView(const int index,const string text,const int dx,const int dy,const int w,const int h);
   
public:
//--- Возвращает (1) список, (2) количество ячеек
   CListElm         *GetListCells(void)                                 { return &this.m_list_cells;        }
   int               CellsTotal(void)                             const { return this.m_list_cells.Total(); }
   
//--- Устанавливает идентификатор
   virtual void      SetID(const int id)                                { this.m_index=this.m_id=id;        }
//--- (1) Устанавливает, (2) возвращает индекс строки
   void              SetIndex(const int index)                          { this.SetID(index);                }
   int               Index(void)                                  const { return this.m_index;              }

//--- (1) Устанавливает, (2) возвращает модель строки
   bool              TableRowModelAssign(CTableRow *row_model);
   CTableRow        *GetTableRowModel(void)                             { return this.m_table_row_model;    }

//--- Распечатывает в журнале назначенную модель строки
   void              TableRowModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS);
   
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0)const { return CLabel::Compare(node,mode);}
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                                   const { return(ELEMENT_TYPE_TABLE_ROW);   }
  
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(void);
   virtual void      InitColors(void);

//--- Конструкторы/деструктор
                     CTableRowView(void);
                     CTableRowView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CTableRowView (void){}
  };

Na linha da tabela, assim como na célula, a propriedade de índice da linha é igual ao identificador da própria linha. Aqui também redefinimos o método virtual de definição do identificador para atribuir simultaneamente as duas propriedades: o índice e o identificador.

Os construtores da classe chamam o método de inicialização do objeto:

//+------------------------------------------------------------------+
//| CTableRowView::Конструктор по умолчанию. Строит объект в главном |
//| окне текущего графика в координатах 0,0 с размерами по умолчанию |
//+------------------------------------------------------------------+
CTableRowView::CTableRowView(void) : CPanel("TableRow","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H), m_index(-1)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CTableRowView::Конструктор параметрический. Строит объект в      |
//| указанном окне указанного графика с указанными текстом,          |
//| координатами и размерами                                         |
//+------------------------------------------------------------------+
CTableRowView::CTableRowView(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,chart_id,wnd,x,y,w,h), m_index(-1)
  {
//--- Инициализация
   this.Init();
  }

Inicialização do objeto:

//+------------------------------------------------------------------+
//| CTableRowView::Инициализация                                     |
//+------------------------------------------------------------------+
void CTableRowView::Init(void)
  {
//--- Инициализация родительского объекта
   CPanel::Init();
//--- Фон - непрозрачный
   this.SetAlphaBG(255);
//--- Ширина рамки
   this.SetBorderWidth(1);
  }

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

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

Método que cria um novo objeto de representação da célula e o adiciona à lista:

//+------------------------------------------------------------------+
//| CTableRowView::Создаёт и добавляет в список                      |
//| новый объект представления ячейки                                |
//+------------------------------------------------------------------+
CTableCellView *CTableRowView::InsertNewCellView(const int index,const string text,const int dx,const int dy,const int w,const int h)
  {
//--- Проверяем есть ли в списке объект с указанным идентификатором и, если да - сообщаем об этом и возвращаем NULL
   this.m_temp_cell.SetIndex(index);
//--- Устанавливаем списку флаг сортировки по идентификатору
   this.m_list_cells.Sort(ELEMENT_SORT_BY_ID);
   if(this.m_list_cells.Search(&this.m_temp_cell)!=NULL)
     {
      ::PrintFormat("%s: Error. The TableCellView object with index %d is already in the list",__FUNCTION__,index);
      return NULL;
     }
//--- Создаём имя и идентификатор объекта ячейки
   string name="TableCellView"+(string)this.Index()+"x"+(string)index;
   int id=this.m_list_cells.Total();
//--- Создаём новый объект TableCellView; при неудаче - сообщаем об этом и возвращаем NULL
   CTableCellView *cell_view=new CTableCellView(id,name,""+text,dx,dy,w,h);
   if(cell_view==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create CTableCellView object",__FUNCTION__);
      return NULL;
     }
//--- Если новый объект не удалось добавить в список - сообщаем об этом, удаляем объект и возвращаем NULL
   if(this.m_list_cells.Add(cell_view)==-1)
     {
      ::PrintFormat("%s: Error. Failed to add CTableCellView object to list",__FUNCTION__);
      delete cell_view;
      return NULL;
     }
//--- Назначаем базовый элемент и возвращаем указатель на объект
   cell_view.RowAssign(this.GetObject());
   return cell_view;
  }

Mesmo associando cada célula à sua própria área no objeto de linha da tabela, ainda é preciso armazenar os objetos criados pelo operador new em uma lista. Outra opção seria monitorá-los manualmente e destruí-los quando necessário.

É mais simples armazená-los em uma lista, pois assim o subsistema do terminal passa a controlar quando eles devem ser destruídos. O método cria um novo objeto de representação da célula e o coloca na lista de células da linha. Depois de criado, o objeto recebe a linha em que está localizado e passa a desenhar os dados do modelo da célula da tabela nos canvases dessa linha.

Método que define o modelo da linha:

//+------------------------------------------------------------------+
//| CTableRowView::Устанавливает модель строки                       |
//+------------------------------------------------------------------+
bool CTableRowView::TableRowModelAssign(CTableRow *row_model)
  {
//--- Если передан пустой объект - сообщаем об этом и возвращаем false
   if(row_model==NULL)
     {
      ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__);
      return false;
     }
//--- Если в переданной модели строки нет ни одной ячейки - сообщаем об этом и возвращаем false
   int total=(int)row_model.CellsTotal();
   if(total==0)
     {
      ::PrintFormat("%s: Error. Row model does not contain any cells",__FUNCTION__);
      return false;
     }
//--- Сохраняем указатель на переданную модель строки и рассчитываем ширину ячейки
   this.m_table_row_model=row_model;
   int cell_w=(int)::round((double)this.Width()/(double)total);
   
//--- В цикле по количеству ячеек в модели строки
   for(int i=0;i<total;i++)
     {
      //--- получаем модель очередной ячейки,
      CTableCell *cell_model=this.m_table_row_model.GetCell(i);
      if(cell_model==NULL)
         return false;
      //--- рассчитываем координату и создаём имя для области ячейки
      int x=cell_w*i;
      string name="CellBound"+(string)this.m_table_row_model.Index()+"x"+(string)i;
      //--- Создаём новую область ячейки
      CBound *cell_bound=this.InsertNewBound(name,x,0,cell_w,this.Height());
      if(cell_bound==NULL)
         return false;
      //--- Создаём новый объект визуального представления ячейки
      CTableCellView *cell_view=this.InsertNewCellView(i,cell_model.Value(),x,0,cell_w,this.Height());
      if(cell_view==NULL)
         return false;
      //--- На текущую область ячейки назначаем соответствующий объект визуального представления ячейки
      cell_bound.AssignObject(cell_view);
     }
//--- Всё успешно
   return true;
  }

O método recebe um ponteiro para o modelo da linha. Em seguida, percorre o número de células do modelo em um laço. A cada iteração, cria uma nova área para posicionar o objeto de representação da célula e cria também a própria célula, da classe CTableCellView. Cada célula criada é associada à respectiva área. Como resultado, obtemos uma lista de áreas de células com ponteiros associados às respectivas células da tabela.

Método que desenha a aparência da linha:

//+------------------------------------------------------------------+
//| CTableRowView::Рисует внешний вид                                |
//+------------------------------------------------------------------+
void CTableRowView::Draw(const bool chart_redraw)
  {
//--- Заливаем объект цветом фона, рисуем линию строки и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Line(this.AdjX(0),this.AdjY(this.Height()-1),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
  
//--- Рисуем ячейки строки
   int total=this.m_list_bounds.Total();
   for(int i=0;i<total;i++)
     {
      //--- Получаем область очередной ячейки
      CBound *cell_bound=this.GetBoundAt(i);
      if(cell_bound==NULL)
         continue;
      
      //--- Из области ячейки получаем присоединённый объект ячейки
      CTableCellView *cell_view=cell_bound.GetAssignedObj();
      //--- Рисуем визуальное представление ячейки
      if(cell_view!=NULL)
         cell_view.Draw(false);
     }
//--- Обновляем канвасы фона и переднего плана с указанным флагом перерисовки графика
   this.Update(chart_redraw);
  }

Primeiro, preenchemos a linha da tabela com a cor de fundo e desenhamos uma linha na parte inferior. Em seguida, percorremos as áreas das células da linha em um laço. A cada iteração, obtemos a área seguinte, extraímos dela o ponteiro para o objeto de célula e chamamos seu método de desenho.

Método que imprime no log o modelo atribuído à célula:

//+------------------------------------------------------------------+
//| CTableRowView::Распечатывает в журнале назначенную модель строки |
//+------------------------------------------------------------------+
void CTableRowView::TableRowModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS)
  {
   if(this.m_table_row_model!=NULL)
      this.m_table_row_model.Print(detail,as_table,cell_width);
  }

O método permite verificar qual modelo de linha do modelo da tabela está associado a este objeto de representação visual da linha.

Métodos para operações com arquivos:

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

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

Os métodos permitem salvar os dados da linha da tabela em um arquivo e carregá-los de um arquivo.

Toda tabela deve ter um cabeçalho. Ele permite entender quais dados aparecem nas colunas da tabela. O cabeçalho da tabela é uma lista de cabeçalhos de coluna. Cada coluna da tabela tem seu próprio cabeçalho, que exibe o tipo e o nome dos dados nela contidos.


Classe de cabeçalho de coluna da tabela (View)

O cabeçalho de coluna da tabela é um objeto independente, derivado da classe Button (CButton). Ele herda o comportamento desse objeto e o complementa. Na implementação atual, as propriedades do botão não serão ampliadas; apenas alteraremos as cores dos diferentes estados. No próximo artigo, adicionaremos ao cabeçalho a indicação da direção de ordenação, com setas para cima e para baixo no lado direito da área do botão. Também adicionaremos um modelo de eventos para iniciar a ordenação quando o botão for pressionado.

Continuaremos escrevendo o código no arquivo Controls.mqh:

//+------------------------------------------------------------------+
//| Класс визуального представления заголовка столбца таблицы        |
//+------------------------------------------------------------------+
class CColumnCaptionView : public CButton
  {
protected:
   CColumnCaption   *m_column_caption_model;                         // Указатель на модель заголовка столбца
   int               m_index;                                        // Индекс в списке столбцов
   
public:
//--- Устанавливает идентификатор
   virtual void      SetID(const int id)                                { this.m_index=this.m_id=id;                 }
//--- (1) Устанавливает, (2) возвращает индекс ячейки
   void              SetIndex(const int index)                          { this.SetID(index);                         }
   int               Index(void)                                  const { return this.m_index;                       }
   
//--- (1) Назначает, (2) возвращает модель заголовка столбца
   bool              ColumnCaptionModelAssign(CColumnCaption *caption_model);
   CColumnCaption   *ColumnCaptionModel(void)                           { return this.m_column_caption_model;        }

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

O objeto possui um índice igual ao identificador, como nas duas classes analisadas anteriormente. O método de definição do identificador também funciona como nas classes anteriores.

Os construtores da classe chamam o método de inicialização do objeto e definem o identificador como zero (ele é alterado após a criação do objeto):

//+------------------------------------------------------------------+
//| CColumnCaptionView::Конструктор по умолчанию. Строит объект      |
//| в главном окне текущего графика в координатах 0,0                |
//| с размерами по умолчанию                                         |
//+------------------------------------------------------------------+
CColumnCaptionView::CColumnCaptionView(void) : CButton("ColumnCaption","Caption",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H), m_index(0)
  {
//--- Инициализация
   this.Init("Caption");
   this.SetID(0);
   this.SetName("ColumnCaption");
  }
//+------------------------------------------------------------------+
//| CColumnCaptionView::Конструктор параметрический.                 |
//| Строит объект в указанном окне указанного графика с              |
//| указанными текстом, координатами и размерами                     |
//+------------------------------------------------------------------+
CColumnCaptionView::CColumnCaptionView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h) :
   CButton(object_name,text,chart_id,wnd,x,y,w,h), m_index(0)
  {
//--- Инициализация
   this.Init(text);
   this.SetID(0);
  }

Método de inicialização do objeto:

//+------------------------------------------------------------------+
//| CColumnCaptionView::Инициализация                                |
//+------------------------------------------------------------------+
void CColumnCaptionView::Init(const string text)
  {
//--- Смещения текста по умолчанию
   this.m_text_x=4;
   this.m_text_y=2;
//--- Устанавливаем цвета различных состояний
   this.InitColors();
  }

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

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

Método que desenha a aparência do cabeçalho de coluna:

//+------------------------------------------------------------------+
//| CColumnCaptionView::Рисует внешний вид                           |
//+------------------------------------------------------------------+
void CColumnCaptionView::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем слева светлую вертикальную линию, справа - тёмную
   this.Fill(this.BackColor(),false);
   color clr_dark =this.BorderColor();                                                       // "Тёмный цвет"
   color clr_light=this.GetBackColorControl().NewColor(this.BorderColor(), 100, 100, 100);   // "Светлый цвет"
   this.m_background.Line(this.AdjX(0),this.AdjY(0),this.AdjX(0),this.AdjY(this.Height()-1),::ColorToARGB(clr_light,this.AlphaBG()));                          // Линия слева
   this.m_background.Line(this.AdjX(this.Width()-1),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(clr_dark,this.AlphaBG())); // Линия справа
//--- обновляем канвас фона
   this.m_background.Update(false);
   
//--- Выводим текст заголовка
   CLabel::Draw(false);
      
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Primeiro, preenchemos o fundo com a cor definida. Depois, desenhamos duas linhas verticais: uma clara à esquerda e uma escura à direita. Em seguida, exibimos o texto do cabeçalho no canvas do primeiro plano.

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

//+------------------------------------------------------------------+
//| CColumnCaptionView::Возвращает описание объекта                  |
//+------------------------------------------------------------------+
string CColumnCaptionView::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   return ::StringFormat("%s%s ID %d, X %d, Y %d, W %d, H %d",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.ID(),this.X(),this.Y(),this.Width(),this.Height());
  }

O método retorna a descrição do tipo do elemento com o identificador, as coordenadas e as dimensões do objeto.

Método que atribui o modelo do cabeçalho de coluna:

//+------------------------------------------------------------------+
//| CColumnCaptionView::Назначает модель заголовка столбца           |
//+------------------------------------------------------------------+
bool CColumnCaptionView::ColumnCaptionModelAssign(CColumnCaption *caption_model)
  {
//--- Если передан невалидный объект модели заголовка столбца - сообщаем об этом и возвращаем false
   if(caption_model==NULL)
     {
      ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__);
      return false;
     }
//--- Сохраняем модель заголовка столбца
   this.m_column_caption_model=caption_model;
//--- Устанавливаем размеры области рисования визуального представления заголовка столбца
   this.m_painter.SetBound(0,0,this.Width(),this.Height());
//--- Всё успешно
   return true;
  }

O método recebe um ponteiro para o modelo do cabeçalho de coluna e o armazena em uma variável da classe. Em seguida, define no objeto de desenho as coordenadas e as dimensões da área de desenho.

Método que imprime no log o modelo do cabeçalho de coluna atribuído:

//+------------------------------------------------------------------+
//| CColumnCaptionView::Распечатывает в журнале                      |
//| назначенную модель заголовка столбца                             |
//+------------------------------------------------------------------+
void CColumnCaptionView::ColumnCaptionModelPrint(void)
  {
   if(this.m_column_caption_model!=NULL)
      this.m_column_caption_model.Print();
  }

Permite verificar a descrição do modelo do cabeçalho de coluna atribuído ao objeto de representação visual do cabeçalho, imprimindo-a no log.

Métodos para operações com arquivos:

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

Permitem salvar os parâmetros do cabeçalho em um arquivo e carregá-los de um arquivo.


Classe de cabeçalho da tabela (View)

O cabeçalho da tabela é uma lista simples de objetos de cabeçalho de coluna, baseada no elemento de controle Panel (CPanel). Na prática, esse objeto fornece ferramentas para gerenciar as colunas da tabela. 

Nesta implementação, ele será um objeto estático comum. Toda a funcionalidade de interação com o usuário será implementada posteriormente.

Continuaremos escrevendo o código no mesmo arquivo:

//+------------------------------------------------------------------+
//| Класс визуального представления заголовка таблицы                |
//+------------------------------------------------------------------+
class CTableHeaderView : public CPanel
  {
protected:
   CColumnCaptionView m_temp_caption;                                // Временный объект заголовка столбца для поиска
   CTableHeader     *m_table_header_model;                           // Указатель на модель заголовка таблицы

//--- Создаёт и добавляет в список новый объект представления заголовка столбца
   CColumnCaptionView *InsertNewColumnCaptionView(const string text, const int x, const int y, const int w, const int h);
   
public:
//--- (1) Устанавливает, (2) возвращает модель заголовка таблицы
   bool              TableHeaderModelAssign(CTableHeader *header_model);
   CTableHeader     *GetTableHeaderModel(void)                          { return this.m_table_header_model;    }

//--- Распечатывает в журнале назначенную модель заголовка таблицы
   void              TableHeaderModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS);
   
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0)const { return CPanel::Compare(node,mode);   }
   virtual bool      Save(const int file_handle)                        { return CPanel::Save(file_handle);    }
   virtual bool      Load(const int file_handle)                        { return CPanel::Load(file_handle);    }
   virtual int       Type(void)                                   const { return(ELEMENT_TYPE_TABLE_HEADER);   }
  
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(void);
   virtual void      InitColors(void);

//--- Конструкторы/деструктор
                     CTableHeaderView(void);
                     CTableHeaderView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CTableHeaderView (void){}
  };

Atribuímos o modelo do cabeçalho da tabela ao objeto. Com base em seu conteúdo, criamos os objetos de cabeçalho de coluna e os adicionamos à lista de elementos vinculados.

Os construtores da classe chamam o método de inicialização do objeto:

//+------------------------------------------------------------------+
//| CTableHeaderView::Конструктор по умолчанию. Строит объект в      |
//| главном окне  текущего графика в координатах 0,0                 |
//| с размерами по умолчанию                                         |
//+------------------------------------------------------------------+
CTableHeaderView::CTableHeaderView(void) : CPanel("TableHeader","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CTableHeaderView::Конструктор параметрический. Строит объект в   |
//| указанном окне указанного графика с указанными текстом,          |
//| координатами и размерами                                         |
//+------------------------------------------------------------------+
CTableHeaderView::CTableHeaderView(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,chart_id,wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init();
  }

Método de inicialização do objeto:

//+------------------------------------------------------------------+
//| CTableHeaderView::Инициализация                                  |
//+------------------------------------------------------------------+
void CTableHeaderView::Init(void)
  {
//--- Инициализация родительского объекта
   CPanel::Init();
//--- Цвет фона - непрозрачный
   this.SetAlphaBG(255);
//--- Ширина рамки
   this.SetBorderWidth(1);
  }

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

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

Método que cria um novo objeto de representação do cabeçalho de coluna e o adiciona à lista:

//+------------------------------------------------------------------+
//| CTableHeaderView::Создаёт и добавляет в список                   |
//| новый объект представления заголовка столбца                     |
//+------------------------------------------------------------------+
CColumnCaptionView *CTableHeaderView::InsertNewColumnCaptionView(const string text,const int x,const int y,const int w,const int h)
  {
//--- Создаём наименование объекта и возвращаем результат создания нового заголовка столбца
   string user_name="ColumnCaptionView"+(string)this.m_list_elm.Total();
   CColumnCaptionView *caption_view=this.InsertNewElement(ELEMENT_TYPE_TABLE_COLUMN_CAPTION,text,user_name,x,y,w,h);
   return(caption_view!=NULL ? caption_view : NULL);
  }

Criamos o objeto de cabeçalho de coluna com o método padrão InsertNewElement() dos objetos da biblioteca. Esse método coloca os objetos criados na lista de elementos gráficos do objeto.

Método que define o modelo do cabeçalho:

//+------------------------------------------------------------------+
//| CTableHeaderView::Устанавливает модель заголовка                 |
//+------------------------------------------------------------------+
bool CTableHeaderView::TableHeaderModelAssign(CTableHeader *header_model)
  {
//--- Если передан пустой объект - сообщаем об этом и возвращаем false
   if(header_model==NULL)
     {
      ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__);
      return false;
     }
//--- Если в переданной модели заголовка нет ни одного заголовка столбца - сообщаем об этом и возвращаем false
   int total=(int)header_model.ColumnsTotal();
   if(total==0)
     {
      ::PrintFormat("%s: Error. Header model does not contain any columns",__FUNCTION__);
      return false;
     }
//--- Сохраняем указатель на переданную модель заголовка таблицы и рассчитываем ширину каждого заголовка столбца
   this.m_table_header_model=header_model;
   int caption_w=(int)::round((double)this.Width()/(double)total);
   
//--- В цикле по количеству заголовков столбцов в модели заголовка таблицы
   for(int i=0;i<total;i++)
     {
      //--- получаем модель очередного заголовка столбца,
      CColumnCaption *caption_model=this.m_table_header_model.GetColumnCaption(i);
      if(caption_model==NULL)
         return false;
      //--- рассчитываем координату и создаём имя для области заголовка столбца
      int x=caption_w*i;
      string name="CaptionBound"+(string)i;
      //--- Создаём новую область заголовка столбца
      CBound *caption_bound=this.InsertNewBound(name,x,0,caption_w,this.Height());
      if(caption_bound==NULL)
         return false;
      //--- Создаём новый объект визуального представления заголовка столбца
      CColumnCaptionView *caption_view=this.InsertNewColumnCaptionView(caption_model.Value(),x,0,caption_w,this.Height());
      if(caption_view==NULL)
         return false;
      //--- На текущую область заголовка столбца назначаем соответствующий объект визуального представления заголовка столбца
      caption_bound.AssignObject(caption_view);
     }
//--- Всё успешно
   return true;
  }

O método recebe o modelo do cabeçalho da tabela. Em um laço pelo número de cabeçalhos de coluna no modelo, criamos a próxima área para posicionar o cabeçalho de coluna. Criamos o respectivo objeto e associamos esse objeto à área atual. Ao concluir o laço, teremos uma lista de áreas associadas aos respectivos cabeçalhos de coluna.

Método que desenha a aparência do cabeçalho da tabela:

//+------------------------------------------------------------------+
//| CTableHeaderView::Рисует внешний вид                             |
//+------------------------------------------------------------------+
void CTableHeaderView::Draw(const bool chart_redraw)
  {
//--- Заливаем объект цветом фона, рисуем линию строки и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Line(this.AdjX(0),this.AdjY(this.Height()-1),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
   
//--- Рисуем заголовки столбцов
   int total=this.m_list_bounds.Total();
   for(int i=0;i<total;i++)
     {
      //--- Получаем область очередного заголовка столбца
      CBound *cell_bound=this.GetBoundAt(i);
      if(cell_bound==NULL)
         continue;
      
      //--- Из области заголовка столбца получаем присоединённый объект заголовка столбца
      CColumnCaptionView *caption_view=cell_bound.GetAssignedObj();
      //--- Рисуем визуальное представление заголовка столбца
      if(caption_view!=NULL)
         caption_view.Draw(false);
     }
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Primeiro, preenchemos toda a área do cabeçalho da tabela com a cor de fundo e depois desenhamos uma linha divisória na parte inferior. Em seguida, percorremos a lista de áreas dos cabeçalhos em um laço. A cada iteração, obtemos a área seguinte e, a partir dela, o objeto de cabeçalho de coluna associado. Então chamamos o método de desenho desse objeto.

Método que imprime no log o modelo do cabeçalho da tabela atribuído:

//+------------------------------------------------------------------+
//| CTableHeaderView::Распечатывает в журнале                        |
//| назначенную модель заголовка таблицы                             |
//+------------------------------------------------------------------+
void CTableHeaderView::TableHeaderModelPrint(const bool detail,const bool as_table=false,const int cell_width=CELL_WIDTH_IN_CHARS)
  {
   if(this.m_table_header_model!=NULL)
      this.m_table_header_model.Print(detail,as_table,cell_width);
  }

Permite imprimir no log o modelo do cabeçalho da tabela atribuído a esse objeto.

A representação visual da tabela é um objeto composto. Ela recebe do modelo os dados da tabela e do cabeçalho e desenha esses dados em diferentes elementos de controle. Trata-se de um painel que contém o cabeçalho e o contêiner com os dados tabulares (linhas da tabela).



Classe da tabela (View)

Continuaremos escrevendo o código no arquivo Controls.mqh:

//+------------------------------------------------------------------+
//| Класс визуального представления таблицы                          |
//+------------------------------------------------------------------+
class CTableView : public CPanel
  {
protected:
//--- Получаемые данные таблицы
   CTable           *m_table_obj;                  // Указатель на объект таблицы (включает модели таблицы и заголовка)
   CTableModel      *m_table_model;                // Указатель на модель таблицы (получаем из CTable)
   CTableHeader     *m_header_model;               // Указатель на модель заголовка таблицы (получаем из CTable)
   
//--- Данные компонента View
   CTableHeaderView *m_header_view;                // Указатель на заголовок таблицы (View)
   CPanel           *m_table_area;                 // Панель для размещения строк таблицы
   CContainer       *m_table_area_container;       // Контейнер для размещения панели со строками таблицы
   
//--- (1) Устанавливает, (2) возвращает модель таблицы
   bool              TableModelAssign(CTableModel *table_model);
   CTableModel      *GetTableModel(void)                                { return this.m_table_model;           }
   
//--- (1) Устанавливает, (2) возвращает модель заголовка таблицы
   bool              HeaderModelAssign(CTableHeader *header_model);
   CTableHeader     *GetHeaderModel(void)                               { return this.m_header_model;          }

//--- Создаёт из модели объект (1) заголовка, (2) таблицы
   bool              CreateHeader(void);
   bool              CreateTable(void);
   
public:
//--- (1) Устанавливает, (2) возвращает объект таблицы
   bool              TableObjectAssign(CTable *table_obj);
   CTable           *GetTableObj(void)                                  { return this.m_table_obj;             }

//--- Распечатывает в журнале назначенную модель (1) таблицы, (2) заголовка, (3) объекта таблицы
   void              TableModelPrint(const bool detail);
   void              HeaderModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS);
   void              TablePrint(const int column_width=CELL_WIDTH_IN_CHARS);

//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0)const { return CPanel::Compare(node,mode);   }
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                                   const { return(ELEMENT_TYPE_TABLE);          }
  
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(void);

//--- Конструкторы/деструктор
                     CTableView(void);
                     CTableView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CTableView (void){}
  };

A classe deriva da classe CPanel. Ela contém três componentes Model e três componentes View. Além disso, contém ponteiros para o modelo da tabela, o modelo do cabeçalho e o objeto da tabela. A partir da tabela, obtemos ponteiros para os dados tabulares e para o cabeçalho. Também há três ponteiros adicionais: um para o cabeçalho, outro para o painel de linhas da tabela e outro para o contêiner que receberá esse painel.

A ideia é anexar as linhas da tabela ao objeto de painel, que pode receber vários elementos. Em seguida, anexamos esse painel ao contêiner, que aceita apenas um elemento e possui barras de rolagem para mover o painel com as linhas da tabela. Visualmente, o elemento aparece como um cabeçalho de tabela e, abaixo dele, como uma pequena tabela rolável, com linhas e colunas formadas pelas células.

Os construtores da classe chamam o método de inicialização do objeto:

//+------------------------------------------------------------------+
//| CTableView::Конструктор по умолчанию.                            |
//| Строит элемент в главном окне текущего графика                   |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CTableView::CTableView(void) : CPanel("TableView","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_PANEL_H),
   m_table_model(NULL),m_header_model(NULL),m_table_obj(NULL),m_header_view(NULL),m_table_area(NULL),m_table_area_container(NULL)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CTableView::Конструктор параметрический.                         |
//| Строит элемент в указанном окне указанного графика               |
//| с указанными текстом, координатами и размерами                   |
//+------------------------------------------------------------------+
CTableView::CTableView(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,chart_id,wnd,x,y,w,h),m_table_model(NULL),m_header_model(NULL),m_table_obj(NULL),m_header_view(NULL),m_table_area(NULL),m_table_area_container(NULL)
  {
//--- Инициализация
   this.Init();
  }

No método de inicialização, criamos no painel do objeto todos os elementos de controle necessários:

//+------------------------------------------------------------------+
//| CTableView::Инициализация                                        |
//+------------------------------------------------------------------+
void CTableView::Init(void)
  {
//--- Инициализация родительского объекта
   CPanel::Init();
//--- Ширина рамки
   this.SetBorderWidth(1);
//--- Создаём заголовок таблицы
   this.m_header_view=this.InsertNewElement(ELEMENT_TYPE_TABLE_HEADER,"","TableHeader",0,0,this.Width(),DEF_TABLE_HEADER_H);
   if(this.m_header_view==NULL)
      return;
   this.m_header_view.SetBorderWidth(1);
   
//--- Создаём контейнер, в котором будет находиться панель строк таблицы
   this.m_table_area_container=this.InsertNewElement(ELEMENT_TYPE_CONTAINER,"","TableAreaContainer",0,DEF_TABLE_HEADER_H,this.Width(),this.Height()-DEF_TABLE_HEADER_H);
   if(this.m_table_area_container==NULL)
      return;
   this.m_table_area_container.SetBorderWidth(0);
   this.m_table_area_container.SetScrollable(true);
   
//--- Присоединяем к контейнеру панель для хранения строк таблицы
   int shift_y=0;
   this.m_table_area=this.m_table_area_container.InsertNewElement(ELEMENT_TYPE_PANEL,"","TableAreaPanel",0,shift_y,this.m_table_area_container.Width(),this.m_table_area_container.Height()-shift_y);
   if(m_table_area==NULL)
      return;
   this.m_table_area.SetBorderWidth(0);
  }

Método que define o modelo da tabela:

//+------------------------------------------------------------------+
//| CTableView::Устанавливает модель таблицы                         |
//+------------------------------------------------------------------+
bool CTableView::TableModelAssign(CTableModel *table_model)
  {
   if(table_model==NULL)
     {
      ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__);
      return false;
     }
   this.m_table_model=table_model;
   return true;
  }

Basta passar ao método um ponteiro para o objeto do modelo da tabela e armazená-lo em uma variável. Depois, acessaremos o objeto por essa variável.

Método que define o modelo do cabeçalho da tabela:

//+------------------------------------------------------------------+
//| CTableView::Устанавливает модель заголовка таблицы               |
//+------------------------------------------------------------------+
bool CTableView::HeaderModelAssign(CTableHeader *header_model)
  {
   if(header_model==NULL)
     {
      ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__);
      return false;
     }
   this.m_header_model=header_model;
   return true;
  }

Passamos ao método um ponteiro para o objeto do modelo do cabeçalho da tabela e o armazenamos em uma variável. Depois, acessaremos o objeto por essa variável.

Método que define o objeto da tabela:

//+------------------------------------------------------------------+
//| CTableView::Устанавливает объект таблицы                         |
//+------------------------------------------------------------------+
bool CTableView::TableObjectAssign(CTable *table_obj)
  {
//--- Если передан пустой объект таблицы - сообщаем об этом и возвращаем false
   if(table_obj==NULL)
     {
      ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__);
      return false;
     }
//--- Сохраняем указатель в переменную
   this.m_table_obj=table_obj;
//--- Записываем результат назначения модели таблицы и модели заголовка
   bool res=this.TableModelAssign(this.m_table_obj.GetTableModel());
   res &=this.HeaderModelAssign(this.m_table_obj.GetTableHeader());
   
//--- Если не удалось назначить какую-либо модель - возвращаем false
   if(!res)
      return false;
   
//--- Записываем результат создания заголовка таблицы из модели и таблицы из модели
   res=this.CreateHeader();
   res&=this.CreateTable();
   
//--- Возвращаем результат
   return res;
  }

O método recebe um ponteiro para o objeto de tabela. A partir desse objeto, obtemos o modelo do cabeçalho e o modelo da tabela e os atribuímos aos campos correspondentes. Em seguida, criamos os objetos de representação visual do cabeçalho e da tabela com base nos dados desses modelos.

Método que cria o objeto da tabela a partir do modelo:

//+------------------------------------------------------------------+
//| CTableView::Создаёт из модели объект таблицы                     |
//+------------------------------------------------------------------+
bool CTableView::CreateTable(void)
  {
   if(this.m_table_area==NULL)
      return false;
   
//--- В цикле создаёи и присоединяем к элементу Panel (m_table_area) RowsTotal строк из элементов TableRowView
   int total=(int)this.m_table_model.RowsTotal();
   int y=1;                   // Смещение по вертикали
   int table_height=0;        // Рассчитываемая высота панели
   CTableRowView *row=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Создаём и присоединяем к панели объект строки таблицы
      row=this.m_table_area.InsertNewElement(ELEMENT_TYPE_TABLE_ROW,"","TableRow"+(string)i,0,y+(row!=NULL ? row.Height()*i : 0),this.m_table_area.Width()-1,DEF_TABLE_ROW_H);
      if(row==NULL)
         return false;
      
      //--- Устанавливаем идентификатор строки
      row.SetID(i);
      //--- В зависимости от номера строки (чет/нечет) устанавливаем цвет её фона
      if(row.ID()%2==0)
         row.InitBackColorDefault(clrWhite);
      else
         row.InitBackColorDefault(clrWhiteSmoke);
      row.BackColorToDefault();
      row.InitBackColorFocused(row.GetBackColorControl().NewColor(row.BackColor(),-4,-4,-4));
      
      //--- Получаем модель строки из объекта таблицы
      CTableRow *row_model=this.m_table_model.GetRow(i);
      if(row_model==NULL)
         return false;
      //--- Созданному объекту строки таблицы назначаем полученную модель строки
      row.TableRowModelAssign(row_model);
      //--- Рассчитываем новое значение высоты панели
      table_height+=row.Height();
     }
//--- Возвращаем результат изменения размера панели на рассчитанное в цикле значение
   return this.m_table_area.ResizeH(table_height+y);
  }

A lógica do método está descrita nos comentários do código. Em um laço pelo número de linhas no modelo da tabela, criamos um novo objeto de linha da tabela e o anexamos ao painel. Também calculamos a altura futura do painel. Após concluir o laço e criar todas as linhas necessárias, o método ajusta a altura do painel de acordo com o tamanho calculado.

Método que desenha a aparência da tabela:

//+------------------------------------------------------------------+
//| CTableView::Рисует внешний вид                                   |
//+------------------------------------------------------------------+
void CTableView::Draw(const bool chart_redraw)
  {
//--- Рисуем заголовок и строки таблицы
   this.m_header_view.Draw(false);
   this.m_table_area_container.Draw(false);
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Primeiro desenhamos o cabeçalho da tabela e, abaixo dele, as linhas da tabela no contêiner.

Método que imprime no log o modelo da tabela atribuído:

//+------------------------------------------------------------------+
//| CTableView::Распечатывает в журнале назначенную модель таблицы   |
//+------------------------------------------------------------------+
void CTableView::TableModelPrint(const bool detail)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.Print(detail);
  }

Permite imprimir no log o modelo da tabela atribuído ao objeto.

Método que imprime no log o modelo do cabeçalho da tabela atribuído:

//+------------------------------------------------------------------+
//| CTableView::Распечатывает в журнале назначенную модель заголовка |
//+------------------------------------------------------------------+
void CTableView::HeaderModelPrint(const bool detail,const bool as_table=false,const int column_width=CELL_WIDTH_IN_CHARS)
  {
   if(this.m_header_model!=NULL)
      this.m_header_model.Print(detail,as_table,column_width);
  }

Permite imprimir no log o modelo do cabeçalho da tabela atribuído ao objeto.

Método que imprime no log o objeto da tabela atribuído:

//+------------------------------------------------------------------+
//| CTableView::Распечатывает в журнале назначенный объект таблицы   |
//+------------------------------------------------------------------+
void CTableView::TablePrint(const int column_width=CELL_WIDTH_IN_CHARS)
  {
   if(this.m_table_obj!=NULL)
      this.m_table_obj.Print(column_width);
  }

Imprime no log toda a tabela associada ao objeto: o cabeçalho e os dados.

Com isso, criamos todas as classes para criar a representação visual do modelo da tabela. Vejamos agora como criar uma tabela simples a partir de um array de dados.


Testando o resultado

No diretório do terminal \MQL5\Indicators\Tables, criaremos um novo indicador sem buffers, desenhado em uma subjanela do gráfico:

//+------------------------------------------------------------------+
//|                                                   iTestTable.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

Incluiremos a biblioteca no arquivo do indicador e declararemos globalmente os ponteiros para os objetos:

//+------------------------------------------------------------------+
//|                                                   iTestTable.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 "Controls\Controls.mqh"    // Библиотека элементов управления

CPanel     *panel=NULL; // Указатель на графический элемент Panel
CTable     *table;      // Указатель на объект таблицы (Model)

Em seguida, adicionaremos o seguinte código ao manipulador OnInit() do indicador:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Ищем подокно графика
   int wnd=ChartWindowFind();

//--- Создаём графический элемент "Панель"
   panel=new CPanel("Panel","",0,wnd,100,40,400,192);
   if(panel==NULL)
      return INIT_FAILED;
//--- Устанавливаем параметры панели
   panel.SetID(1);                     // Идентификатор 
   panel.SetAsMain();                  // На графике обязательно должен быть один главный элемент
   panel.SetBorderWidth(1);            // Ширина рамки (отступ видимой области на один пиксель с каждой стороны контейнера)
   panel.SetResizable(false);          // Возможность менять размеры перетаскиванием за грани и углы отключена
   panel.SetName("Main container");    // Наименование
   
//--- Создаём данные для таблицы
//--- Объявляем и заполняем массив заголовков столбцов с размерностью 4
   string captions[4]={"Column 0","Column 1","Column 2","Column 3"};
  
//--- Объявляем и заполняем массив данных с размерностью 10x4
//--- Тип массива может быть double, long, datetime, color, string
   long array[10][4]={{ 1,  2,  3,  4},
                      { 5,  6,  7,  8},
                      { 9, 10, 11, 12},
                      {13, 14, 15, 16},
                      {17, 18, 19, 20},
                      {21, 22, 23, 24},
                      {25, 26, 27, 28},
                      {29, 30, 31, 32},
                      {33, 34, 35, 36},
                      {37, 38, 39, 40}};
//--- Создаём объект таблицы из вышесозданного long-массива array 10x4 и string-массива заголовков столбцов (компонент Model)
   table=new CTable(array,captions);
   if(table==NULL)
      return INIT_FAILED;
   PrintFormat("The [%s] has been successfully created:",table.Description());
   
//--- На панели создаём новый элемент - таблицу (компонент View)
   CTableView *table_view=panel.InsertNewElement(ELEMENT_TYPE_TABLE,"","TableView",4,4,panel.Width()-8,panel.Height()-8);
//--- Графическому элементу "Таблица" (View) назначаем объект таблицы (Model)
   table_view.TableObjectAssign(table);
//--- Распечатаем в журнале модель таблицы
   table_view.TablePrint();
   
//--- Нарисуем таблицу вместе с панелью
   panel.Draw(true);

//--- Успешно
   return(INIT_SUCCEEDED);
  }

Aqui há três blocos de código:

  1. Criação do painel no qual a tabela será montada;
  2. Criação dos dados tabulares em dois arrays: tabela e cabeçalho (componentes Model);
  3. Criação da tabela no painel (componente View).

Em essência, para criar a tabela são necessárias duas etapas: preparar os dados (item 2) e criar a tabela (item 3). O painel é necessário apenas para o acabamento visual e como base do elemento gráfico.

Acrescentaremos o restante do código do indicador:

//+------------------------------------------------------------------+
//| Custom deindicator initialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Удаляем элемент Панель и уничтожаем таблицу и менеджер общих ресурсов библиотеки
   delete panel;
   delete table;
   CCommonManager::DestroyInstance();
  }
//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Вызываем обработчик OnChartEvent элемента Панель
   panel.OnChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
//--- Вызываем обработчик OnTimer элемента Панель
   panel.OnTimer();
  }

Compilaremos o indicador e o executaremos no gráfico:

 O log do terminal exibirá uma mensagem de criação bem-sucedida da tabela, seguida da descrição da tabela e da própria tabela com o cabeçalho:

The [Table: Rows total: 10, Columns total: 4] has been successfully created:
Table: Rows total: 10, Columns total: 4:
|                n/n |           Column 0 |           Column 1 |           Column 2 |           Column 3 |
| 0                  |                  1 |                  2 |                  3 |                  4 |
| 1                  |                  5 |                  6 |                  7 |                  8 |
| 2                  |                  9 |                 10 |                 11 |                 12 |
| 3                  |                 13 |                 14 |                 15 |                 16 |
| 4                  |                 17 |                 18 |                 19 |                 20 |
| 5                  |                 21 |                 22 |                 23 |                 24 |
| 6                  |                 25 |                 26 |                 27 |                 28 |
| 7                  |                 29 |                 30 |                 31 |                 32 |
| 8                  |                 33 |                 34 |                 35 |                 36 |
| 9                  |                 37 |                 38 |                 39 |                 40 |

No gráfico, observamos que os cabeçalhos das colunas e as linhas da tabela estão ativos e reagem quando o ponteiro do mouse passa sobre eles. No próximo artigo, discutiremos como tratar a interação da tabela com o usuário.


Conclusão

Aprendemos a criar tabelas simples e exibi-las no gráfico. Por enquanto, porém, trata-se apenas de uma tabela estática, que exibe dados obtidos uma única vez. O próximo artigo será dedicado a tornar as tabelas dinâmicas: vamos alterar e exibir dados dinâmicos e adicionar interação com o usuário para configurar a exibição das linhas e colunas da tabela. No próximo artigo, também simplificaremos ainda mais a criação de tabelas.

Programas usados no artigo:

#
 Nome Tipo
Descrição
 1  Tables.mqh  Biblioteca de classes  Classes para criar o modelo da tabela
 2  Base.mqh  Biblioteca de classes  Classes para criar o objeto base de elementos de controle
 3  Controls.mqh  Biblioteca de classes  Classes dos elementos de controle
 4  iTestTable.mq5  Indicador de teste  Indicador para testar o uso do elemento de controle TableView
 5  MQL5.zip  Arquivo  Arquivo compactado com os arquivos apresentados acima, para ser descompactado no diretório MQL5 do terminal cliente

Todos os arquivos criados estão anexados ao artigo para consulta e análise. Depois de descompactar o arquivo na pasta do terminal, todos os arquivos ficarão na pasta necessária: \MQL5\Indicators\Tables\.

O código-fonte completo do projeto, com todos os arquivos descritos no artigo, está disponível no repositório.


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

Arquivos anexados |
Tables.mqh (261.78 KB)
Base.mqh (294.37 KB)
Controls.mqh (650.82 KB)
iTestTable.mq5 (10.71 KB)
MQL5.zip (111.29 KB)
Últimos Comentários | Ir para discussão (2)
Alireza
Alireza | 29 dez. 2025 em 04:45

Excelente explicação sobre o MVC, mas como exatamente essa complexidade melhora as negociações, que é o que realmente nos interessa aqui?

Será que todos os outros desafios da negociação já foram resolvidos, restando apenas a interface do usuário?

Às vezes esquecemos para que uma ferramenta realmente serve — ou ficamos frustrados quando ela não funciona da maneira que queremos — e acabamos tentando fazê-la realizar tarefas para as quais ela nunca foi projetada e que nunca será capaz de realizar

Artyom Trishkin
Artyom Trishkin | 29 dez. 2025 em 09:02
Alireza #:

Excelente descrição do MVC, mas como exatamente essa complexidade melhora o comércio, que é, afinal, o motivo pelo qual nos reunimos aqui?

Será que todos os outros problemas do comércio já foram resolvidos e só resta a interface do usuário?

Às vezes, esquecemos para que realmente serve uma ferramenta, ou ficamos frustrados quando ela não funciona como queremos e, no fim das contas, tentamos forçá-la a fazer algo para o qual ela nunca foi projetada e que nunca fará.

Sim, claro, não é a interface que gera lucro. Mas a interface é como o painel de instrumentos de um avião: ela não ensina a voar, mas ajuda a não cair no meio da neblina. Quando os principais problemas da negociação já estão resolvidos, a arquitetura do código e a facilidade de uso da plataforma se tornam o fator que permite ampliar o sucesso, em vez de afundar na rotina.
Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Redes neurais em trading: modelo de difusão adaptativa em grafos (SAGDFN) Redes neurais em trading: modelo de difusão adaptativa em grafos (SAGDFN)
Neste artigo, exploramos a arquitetura SAGDFN, um framework moderno capaz de transformar a forma de processar dados espaço-temporais. Ele preserva informações essenciais mesmo em grafos complexos e, ao mesmo tempo, reduz os custos computacionais.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
EA baseado em rede neural com PatchTST EA baseado em rede neural com PatchTST
Este artigo apresenta a revolucionária arquitetura PatchTST, um transformer adaptado à análise de séries temporais financeiras que divide os dados de mercado em patches de 16 barras para um processamento mais eficiente. O texto examina detalhadamente a implementação completa de um robô de negociação em MQL5, abordando desde os fundamentos matemáticos e as estruturas de dados até a criação de um Expert Advisor pronto para uso, incluindo sistemas de gestão de risco e treinamento contínuo.