Tabelas no paradigma MVC em MQL5: Integramos o componente Model ao componente View
Sumário
- Introdução
- Aprimorando as classes da biblioteca
- Classe de célula da tabela (View)
- Classe de linha da tabela (View)
- Classe de cabeçalho de coluna da tabela (View)
- Classe de cabeçalho da tabela (View)
- Classe de tabela (View)
- Testando o resultado
- Conclusão
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:
- Panel: base à qual anexamos o cabeçalho e a área de dados da tabela;
- 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;
- Container: contêiner de dados tabulares com possibilidade de rolagem do conteúdo;
- 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;
- 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;
- 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:
- Criação do painel no qual a tabela será montada;
- Criação dos dados tabulares em dois arrays: tabela e cabeçalho (componentes Model);
- 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
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Caminhe em novos trilhos: Personalize indicadores no MQL5
Redes neurais em trading: modelo de difusão adaptativa em grafos (SAGDFN)
Está chegando o novo MetaTrader 5 e MQL5
EA baseado em rede neural com PatchTST
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
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
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á.