Tables in the MVC Paradigm in MQL5: Integrating the Model Component into the View Component
Contents
- Introduction
- Refining library classes
- Table cell class (View)
- Table row class (View)
- Column header class of the table (View)
- Table header class (View)
- Table class (View)
- Testing the Result
- Conclusion
Introduction
In article “Table and Header Classes based on a table model in MQL5: Applying the MVC concept" we completed creating the table model class (in the MVC concept it means the Model component). Next, we were developing a library of simple controls that allow for creating controls based on them that are completely different in purpose and complexity. In particular, the View component for creating the TableView control.
This article will cover implementation of the interaction between the Model component and the View component. In other words, today we will combine tabular data with their graphical representation in a single control.
The control will be created based on the Panel object class, and will consist of several elements:
- Panel is the base, the substrate to which the table header and the data area of the table are attached;
- Panel is a table header consisting of a number of elements: column headers created based on the Button object class;
- Container is the tabular data container with scrollable content;
- Panel is a panel for arranging table rows on it (attached to the container (item 3) and, when going beyond the container, scrolls using container scrollbars);
- Panel — table row is a panel for drawing table cells on it, attached to the panel (item 4). A number of such objects corresponds to the number of rows in the table;
- The table cell class is a new class that allows drawing at specified coordinates on the indicated canvas (CCanvas). An object of this class is attached to a table row object (item 5), the canvases of the table row object are specified as drawing elements; and the table cell is drawn on this panel at the specified coordinates. The area of each table cell is set in the table row object (item 5) by an instance of the CBound class object, and an object of the table cell class is attached to this object.
This algorithm for constructing rows and cells in a table, where each row is divided into horizontal areas to which cell class objects are attached, makes it easy to sort and reposition cells by simply reassigning cells to the desired areas of the row.
Based on the above written, we understand that we need a new object class to which a pointer to the control will be passed, and drawing will take place on its canvas. Currently, all library objects, when created by the new operator, create their own canvas objects (background and foreground). But we want an object that will enable setting and getting its size, as well setting a pointer to the control in it, on which the drawing will be performed.
The CBound class enables to set and retrieve the dimensions of the created object, and it is located as part of the base object of all controls. Background and foreground canvases are created in that very object. This means that another intermediate object should be created that will inherit from the base one and will include the CBound class for setting and getting dimensions. The class in which CCanvas objects are created will inherit from this object. Thus, from this intermediate class we can inherit the class to which a pointer to the control on which we will draw, will be passed.
Let's finalize the files of the already created classes, and only then write new ones.
Refining Library Classes
All files of the library being developed are located at \MQL5\Indicators\Tables\Controls\. If they are not available yet, a previous version of all files is available in the previous article. The project will also require a table class file (the Model component), which can be obtained from the article where we completed their development. Tables.mqh must be saved to \MQL5\Indicators\Tables\. Files Base.mqh and Control.mqh will be refined.
At the very beginning of Tables.mqh file, in the macros section, write the file ID:
//+------------------------------------------------------------------+ //| Макросы | //+------------------------------------------------------------------+ #define __TABLES__ // Идентификатор данного файла #define MARKER_START_DATA -1 // Маркер начала данных в файле #define MAX_STRING_LENGTH 128 // Максимальная длина строки в ячейке #define CELL_WIDTH_IN_CHARS 19 // Ширина ячейки таблицы в символах
This is necessary so that the MARKER_START_DATA macro substitution can be declared in two different include files for their separate compilation.
Open Base.mqh file. Connect the class file of the table model to it and specify the forward declaration of new 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 //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+
In the macro substitutions section, frame the declaration of MARKER_START_DATA macro by verifying its existence:
//+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #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 // Толщина зоны для захвата границы/угла
Now declaring the same macro substitution in two different files will not result in compilation errors, either individually or jointly between the two files.
Add new types to the enumeration of types of UI elements, and set a new maximum type of the active element:
//+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ 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 // Максимальное значение списка активных элементов
Add new return values to the function that returns the element’s short name by type:
//+------------------------------------------------------------------+ //| Возвращает короткое имя элемента по типу | //+------------------------------------------------------------------+ 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 } }
In the base class of UI elements, make the identifier setting method virtual, so far as it should be redefined in the new classes:
//+------------------------------------------------------------------+ //| Базовый класс графических элементов | //+------------------------------------------------------------------+ 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) {} };
Each object within its composition has a rectangular area class CBound, which returns the size of the control. Also, each control has a list of CBound objects. This list allows you to store many CBound objects, which in turn allow you to specify many different areas on the UI element surface.
Each such area can be used for any scenarios. For example, you can define a certain zone inside an UI element, track the cursor location inside this area and react to the interaction of this area with the mouse cursor. A certain control can be placed within a certain area to display it inside that area. To do this, it is necessary to make it possible to write a pointer to the control to the CBound object.
Implement such a feature in the CBound class:
//+------------------------------------------------------------------+ //| Класс прямоугольной области | //+------------------------------------------------------------------+ 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); } };
At the moment, the CBound rectangular area object is declared in the CCanvasBase class, in which two CCanvas objects for drawing the background and foreground of an UI element are created. That is, all the UI elements have two canvases within them. But we want to create a class that will allow us to specify in it the control on which canvases we want to draw. At the same time, not to create own canvases, and at the same time not to stray from the general concept of creating graphical elements.
This means that we want to transfer the CBound class object from the CCanvasBase class to another parent class, which will not create canvas objects, and from which the CCanvasBase class will inherit. And in the CCanvasBase class, not to declare instances of two CCanvas, but to declare pointers to the canvases being created and implement a method that will create two canvas objects. Also, we want to declare a flag indicating that a graphical element controls or does not control canvas objects of the background and foreground. This is required to manage the deletion of created canvas objects.
In the Base.mqh file, immediately after the CAutoRepeat class, before the CCanvasBase class, implement a new CBoundedObj class, inherited from CBaseObj, into which simply transfer all methods of working with the CBound object from the CCanvasBase class:
//+------------------------------------------------------------------+ //| Базовый класс, хранящий размеры объекта | //+------------------------------------------------------------------+ 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); }
The presented class simply inherits properties of the CBaseObj base class, allows to specify object boundaries (the CBound class), and has a canvas management flag.
The base class of the graphical elements canvas should now be inherited from the new CBoundedObj, rather than from the CBaseObj class, as it was before. And everything related to working with the CBound class object to set and return sizes of an element has already been moved to a new CBoundedObj class and removed from the CCanvasBase class. Now canvas objects are not declared as instances of a class, but as pointers to CCanvas objects being created:
//+------------------------------------------------------------------+ //| Базовый класс холста графических элементов | //+------------------------------------------------------------------+ 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; // Объект управления цветом рамки
Methods for obtaining coordinates and sizes of canvas graphical objects are performed public, and methods for resizing canvases are implemented virtual:
//--- Возврат координат, границ и размеров графического объекта 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)); }
A method for creating canvases is declared in the protected section of the class.:
//--- Устанавливает указатель на родительский объект-контейнер 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; }
The method that sets a flag for allowing cropping of an element along container boundaries is implemented virtual:
//--- Устанавливает объекту флаг (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; }
In the class constructor a method for creating canvas objects for background and foreground is now called:
//+------------------------------------------------------------------+ //| 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(); } }
A method that creates canvases for the background and foreground:
//+------------------------------------------------------------------+ //| 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; }
In the method that destroys the object, now check the canvas management flag:
//+------------------------------------------------------------------+ //| 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; } }
Canvases are deleted only if an object manages canvases. If canvases of another control are passed to an object for drawing, it will not delete them — they should be deleted only by the object to which they belong.
In the default initialization method of the class, a canvas management flag is set for the object:
//+------------------------------------------------------------------+ //| 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; }
Only for those objects that do not have their own canvases, and which will inherit from the CBoundedObj class, they will have to reset this flag to false.
Now open the Controls.mqh file and make improvements.
In the macro substitutions section, implement two new ones that define the default height of the table row and header:
//+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #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 // Частота автоповторов
The file contains a class of linked list of CListObj objects for storing a list of graphical elements. We copied it to this file from the table model class file. If you leave the name of the class the same, you will have a name conflict, and the objects stored in these two lists in different files are completely different. So here leave the class unchanged, but rename it. This will be a class of the linked list of UI elements:
//+------------------------------------------------------------------+ //| Класс связанного списка графических элементов | //+------------------------------------------------------------------+ 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); };
Replace the string "CListObj" with the string "CListElm" throughout the file.
In the method of creating a list item, add new types of UI elements:
//+------------------------------------------------------------------+ //| Метод создания элемента списка | //+------------------------------------------------------------------+ 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; } }
In the UI element base class, add a method that returns a pointer to the object:
public: //--- Возвращает себя CElementBase *GetObject(void) { return &this; } //--- Возвращает указатель на (1) класс рисования, (2) список подсказок CImagePainter *Painter(void) { return &this.m_painter; } CListElm *GetListHints(void) { return &this.m_list_hints; }
In the text label class, make the method that displays the text on canvas, virtual:
//--- Устанавливает координату (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);
In the CPanel panel class, refine the resizing methods:
//+------------------------------------------------------------------+ //| 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; }
The essence of methods refinement is that if an element is within a container, or if an element has been resized, then it is necessary to check the new element dimensions with respect to the container. If an element has become larger than container bounds, then the element is cropped according to the container size (this has already been implemented), but scrollbars should still appear on the container. We added this check using the CheckElementSizes() container object method to methods for UI element resizing.
In the method for drawing the appearance of a panel object in the CPanel class, draw a border only if at least one of the four sides has a non-zero value for the BorderWidth parameter:
//+------------------------------------------------------------------+ //| 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); }
In the method of creating and adding a new element to the list, in the panel object class, add creation of new controls:
//+------------------------------------------------------------------+ //| 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; }
In the method that places an object in the foreground, add cropping of attached objects according to the container size:
//+------------------------------------------------------------------+ //| 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); }
There is a flag in the horizontal scrollbar class indicating that the element can be cropped along the container bounds. The scrollbar object consists of several objects, and for each of them the same flag value should be set. Let's do everything in one method. In the class redefine the method that sets the trim flag along the container boundaries:
//+------------------------------------------------------------------+ //| Класс горизонтальной полосы прокрутки | //+------------------------------------------------------------------+ 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) {} };
Implementing the method:
//+------------------------------------------------------------------+ //| 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); }
A flag passed to the method is set for each of the objects that make up the scrollbar.
In the object initialization method, you no longer need to set the trim flag for each of the elements along container boundaries. It is sufficient to call the SetTrimmered() method at the end of the method:
//+------------------------------------------------------------------+ //| 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); }
Exactly the same improvements have been made for the vertical scrollbar class. They are completely identical to those discussed, and we will not repeat them.
Make the CheckElementSizes method public in the container object class:
//+------------------------------------------------------------------+ //| Класс Контейнер | //+------------------------------------------------------------------+ 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()); }
This method is called from the elements bound to the container, and must be public to access it.
We have reviewed the main improvements to library classes. Some minor improvements and fixes have been left out of the scope. View full codes in the files attached to the article.
The table model consists of a table cell, a table row, and a list of rows that end up being a table. In addition to the above, the table also has a header consisting of table column headers.
- A table cell is a separate element that stores the row number, column number, and contents;
- A table row is a list of table cells that has its own number;
- A table is a list of rows, and it can have a header that represents a list of column headers.
The View component will have approximately the same structure for displaying data from the table model. The table object will be based on, for example, an array of data and an array of headers of this data. Then we will pass a pointer to the table model (the Model component) to the created table visual display object (the View component) and all data will be written to table cells.
At this milestone, a simple static table will be created without the feature to control its display with the mouse cursor.
Table Cell Class (View)
The table consists of a list of rows. The rows, in turn, consist of objects provided with two canvases for drawing the background and foreground. Table cells are arranged horizontally on their rows. It makes no sense to give cells their own canvases. You can simply "slice" each row into areas, where each area will correspond to the location of its cell. And draw the cell data on the canvas of the table row object.
It is for these purposes that we created an intermediate class at the very beginning. It has only the object size with its coordinates and a pointer to the control on which canvas drawing will be performed. And such an object will be a table cell. We will pass a pointer to the table row object for drawing on its canvases.
Implement a new class in Controls.mqh:
//+------------------------------------------------------------------+ //| Класс визуального представления ячейки таблицы | //+------------------------------------------------------------------+ 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){} };
All variables and methods here are commented out in the code. The cell's index and its identifier are the same thing here. Therefore, the SetID() virtual method has been redefined, where the index and identifier are set to the same values. Let's look at the implementation of declared methods.
In the class constructors the object initialization method is called, the cell identifier and its name are set:
//+------------------------------------------------------------------+ //| 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); }
Class Object Initialization Method:
//+------------------------------------------------------------------+ //| 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; }
Be sure to set a flag for the object that it does not manage canvases, so that when the object is destroyed, it does not delete other objets' canvases.
A Method That Returns Description of the Object:
//+------------------------------------------------------------------+ //| 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()); }
The method prints out to the log a line with the name of the object type, identifier, coordinates and cell sizes.
A Method That Assigns a Table Row and Background and Foreground Canvases To a Cell:
//+------------------------------------------------------------------+ //| 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(); }
A pointer to a table row object is passed to the method (the class description will be below) and if the pointer is not valid, report it and return NULL. Next, a pointer to a table row is written to class variables, and from the table row object pointers are written to canvases of the background and foreground, and to the drawing object.
The table model object also has its own rows and cells. A table cell model is assigned to this class, and it draws data from the cell model on the row object canvas.
A Method That Assigns the Cell Model:
//+------------------------------------------------------------------+ //| 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; }
First, the validity of the passed pointer to the cell model is checked, then it is checked that the table row has already been assigned to this cell object. If any of this is incorrect, it is reported in the log and false is returned. Next, the pointer to the cell model is stored in a variable and the coordinates and dimensions of the cell area on the corresponding row are set.
When sorting out rows and columns of the table, the pointer to the cell object will simply be reassigned, and this object, which has its exact coordinates in the table, will draw the data of the new cell assigned to it.
A Method That Returns the X and Y Coordinates Of the Text Depending On the Anchor Point:
//+------------------------------------------------------------------+ //| 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; }
Variables are passed to the method by reference, into which the calculated coordinates of location of the upper-left corner of the output text will be written. If the cell text size fails, the method returns false.
After calculating the coordinates of the upper-left corner, the calculated coordinates of the text are written to the variables and return true.
A Method That Sets the Anchor Point of the Text:
//+------------------------------------------------------------------+ //| 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); }
First, a new anchor point is written to the variable, then, if the redraw flag for this cell is set, the method for drawing it is called with the specified chart redraw flag.
A Method That Sets the Anchor Point and Text Shift:
//+------------------------------------------------------------------+ //| 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); }
In this method, in addition to setting the anchor point, initial text shifts along the X and Y axes are set.
A Method That Fills In an Object With a Color:
//+------------------------------------------------------------------+ //| 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); }
Get correct coordinates of the four corners of the rectangular area. Then fill the background with the background color of the table row, and the foreground with a transparent color.
A Method That Updates an Object To Display Changes:
//+------------------------------------------------------------------+ //| 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); }
If the background and foreground canvases are valid, their update methods are called with the specified chart redrawing flag.
A Method That Draws the Cell View:
//+------------------------------------------------------------------+ //| 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); }
The method's logic is explained in the comments. Correction of coordinates is necessary if the object is cropped along the container boundaries. In this case, the initial coordinates are calculated not from the actual coordinates of the canvas graphical object, but from the graphical element coordinates, which are virtually located outside the container boundaries.
Dividing lines are drawn only on the right side of the cell. And if it is not the cell on the far right, it is limited by its container, and it is not expedient to draw an additional line there.
A Method For Displaying the Cell Text:
//+------------------------------------------------------------------+ //| 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); }
The method's logic is fully explained in comments to the code. If the printed text extends beyond the cell area, it is cropped along the area boundaries, and its end is replaced with a colon ("..."), indicating that not all the text fits into the cell size in its width.
A Method That Prints Out the Assigned String Model to the Log:
//+------------------------------------------------------------------+ //| CTableCellView::Распечатывает в журнале назначенную модель строки| //+------------------------------------------------------------------+ void CTableCellView::TableCellModelPrint(void) { if(this.m_table_cell_model!=NULL) this.m_table_cell_model.Print(); }
This method allows you to control which cell of the Model component is assigned to the cell of the View component.
A Method for Operating Files:
//+------------------------------------------------------------------+ //| 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; }
The methods allow you to save the table cell data to a file and load it from the file.
Now let's study the visual representation class of a table row.
Table Row Class (View)
A table row is an object inherited from the panel class. It has a list of cell objects of the CTableCellView class.
Continue writing the code in Controls.mqh file:
//+------------------------------------------------------------------+ //| Класс визуального представления строки таблицы | //+------------------------------------------------------------------+ 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){} };
A table row has the same property as a cell, namely, the row index property is equal to its identifier. Here, the virtual method of setting the identifier is also redefined, and sets both properties simultaneously — the index and the identifier.
In the class constructors the object initialization method is called:
//+------------------------------------------------------------------+ //| 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(); }
Object Initialization:
//+------------------------------------------------------------------+ //| CTableRowView::Инициализация | //+------------------------------------------------------------------+ void CTableRowView::Init(void) { //--- Инициализация родительского объекта CPanel::Init(); //--- Фон - непрозрачный this.SetAlphaBG(255); //--- Ширина рамки this.SetBorderWidth(1); }
Default Object Color Initialization Method:
//+------------------------------------------------------------------+ //| 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); }
A Method That Implements and Adds a New Cell Representation Object to the List:
//+------------------------------------------------------------------+ //| 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; }
In addition to the fact that each cell is assigned to its own area of the table row object, objects created using the new operator must still be stored in the list, or they should be tracked by yourself and deleted, if necessary.
It is easier to store them in a list — then the terminal subsystem itself will keep track of when they need to be deleted. The method creates a new cell view object and places it in the list of row cells. After creating an object, it is assigned a row in which it is located, and on which canvases the created object will draw model data of the table cell.
The Method That Sets the Row Model:
//+------------------------------------------------------------------+ //| 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; }
A pointer to the row model is passed to the method, and in a loop by the number of cells in the model, new areas of the cell representation objects and the cells themselves are created (CTableCellView class). And each new such cell is assigned to each new area. In the end, we get a list of cell areas to which pointers to the corresponding table cells are assigned.
A Method That Draws the Row View:
//+------------------------------------------------------------------+ //| 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); }
First, the row is filled with the background color and a line is drawn from below. Then, in a loop through the cell areas of the row, we get the next cell area, from it we get a pointer to the cell object and call its drawing method.
A Method That Prints Out the Assigned String Model to the Log:
//+------------------------------------------------------------------+ //| 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); }
The method allows you to control which row model from the table model is assigned to this row visual representation object.
A Method for Operating Files:
//+------------------------------------------------------------------+ //| 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; }
The methods allow you to save the table row data to a file and load it from the file.
Each table must have a header. It allows you to understand what data is displayed in table columns. A table header is a list of column headers. Each column of the table has its own header, which displays the type and name of the data located in the column.
Column header class of the table (View)
The column header of the table is an independent object inherited from the “Button” object (CButton), inheriting this object’s behavior and complementing it. In the current implementation, the button properties will not be supplemented — we simply change colors of various states. In the next article, to the header we will add an indication of the sorting direction — up/down arrows on the right side of the button space and an event model to start sorting by clicking on the button.
Continue writing the code in Controls.mqh file:
//+------------------------------------------------------------------+ //| Класс визуального представления заголовка столбца таблицы | //+------------------------------------------------------------------+ 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){} };
The method has an index equal to the identifier, as in the case of the two previous classes considered. And the method of setting the identifier is similar to the previous classes.
In the class constructors the object initialization method is called and the null identifier is set (it changes after the object is created):
//+------------------------------------------------------------------+ //| 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); }
Object Initialization Method:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Инициализация | //+------------------------------------------------------------------+ void CColumnCaptionView::Init(const string text) { //--- Смещения текста по умолчанию this.m_text_x=4; this.m_text_y=2; //--- Устанавливаем цвета различных состояний this.InitColors(); }
Default Object Color Initialization Method:
//+------------------------------------------------------------------+ //| 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); }
A Method That Draws the Column Header View:
//+------------------------------------------------------------------+ //| 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); }
First, the background is filled in with the set color, then two vertical lines are drawn — a light one on the left and a dark one on the right. Next, a title text is displayed on the foreground canvas.
A Method That Returns Description of the Object:
//+------------------------------------------------------------------+ //| 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()); }
The method returns a description of the element type with the ID, coordinates, and dimensions of the object.
A Method That Assigns the Column Header Model:
//+------------------------------------------------------------------+ //| 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; }
A pointer to the column header model is passed to the method, which is saved to a variable. Next, coordinates and dimensions of the drawing area are set for the drawing object.
A Method That Prints Out Assigned Column Header Model to the Log:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Распечатывает в журнале | //| назначенную модель заголовка столбца | //+------------------------------------------------------------------+ void CColumnCaptionView::ColumnCaptionModelPrint(void) { if(this.m_column_caption_model!=NULL) this.m_column_caption_model.Print(); }
Allows you to check, by printing in the log a description of the column header model assigned to the object of visual representation of the header.
A Method for Operating Files:
//+------------------------------------------------------------------+ //| 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; }
They provide a feature to save and load header parameters from a file.
Table Header Class (View)
The table header is a regular list of column header objects based on Panel control (CPanel). As a result, such an object provides tools for managing table columns.
In this implementation, it will be a regular static object. All the functionality of user interaction will be added later.
Continue writing the code in the same file:
//+------------------------------------------------------------------+ //| Класс визуального представления заголовка таблицы | //+------------------------------------------------------------------+ 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){} };
A table header model is assigned to the object, and column header objects are created based on its contents and added to the list of attached elements.
In the class constructors the object initialization method is called:
//+------------------------------------------------------------------+ //| 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(); }
Object Initialization Method:
//+------------------------------------------------------------------+ //| CTableHeaderView::Инициализация | //+------------------------------------------------------------------+ void CTableHeaderView::Init(void) { //--- Инициализация родительского объекта CPanel::Init(); //--- Цвет фона - непрозрачный this.SetAlphaBG(255); //--- Ширина рамки this.SetBorderWidth(1); }
Default Object Color Initialization Method:
//+------------------------------------------------------------------+ //| 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); }
A Method That Implements and Adds a New Column Header Representation Object to the List:
//+------------------------------------------------------------------+ //| 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); }
The column header object is created using the standard InsertNewElement() method for library objects, which places created objects in the list of object’s UI elements.
A Method That Sets the Header Model:
//+------------------------------------------------------------------+ //| 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; }
The table header model is passed to the method. In the loop, according to the number of column headers in the model, create another area to place the column header. Create a column header object and assign it to the current area. At the cycle end, we will have a list of areas with column headers assigned to them.
A Method That Draws the Table Header View:
//+------------------------------------------------------------------+ //| 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); }
First, the entire table header area is filled in with the background color, then a dividing line is drawn from below. Next, in the loop, through the list of header areas, get another area. And from this area get the column header object assigned to this area. The drawing method of this object we call.
A Method That Prints Out Assigned Table Header Model to the Log:
//+------------------------------------------------------------------+ //| 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); }
Allows you to print the table header model assigned to this object in the log.
A table, its visual representation, is a composite object that receives data about the table and a header from its model, and draws this data on various controls. This is a panel that contains a header and a container with tabular data (table rows).
Table class (View)
Continue writing the code in Controls.mqh file:
//+------------------------------------------------------------------+ //| Класс визуального представления таблицы | //+------------------------------------------------------------------+ 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){} };
The class is inherited from the panel object class. Three Model components and three View components are visible. The class has pointers to the table model, header model, and the table. From the table we get pointers to the tabular data and the header. And there are three pointers to the header, the table row panel and a container for locating this panel.
The point is that the rows of the table are attached to the panel object (many elements can be attached), and this panel is attached to the container (you can attach a single element), where there are scrollbars that can be used to move the panel with table rows. Thus, visually, the element looks like a table header, and below a scrollable table is located (rows and columns formed by cells).
In the class constructors call the object initialization method:
//+------------------------------------------------------------------+ //| 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(); }
In the initialization method, create all the necessary controls on the object panel:
//+------------------------------------------------------------------+ //| 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); }
A Method That Sets the Table Model:
//+------------------------------------------------------------------+ //| 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; }
We simply pass a pointer to the table model object to the method and write it to a variable from which we will access it.
A Method That Sets the Table Header Model:
//+------------------------------------------------------------------+ //| 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; }
Pass a pointer to the table header model object to the method and write it to a variable from which we will access it.
A Method That Sets a Table Object:
//+------------------------------------------------------------------+ //| 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; }
A pointer to a table object is passed to the method. From this object, get and assign the header model and the table model. Next, using the data from the header and table models, create objects for the visual representation of the header and table.
A Method That Creates a Table Object From a Model:
//+------------------------------------------------------------------+ //| 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); }
The method's logic is explained in the comments to the code. In a loop, by the number of rows in the table model, create a new table row object and attach it to the panel; while simultaneously calculating the future height of the panel. After completing the loop and creating all the necessary rows, the panel height is adjusted according to the size calculated in the loop.
A Method That Draws the Table View:
//+------------------------------------------------------------------+ //| 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); }
First, draw the table header, and under it — table rows in the container.
A Method That Prints Out the Assigned Table Model to the Log:
//+------------------------------------------------------------------+ //| CTableView::Распечатывает в журнале назначенную модель таблицы | //+------------------------------------------------------------------+ void CTableView::TableModelPrint(const bool detail) { if(this.m_table_model!=NULL) this.m_table_model.Print(detail); }
It allows printing the assigned table model to the log.
A Method That Prints Out Assigned Table Header Model to the Log:
//+------------------------------------------------------------------+ //| 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); }
It allows printing the assigned table header model to the log.
A Method That Prints Out the Assigned Table Object to the Log:
//+------------------------------------------------------------------+ //| 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); }
It prints out the entire table assigned to the object in the log, including the header and data.
That's it, we have implemented all the classes to create a visual representation of the table model. Let's see how you can now create a simple table from an array of data.
Testing the Result
In the terminal directory \MQL5\Indicators\Tables\ create a new indicator which has no buffers and is being built in the chart subwindow:
//+------------------------------------------------------------------+ //| 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
Connect the library to the indicator file and declare pointers to objects globally:
//+------------------------------------------------------------------+ //| 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)
Further, write the following code in the OnInit() handler of the indicator:
//+------------------------------------------------------------------+ //| 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); }
Here we have three blocks of the code:
- Creating a panel on which the table will be built;
- Creating tabular data in two arrays — a table and a header (Model components);
- Creating a table in the panel (View component).
In fact, two steps are required to create a table — to build data (item 2) and to build a table (item 3). The panel is only needed for decoration and as the basis for a graphical element.
Add the rest of the indicator code:
//+------------------------------------------------------------------+ //| 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(); }
Compile the indicator and run it on the chart:

The terminal log will print out a message about a successfully created table, table description and the table with a header:
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 |
On the chart, we can see that column headers and table rows are active and respond to mouse hovering. Handling of table's interaction with the user will be discussed in the next article.
Conclusion
We have learned how to create simple tables and display them on a chart. But so far, this is just a static table displaying the data received once. The next article will focus on table animation — changing and displaying dynamic data and interacting with the user to customize the display of rows and columns in a table. As well, in the next article, we will implement even simpler creation of tables.
Programs used in the article:
| # | Name | Type | Description |
|---|---|---|---|
| 1 | Tables.mqh | Class Library | Classes for creating a table model |
| 2 | Base.mqh | Class Library | Classes for creating a base object of controls |
| 3 | Controls.mqh | Class Library | Control classes |
| 4 | iTestTable.mq5 | Test indicator | Indicator for testing manipulations with the TableView control |
| 5 | MQL5.zip | Archive | An archive of the files above for unpacking into the MQL5 directory of the client terminal |
All created files are attached to the article for self-study. The archive file can be unzipped to the terminal folder, and all files will be located in the desired folder: \MQL5\Indicators\Tables\.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/19288
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Creating Custom Indicators in MQL5 (Part 1): Building a Pivot-Based Trend Indicator with Canvas Gradient
Introduction to MQL5 (Part 32): Mastering API and WebRequest Function in MQL5 (VI)
Features of Experts Advisors
From Novice to Expert: Automating Trade Discipline with an MQL5 Risk Enforcement EA
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use