The View and Controller components for tables in the MQL5 MVC paradigm: Simple controls
Contents
- Introduction
- Controller Component. Refining Base Classes
- Simple Controls
- Auxiliary Classes
- Text Label
- Simple Button
- Two-way Button
- Up Arrow Button
- Down Arrow Button
- Left Arrow Button
- Right Arrow Button
- Checkbox
- Radio Button
- Testing the Result
- Conclusion
Introduction
As part of the development of the Table View control in MVC (Model-View-Controller) paradigm, we created a table model (the Model component) and started creating the View component. At the first stage, a basic object was created, which is the progenitor of all other graphical elements.
Today we will start developing simple controls that will later serve as building blocks for composite UI elements. Each control element will possess functionality for interaction with the user and with other elements. In other words, this essentially corresponds to the functionality of the Controller component.
Since in the MQL language the event model is integrated into objects created using chart events, event handling will be organized in all subsequent controls to implement the connection between the View component and the Controller component. To do this, refine the base class of graphical elements.
Next, create simple controls — a text label and various buttons. Each element will support drawing icons. This will make it possible to create completely different controls from simple buttons. If you look at the string of the tree view, where an icon is on the left and text is on the right, then this seems to be a separate control. But we can easily create such a control by using a regular button as a base. At the same time, it will be possible to adjust the string parameters so that it either reacts by changing the color when the mouse cursor is focused on and clicked, or it is static, but reacts to clicks.
All this can be implemented with just a few configuration lines after creating the object. And from such elements, we will continue to create complex composite controls that are fully interactive and ready to use.
Controller Component. Refining Base Classes
So, in order to implement our plans, we must slightly refine the already implemented classes, macro substitutions, and enumerations. Most of the necessary functionality will be located in the base object of graphical elements. Therefore, it is it that will be mainly refined.
Previously, this class was at MQL5\Scripts\Tables\Controls\Base.mqh.
Today we will write a test indicator, so we want to create a new folder \Tables\Controls\ in the indicator directory \MQ5\Indicators\ and locate the Base.mqh file in it. That's what we'll be working on today.
Further, objects will contain lists of attached controls. Containers can be such objects, for example. In order for these lists to handle files correctly i.e. create objects stored in lists, it is necessary to declare all classes of elements being created in advance. Write a class declaration, new macro substitutions, enumerations, and enumeration constants into Base.mqh file:
//+------------------------------------------------------------------+ //| Base.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include <Canvas\Canvas.mqh> // Класс СБ CCanvas #include <Arrays\List.mqh> // Класс СБ CList //--- Форвард-декларация классов элементов управления class CImagePainter; // Класс рисования изображений class CLabel; // Класс текстовой метки class CButton; // Класс простой кнопки class CButtonTriggered; // Класс двухпозиционной кнопки class CButtonArrowUp; // Класс кнопки со стрелкой вверх class CButtonArrowDown; // Класс кнопки со стрелкой вниз class CButtonArrowLeft; // Класс кнопки со стрелкой влево class CButtonArrowRight; // Класс кнопки со стрелкой вправо class CCheckBox; // Класс элемента управления CheckBox class CRadioButton; // Класс элемента управления RadioButton //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define clrNULL 0x00FFFFFF // Прозрачный цвет для CCanvas #define MARKER_START_DATA -1 // Маркер начала данных в файле #define DEF_FONTNAME "Calibri" // Шрифт по умолчанию #define DEF_FONTSIZE 10 // Размер шрифта по умолчанию //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_TYPE // Перечисление типов графических элементов { ELEMENT_TYPE_BASE = 0x10000, // Базовый объект графических элементов ELEMENT_TYPE_COLOR, // Объект цвета ELEMENT_TYPE_COLORS_ELEMENT, // Объект цветов элемента графического объекта ELEMENT_TYPE_RECTANGLE_AREA, // Прямоугольная область элемента ELEMENT_TYPE_IMAGE_PAINTER, // Объект для рисования изображений ELEMENT_TYPE_CANVAS_BASE, // Базовый объект холста графических элементов ELEMENT_TYPE_LABEL, // Текстовая метка ELEMENT_TYPE_BUTTON, // Простая кнопка ELEMENT_TYPE_BUTTON_TRIGGERED, // Двухпозиционная кнопка ELEMENT_TYPE_BUTTON_ARROW_UP, // Кнопка со стрелкой вверх ELEMENT_TYPE_BUTTON_ARROW_DOWN, // Кнопка со стрелкой вниз ELEMENT_TYPE_BUTTON_ARROW_LEFT, // Кнопка со стрелкой влево ELEMENT_TYPE_BUTTON_ARROW_RIGHT, // Кнопка со стрелкой вправо ELEMENT_TYPE_CHECKBOX, // Элемент управления CheckBox ELEMENT_TYPE_RADIOBUTTON, // Элемент управления RadioButton }; enum ENUM_ELEMENT_STATE // Состояние элемента { ELEMENT_STATE_DEF, // По умолчанию (напр. кнопка отжата, и т.п.) ELEMENT_STATE_ACT, // Активирован (напр. кнопка нажата, и т.п.) }; enum ENUM_COLOR_STATE // Перечисление цветов состояний элемента { COLOR_STATE_DEFAULT, // Цвет обычного состояния COLOR_STATE_FOCUSED, // Цвет при наведении курсора на элемент COLOR_STATE_PRESSED, // Цвет при нажатии на элемент COLOR_STATE_BLOCKED, // Цвет заблокированного элемента }; //+------------------------------------------------------------------+ //| Функции | //+------------------------------------------------------------------+
When creating methods for saving and loading objects to/from files, each method has constant repeating strings without changing from method to method:
//--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем идентификатор if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем наименование if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false;
And there are the same methods Description and Print. So, it is reasonable to transfer these strings to load/save methods in the base object. Then they won't have to be written in every new load/save method in every new class where manipulations with files are provided.
Declare these methods in the base object:
public: //--- Устанавливает (1) наименование, (2) идентификатор void SetName(const string name) { ::StringToShortArray(name,this.m_name); } void SetID(const int id) { this.m_id=id; } //--- Возвращает (1) наименование, (2) идентификатор string Name(void) const { return ::ShortArrayToString(this.m_name); } int ID(void) const { return this.m_id; } //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_BASE); } //--- (1) Возвращает, (2) выводит в журнал описание объекта virtual string Description(void); virtual void Print(void); //--- Конструктор/деструктор CBaseObj (void) : m_id(-1) { this.SetName(""); } ~CBaseObj (void) {} };
And write their implementation:
//+------------------------------------------------------------------+ //| CBaseObj::Возвращает описание объекта | //+------------------------------------------------------------------+ string CBaseObj::Description(void) { string nm=this.Name(); string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm); return ::StringFormat("%s%s ID %d",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.ID()); } //+------------------------------------------------------------------+ //| CBaseObj::Выводит в журнал описание объекта | //+------------------------------------------------------------------+ void CBaseObj::Print(void) { ::Print(this.Description()); } //+------------------------------------------------------------------+ //| CBaseObj::Сохранение в файл | //+------------------------------------------------------------------+ bool CBaseObj::Save(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем идентификатор if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем наименование if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CBaseObj::Загрузка из файла | //+------------------------------------------------------------------+ bool CBaseObj::Load(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=-1) return false; //--- Загружаем тип объекта if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return false; //--- Загружаем идентификатор this.m_id=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем наименование if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- Всё успешно return true; }
Now, for each new class, the Description and Print methods can be declared and implemented only if their logic differs from the logic prescribed in this class.
And in the methods of working with files in derived classes, instead of repeatedly writing the same code lines in each method of each class, we will simply address to the methods of working with files of this base object.
From all subsequent classes of this file (Base.mqh), remove all Print methods — they are already in the base object, and they completely repeat it.
In all methods of working with files, delete such strings:
//+------------------------------------------------------------------+ //| CColor::Сохранение в файл | //+------------------------------------------------------------------+ bool CColor::Save(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем цвет if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем идентификатор if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем наименование if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- Всё успешно return true; }
Now, instead of these strings, we just have a call of the base class method:
//+------------------------------------------------------------------+ //| CColor::Сохранение в файл | //+------------------------------------------------------------------+ bool CColor::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CBaseObj::Save(file_handle)) return false; //--- Сохраняем цвет if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; }
Such changes have already been made in all methods of working with files in this file. We will take these changes into account when writing subsequent classes.
In the CColorElement class, replace identical duplicate strings in class constructors.
//+------------------------------------------------------------------+ //| CColorControl::Конструктор с установкой прозрачных цветов объекта| //+------------------------------------------------------------------+ CColorElement::CColorElement(void) { this.InitColors(clrNULL,clrNULL,clrNULL,clrNULL); this.m_default.SetName("Default"); this.m_default.SetID(1); this.m_focused.SetName("Focused"); this.m_focused.SetID(2); this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3); this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4); this.SetCurrentAs(COLOR_STATE_DEFAULT); this.m_current.SetName("Current"); this.m_current.SetID(0); }
using one method Init():
public: //--- Возвращает новый цвет color NewColor(color base_color, int shift_red, int shift_green, int shift_blue); //--- Инициализация класса void Init(void); //--- Инициализация цветов различных состояний
...
Its implementation:
//+------------------------------------------------------------------+ //| CColorControl::Инициализация класса | //+------------------------------------------------------------------+ void CColorElement::Init(void) { this.m_default.SetName("Default"); this.m_default.SetID(1); this.m_focused.SetName("Focused"); this.m_focused.SetID(2); this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3); this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4); this.SetCurrentAs(COLOR_STATE_DEFAULT); this.m_current.SetName("Current"); this.m_current.SetID(0); } //+------------------------------------------------------------------+
If a transparent color is passed to the color initialization method, then it does not need to be changed in any way for any states.
Mind it in method implementation:
//+------------------------------------------------------------------+ //| CColorControl::Устанавливает цвета для всех состояний по текущему| //+------------------------------------------------------------------+ void CColorElement::InitColors(const color clr) { this.InitDefault(clr); this.InitFocused(clr!=clrNULL ? this.NewColor(clr,-20,-20,-20) : clrNULL); this.InitPressed(clr!=clrNULL ? this.NewColor(clr,-40,-40,-40) : clrNULL); this.InitBlocked(clrWhiteSmoke); }
In the CBound class, add a method that returns a flag for cursor presence inside a rectangular area. This is required when implementing the Controller component:
//+------------------------------------------------------------------+ //| Класс прямоугольной области | //+------------------------------------------------------------------+ class CBound : public CBaseObj { protected: CRect m_bound; // Структура прямоугольной области public: //--- Изменяет (1) ширину, (2) высоту, (3) размер ограничивающего прямоугольника void ResizeW(const int size) { this.m_bound.Width(size); } void ResizeH(const int size) { this.m_bound.Height(size); } void Resize(const int w,const int h) { this.m_bound.Width(w); this.m_bound.Height(h); } //--- Устанавливает координату (1) X, (2) Y, (3) обе координаты ограничивающего прямоугольника void SetX(const int x) { this.m_bound.left=x; } void SetY(const int y) { this.m_bound.top=y; } void SetXY(const int x,const int y) { this.m_bound.LeftTop(x,y); } //--- (1) Устанавливает, (2) смещает ограничивающий прямоугольник на указанные координаты/размер смещения void Move(const int x,const int y) { this.m_bound.Move(x,y); } void Shift(const int dx,const int dy) { this.m_bound.Shift(dx,dy); } //--- Возвращает координаты, размеры и границы объекта int X(void) const { return this.m_bound.left; } int Y(void) const { return this.m_bound.top; } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } int Right(void) const { return this.m_bound.right-(this.m_bound.Width() >0 ? 1 : 0);} int Bottom(void) const { return this.m_bound.bottom-(this.m_bound.Height()>0 ? 1 : 0);} //--- Возвращает флаг нахождения курсора внутри области bool Contains(const int x,const int y) const { return this.m_bound.Contains(x,y); } //--- Возвращает описание объекта virtual string Description(void); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_RECTANGLE_AREA); } //--- Конструкторы/деструктор CBound(void) { ::ZeroMemory(this.m_bound); } CBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.Resize(w,h); } ~CBound(void) { ::ZeroMemory(this.m_bound); } };
Now it is necessary to add everything for the implementation of Controller component to the base class of the CCanvasBase graphical element canvas.
When graphic elements interact with the mouse, it is necessary to disable some properties of the chart, such as scrolling the chart with the wheel, the right mouse button menu, etc. Each object of the graphical elements will do this. But when you first start, you must remember the state of chart properties as it was before the program was launched. And after completing the work, return everything to its place.
To do this, in the private section of the CCanvasBase class declare variables for storing values of stored chart properties and declare a method for setting restrictions on chart properties:
//+------------------------------------------------------------------+ //| Базовый класс холста графических элементов | //+------------------------------------------------------------------+ class CCanvasBase : public CBaseObj { private: bool m_chart_mouse_wheel_flag; // Флаг отправки сообщений о прокрутке колёсика мышки bool m_chart_mouse_move_flag; // Флаг отправки сообщений о перемещениях курсора мышки bool m_chart_object_create_flag; // Флаг отправки сообщений о событии создания графического объекта bool m_chart_mouse_scroll_flag; // Флаг прокрутки графика левой кнопкой и колёсиком мышки //--- Установка запретов для графика (прокрутка колёсиком, контекстное меню и перекрестие) void SetFlags(const bool flag); protected:
UI elements can have two states (maybe more, but for now — two). For example, for a button — pressed, released. This means that we must control the color states of the element in its two states. In the protected section of the class, define a variable to store the state of the element, another set of color management objects, and separate transparency control for the background and foreground canvas:
protected: CCanvas m_background; // Канвас для рисования фона CCanvas m_foreground; // Канвас для рисования переднего плана CBound m_bound; // Границы объекта CCanvasBase *m_container; // Родительский объект-контейнер CColorElement m_color_background; // Объект управления цветом фона CColorElement m_color_foreground; // Объект управления цветом переднего плана CColorElement m_color_border; // Объект управления цветом рамки CColorElement m_color_background_act; // Объект управления цветом фона активированного элемента CColorElement m_color_foreground_act; // Объект управления цветом переднего плана активированного элемента CColorElement m_color_border_act; // Объект управления цветом рамки активированного элемента ENUM_ELEMENT_STATE m_state; // Состояние элемента (напр., кнопки (вкл/выкл)) long m_chart_id; // Идентификатор графика int m_wnd; // Номер подокна графика int m_wnd_y; // Смещение координаты Y курсора в подокне int m_obj_x; // Координата X графического объекта int m_obj_y; // Координата Y графического объекта uchar m_alpha_bg; // Прозрачность фона uchar m_alpha_fg; // Прозрачность переднего плана uint m_border_width; // Ширина рамки string m_program_name; // Имя программы bool m_hidden; // Флаг скрытого объекта bool m_blocked; // Флаг заблокированного элемента bool m_focused; // Флаг элемента в фокусе
Here, also declare methods for controlling the mouse cursor, color management, and virtual event handlers:
//--- Ограничивает графический объект по размерам контейнера virtual void ObjectTrim(void); //--- Возвращает флаг нахождения курсора внутри объекта bool Contains(const int x,const int y); //--- Проверяет установленный цвет на равенство указанному bool CheckColor(const ENUM_COLOR_STATE state) const; //--- Изменяет цвета фона, текста и рамки в зависимости от условия void ColorChange(const ENUM_COLOR_STATE state); //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Обработчики событий (1) наведения курсора (Focus), (2) нажатий кнопок мышки (Press), (3) прокрутки колёсика (Wheel), //--- (4) ухода из фокуса (Release), (5) создания графического объекта (Create). Должны определяться в наследниках virtual void OnFocusEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnReleaseEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnCreateEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam) { return; } // обработчик здесь отключен //--- Обработчики пользовательских событий элемента при наведении курсора, щелчке и прокрутке колёсика в области объекта virtual void MouseMoveHandler(const int id, const long lparam, const double dparam, const string sparam) { return; } // обработчик здесь отключен virtual void MousePressHandler(const int id, const long lparam, const double dparam, const string sparam) { return; } // обработчик здесь отключен virtual void MouseWheelHandler(const int id, const long lparam, const double dparam, const string sparam) { return; } // обработчик здесь отключен public:
In the public section of the class, add methods for getting element color management objects in the activated state and methods for getting colors in various states of the element:
public: //--- Возвращает указатель на канвас (1) фона, (2) переднего плана CCanvas *GetBackground(void) { return &this.m_background; } CCanvas *GetForeground(void) { return &this.m_foreground; } //--- Возвращает указатель на объект управления цветом (1) фона, (2) переднего плана, (3) рамки CColorElement *GetBackColorControl(void) { return &this.m_color_background; } CColorElement *GetForeColorControl(void) { return &this.m_color_foreground; } CColorElement *GetBorderColorControl(void) { return &this.m_color_border; } //--- Возвращает указатель на объект управления цветом (1) фона, (2) переднего плана, (3) рамки активированного элемента CColorElement *GetBackColorActControl(void) { return &this.m_color_background_act; } CColorElement *GetForeColorActControl(void) { return &this.m_color_foreground_act; } CColorElement *GetBorderColorActControl(void) { return &this.m_color_border_act; } //--- Возврат текущего цвета (1) фона, (2) переднего плана, (3) рамки color BackColor(void) const { return(!this.State() ? this.m_color_background.GetCurrent() : this.m_color_background_act.GetCurrent()); } color ForeColor(void) const { return(!this.State() ? this.m_color_foreground.GetCurrent() : this.m_color_foreground_act.GetCurrent()); } color BorderColor(void) const { return(!this.State() ? this.m_color_border.GetCurrent() : this.m_color_border_act.GetCurrent()); } //--- Возврат предустановленного цвета DEFAULT (1) фона, (2) переднего плана, (3) рамки color BackColorDefault(void) const { return(!this.State() ? this.m_color_background.GetDefault() : this.m_color_background_act.GetDefault()); } color ForeColorDefault(void) const { return(!this.State() ? this.m_color_foreground.GetDefault() : this.m_color_foreground_act.GetDefault()); } color BorderColorDefault(void)const { return(!this.State() ? this.m_color_border.GetDefault() : this.m_color_border_act.GetDefault()); } //--- Возврат предустановленного цвета FOCUSED (1) фона, (2) переднего плана, (3) рамки color BackColorFocused(void) const { return(!this.State() ? this.m_color_background.GetFocused() : this.m_color_background_act.GetFocused()); } color ForeColorFocused(void) const { return(!this.State() ? this.m_color_foreground.GetFocused() : this.m_color_foreground_act.GetFocused()); } color BorderColorFocused(void)const { return(!this.State() ? this.m_color_border.GetFocused() : this.m_color_border_act.GetFocused()); } //--- Возврат предустановленного цвета PRESSED (1) фона, (2) переднего плана, (3) рамки color BackColorPressed(void) const { return(!this.State() ? this.m_color_background.GetPressed() : this.m_color_background_act.GetPressed()); } color ForeColorPressed(void) const { return(!this.State() ? this.m_color_foreground.GetPressed() : this.m_color_foreground_act.GetPressed()); } color BorderColorPressed(void)const { return(!this.State() ? this.m_color_border.GetPressed() : this.m_color_border_act.GetPressed()); } //--- Возврат предустановленного цвета BLOCKED (1) фона, (2) переднего плана, (3) рамки color BackColorBlocked(void) const { return this.m_color_background.GetBlocked(); } color ForeColorBlocked(void) const { return this.m_color_foreground.GetBlocked(); } color BorderColorBlocked(void) const { return this.m_color_border.GetBlocked(); } //--- Установка цветов фона для всех состояний
Now, in each of the color retrieving methods, the state of the element is checked (activated/deactivated) and the required color is returned according to the element state.
Add methods for setting the colors of the activated element and refine the methods for setting the colors of element states relative to the mouse cursor, given the state of the element as activated/non-activated:
//--- Установка цветов фона для всех состояний void InitBackColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_background_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitBackColorsAct(const color clr) { this.m_color_background_act.InitColors(clr); } //--- Установка цветов переднего плана для всех состояний void InitForeColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_foreground_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitForeColorsAct(const color clr) { this.m_color_foreground_act.InitColors(clr); } //--- Установка цветов рамки для всех состояний void InitBorderColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_border_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitBorderColorsAct(const color clr) { this.m_color_border_act.InitColors(clr); } //--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки начальными значениями void InitBackColorActDefault(const color clr) { this.m_color_background_act.InitDefault(clr); } void InitForeColorActDefault(const color clr) { this.m_color_foreground_act.InitDefault(clr); } void InitBorderColorActDefault(const color clr){ this.m_color_border_act.InitDefault(clr); } //--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при наведении курсора начальными значениями void InitBackColorActFocused(const color clr) { this.m_color_background_act.InitFocused(clr); } void InitForeColorActFocused(const color clr) { this.m_color_foreground_act.InitFocused(clr); } void InitBorderColorActFocused(const color clr){ this.m_color_border_act.InitFocused(clr); } //--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при щелчке по объекту начальными значениями void InitBackColorActPressed(const color clr) { this.m_color_background_act.InitPressed(clr); } void InitForeColorActPressed(const color clr) { this.m_color_foreground_act.InitPressed(clr); } void InitBorderColorActPressed(const color clr){ this.m_color_border_act.InitPressed(clr); } //--- Установка текущего цвета фона в различные состояния bool BackColorToDefault(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool BackColorToFocused(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool BackColorToPressed(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool BackColorToBlocked(void) { return this.m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED); } //--- Установка текущего цвета переднего плана в различные состояния bool ForeColorToDefault(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool ForeColorToFocused(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool ForeColorToPressed(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool ForeColorToBlocked(void) { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_BLOCKED); } //--- Установка текущего цвета рамки в различные состояния bool BorderColorToDefault(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool BorderColorToFocused(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool BorderColorToPressed(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool BorderColorToBlocked(void) { return this.m_color_border.SetCurrentAs(COLOR_STATE_BLOCKED); }
Add methods for setting and returning the state of an element:
//--- Создаёт OBJ_BITMAP_LABEL bool Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h); //--- (1) Устанавливает, (2) возвращает состояние void SetState(ENUM_ELEMENT_STATE state) { this.m_state=state; this.ColorsToDefault(); } ENUM_ELEMENT_STATE State(void) const { return this.m_state; } //--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного элемента (4) в фокусе, (5) имя графического объекта (фон, текст)
When setting the state of an element, after setting the status flag, all the colors of the element must be recorded as current. If the element is activated, for example, the button is pressed, then all the current colors are set as colors for the pressed button. Otherwise, current colors are taken from the colors list for the released button.
So far as we have now separated setting and returning transparency for the background and foreground, add new methods for transparency setting and returning:
string NameBG(void) const { return this.m_background.ChartObjectName(); } string NameFG(void) const { return this.m_foreground.ChartObjectName(); } //--- (1) Возвращает, (2) устанавливает прозрачность фона uchar AlphaBG(void) const { return this.m_alpha_bg; } void SetAlphaBG(const uchar value) { this.m_alpha_bg=value; } //--- (1) Возвращает, (2) устанавливает прозрачность переднего плана uchar AlphaFG(void) const { return this.m_alpha_fg; } void SetAlphaFG(const uchar value) { this.m_alpha_fg=value; } //--- Устанавливает прозрачность для фона и переднего плана void SetAlpha(const uchar value) { this.m_alpha_fg=this.m_alpha_bg=value; } //--- (1) Возвращает, (2) устанавливает ширину рамки
Declare an event handler which is supposed to be called from the event handler of the control program:
//--- Обработчик событий | void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Конструкторы/деструктор CCanvasBase(void) : m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_chart_id(::ChartID()), m_wnd(0), m_alpha_bg(0), m_alpha_fg(255), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0), m_wnd_y(0), m_state(0) { this.Init(); } CCanvasBase(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h); ~CCanvasBase(void); };
In the constructor, correctly specify properties of the font being drawn on the canvas and call the Init() method to store properties of the chart and mouse:
//+------------------------------------------------------------------+ //| CCanvasBase::Конструктор | //+------------------------------------------------------------------+ CCanvasBase::CCanvasBase(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_wnd(wnd<0 ? 0 : wnd), m_alpha_bg(0), m_alpha_fg(255), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0), m_state(0) { //--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y //--- между верхней рамкой подокна индикатора и верхней рамкой главного окна графика this.m_chart_id=this.CorrectChartID(chart_id); this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd); //--- Если графический ресурс и графический объект созданы if(this.Create(this.m_chart_id,this.m_wnd,object_name,x,y,w,h)) { //--- Очищаем канвасы фона и переднего плана и устанавливаем начальные значения координат, //--- наименования графических объектов и свойства текста, рисуемого на переднем плане this.Clear(false); this.m_obj_x=x; this.m_obj_y=y; this.m_color_background.SetName("Background"); this.m_color_foreground.SetName("Foreground"); this.m_color_border.SetName("Border"); this.m_foreground.FontSet(DEF_FONTNAME,-DEF_FONTSIZE*10,FW_MEDIUM); this.m_bound.SetName("Perimeter"); //--- Запоминаем разрешения для мышки и инструментов графика this.Init(); } }
In the class destructor, destroy the created graphic object and restore the stored properties of the chart and mouse permissions:
//+------------------------------------------------------------------+ //| CCanvasBase::Деструктор | //+------------------------------------------------------------------+ CCanvasBase::~CCanvasBase(void) { //--- Уничтожаем объект this.Destroy(); //--- Возвращаем разрешения для мышки и инструментов графика ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, this.m_chart_mouse_wheel_flag); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, this.m_chart_mouse_move_flag); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, this.m_chart_object_create_flag); ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL, this.m_chart_mouse_scroll_flag); ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU, this.m_chart_context_menu_flag); ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL, this.m_chart_crosshair_tool_flag); }
In the method of creating a graphical element, the name of the graphic object to be created should not have spaces. This can be corrected by replacing spaces in the name with underscores:
//+------------------------------------------------------------------+ //| CCanvasBase::Создаёт графические объекты фона и переднего плана | //+------------------------------------------------------------------+ bool CCanvasBase::Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h) { //--- Получаем скорректированный идентификатор графика long id=this.CorrectChartID(chart_id); //--- Корректируем переданное имя для объекта string nm=object_name; ::StringReplace(nm," ","_"); //--- Создаём имя графического объекта для фона и создаём канвас string obj_name=nm+"_BG"; if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- Создаём имя графического объекта для переднего плана и создаём канвас obj_name=nm+"_FG"; if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- При успешном создании в свойство графического объекта OBJPROP_TEXT вписываем наименование программы ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name); //--- Устанавливаем размеры прямоугольной области и возвращаем true this.m_bound.SetXY(x,y); this.m_bound.Resize(w,h); return true; }
A method that returns the cursor location flag inside an object:
//+------------------------------------------------------------------+ //| CCanvasBase::Возвращает флаг нахождения курсора внутри объекта | //+------------------------------------------------------------------+ bool CCanvasBase::Contains(const int x,const int y) { //--- check and return the result int left=::fmax(this.X(),this.ObjectX()); int right=::fmin(this.Right(),this.ObjectRight()); int top=::fmax(this.Y(),this.ObjectY()); int bottom=::fmin(this.Bottom(),this.ObjectBottom()); return(x>=left && x<=right && y>=top && y<=bottom); }
Since the object size and the canvas size may vary (the ObjectTrim method resizes the canvas without resizing the object), here one of the values is taken as the bounds within which the cursor is located: either object bound or a corresponding edge of the canvas. The method returns a flag of location of the coordinates passed to the method within the received bounds.
In the element lock method, the setting of the lock flag must be before calling the element drawing method, fix:
//+------------------------------------------------------------------+ //| CCanvasBase::Блокирует элемент | //+------------------------------------------------------------------+ void CCanvasBase::Block(const bool chart_redraw) { //--- Если элемент уже заблокирован - уходим if(this.m_blocked) return; //--- Устанавливаем текущие цвета как цвета заблокированного элемента, //--- устанавливаем флаг блокировки и перерисовываем объект this.ColorsToBlocked(); this.m_blocked=true; this.Draw(chart_redraw); }
This correction allows you to draw the blocked element correctly. Prior to this correction, when drawing an element, colors were taken from the default state, not from the blocked one, since the flag was set after the lock.
A Method for Setting Bans for the Chart:
//+------------------------------------------------------------------+ //| CCanvasBase::Установка запретов для графика | //| (прокрутка колёсиком, контекстное меню и перекрестие) | //+------------------------------------------------------------------+ void CCanvasBase::SetFlags(const bool flag) { //--- Если нужно установить флаги, и они уже были установлены ранее - уходим if(flag && this.m_flags_state) return; //--- Если нужно сбросить флаги, и они уже были сброшены ранее - уходим if(!flag && !this.m_flags_state) return; //--- Устанавливаем требуемый флаг для контекстного меню, //--- инструмента "перекрестие" и прокрутки графика колёсиком мышки. //--- После установки запоминаем значение установленного флага ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU, flag); ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL,flag); ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL, flag); this.m_flags_state=flag; //--- Делаем обновление графика для немедленного применения установленных флагов ::ChartRedraw(this.m_chart_id); }
Class Initialization Method:
//+------------------------------------------------------------------+ //| 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(); }
Default Object Color Initialization Method:
//+------------------------------------------------------------------+ //| CCanvasBase::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CCanvasBase::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrDarkGray); this.InitBorderColorsAct(clrDarkGray); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrLightGray); this.InitForeColorBlocked(clrSilver); }
A Method That Checks the Set Color for Equality to the Specified One:
//+------------------------------------------------------------------+ //| CCanvasBase::Проверяет установленный цвет на равенство указанному| //+------------------------------------------------------------------+ bool CCanvasBase::CheckColor(const ENUM_COLOR_STATE state) const { bool res=true; //--- В зависимости от проверяемого события switch(state) { //--- проверяем равенство всех STANDARD цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_DEFAULT : res &=this.BackColor()==this.BackColorDefault(); res &=this.ForeColor()==this.ForeColorDefault(); res &=this.BorderColor()==this.BorderColorDefault(); break; //--- проверяем равенство всех FOCUSED цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_FOCUSED : res &=this.BackColor()==this.BackColorFocused(); res &=this.ForeColor()==this.ForeColorFocused(); res &=this.BorderColor()==this.BorderColorFocused(); break; //--- проверяем равенство всех PRESSED цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_PRESSED : res &=this.BackColor()==this.BackColorPressed(); res &=this.ForeColor()==this.ForeColorPressed(); res &=this.BorderColor()==this.BorderColorPressed(); break; //--- проверяем равенство всех BLOCKED цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_BLOCKED : res &=this.BackColor()==this.BackColorBlocked(); res &=this.ForeColor()==this.ForeColorBlocked(); res &=this.BorderColor()==this.BorderColorBlocked(); break; default: res=false; break; } return res; }
In order to change element colors only when the state of the element is toggled, this method returns the flag of already set colors corresponding to element state. If the current colors of the element are not equal to those set for the state checked, the method allows changing the color and redrawing the graphical element. If colors are already set according to the state of the element, there is no need to change the colors and redraw the object; the method bans color change.
A Method That Changes Colors of Object's Elements Based on an Event:
//+------------------------------------------------------------------+ //| CCanvasBase::Смена цвета элементов объекта по событию | //+------------------------------------------------------------------+ void CCanvasBase::ColorChange(const ENUM_COLOR_STATE state) { //--- В зависимости от события устанавливаем цвета события как основные switch(state) { case COLOR_STATE_DEFAULT : this.ColorsToDefault(); break; case COLOR_STATE_FOCUSED : this.ColorsToFocused(); break; case COLOR_STATE_PRESSED : this.ColorsToPressed(); break; case COLOR_STATE_BLOCKED : this.ColorsToBlocked(); break; default : break; } }
Depending on the event for which the color must be changed, current colors are set according to the event (element state).
Event handler:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик событий | //+------------------------------------------------------------------+ void CCanvasBase::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Если изменение графика if(id==CHARTEVENT_CHART_CHANGE) { //--- скорректируем дистанцию между верхней рамкой подокна индикатора и верхней рамкой главного окна графика this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd); } //--- Если элемент заблокирован или скрыт - уходим if(this.IsBlocked() || this.IsHidden()) return; //--- Координаты курсора мышки int x=(int)lparam; int y=(int)dparam-this.m_wnd_y; // Корректируем Y по высоте окна индикатора //--- Событие перемещения курсора, либо щелчка кнопкой мышки if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_OBJECT_CLICK) { //--- Если курсор в пределах объекта if(this.Contains(x, y)) { //--- Если объект не в составе контейнера - запрещаем прокрутку графика, контекстное меню и инструмент "Перекрестие" if(this.m_container==NULL) this.SetFlags(false); //--- Получаем состояние кнопок мышки, если нажаты - вызываем обработчик нажатий if(sparam=="1" || sparam=="2" || sparam=="16") this.OnPressEvent(id, lparam, dparam, sparam); //--- кнопки не нажаты - обрабатываем перемещение курсора else this.OnFocusEvent(id, lparam, dparam, sparam); } //--- Курсор за пределами объекта else { //--- Обрабатываем увод курсора за границы объекта this.OnReleaseEvent(id,lparam,dparam,sparam); //--- Если объект не в составе контейнера - разрешаем прокрутку графика, контекстное меню и инструмент "Перекрестие" if(this.m_container==NULL) this.SetFlags(true); } } //--- Событие прокрутки колёсика мышки if(id==CHARTEVENT_MOUSE_WHEEL) { this.OnWheelEvent(id,lparam,dparam,sparam); } //--- Событие создания графического объекта if(id==CHARTEVENT_OBJECT_CREATE) { this.OnCreateEvent(id,lparam,dparam,sparam); } //--- Если пришло пользовательское событие графика if(id>CHARTEVENT_CUSTOM) { //--- собственные события не обрабатываем if(sparam==this.NameBG()) return; //--- приводим пользовательское событие в соответствие со стандартными ENUM_CHART_EVENT chart_event=ENUM_CHART_EVENT(id-CHARTEVENT_CUSTOM); //--- Если щелчок мышки по объекту if(chart_event==CHARTEVENT_OBJECT_CLICK) { this.MousePressHandler(chart_event, lparam, dparam, sparam); } //--- Если перемещение курсора мышки if(chart_event==CHARTEVENT_MOUSE_MOVE) { this.MouseMoveHandler(chart_event, lparam, dparam, sparam); } //--- Если прокрутка колёсика мышки if(chart_event==CHARTEVENT_MOUSE_WHEEL) { this.MouseWheelHandler(chart_event, lparam, dparam, sparam); } } }
The logic of handling the interaction of the mouse cursor with graphical elements is arranged in the base object of graphical elements. Virtual handlers are called for various events being monitored. Some handlers are implemented directly in this class, and some simply do nothing, and they must be implemented in descendant objects of this class.
The event handlers, which name ends in *Handler, are designated to handle interactions within controls between their constituent components. Whereas the handlers with *Event in their name directly handle chart events and send custom events to the chart, which can be used to determine the type of event and which control it was sent from. This will allow the user to handle such events in their program.
Out-of-focus Handler:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик ухода из фокуса | //+------------------------------------------------------------------+ void CCanvasBase::OnReleaseEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Элемент не в фокусе при уводе курсора this.m_focused=false; //--- восстанавливаем исходные цвета, сбрасываем флаг Focused и перерисовываем объект if(!this.CheckColor(COLOR_STATE_DEFAULT)) { this.ColorChange(COLOR_STATE_DEFAULT); this.Draw(true); } }
Cursor Hover Handler:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик наведения курсора | //+------------------------------------------------------------------+ void CCanvasBase::OnFocusEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Элемент в фокусе this.m_focused=true; //--- Если цвета объекта не для режима Focused if(!this.CheckColor(COLOR_STATE_FOCUSED)) { //--- устанавливаем цвета и флаг Focused и перерисовываем объект this.ColorChange(COLOR_STATE_FOCUSED); this.Draw(true); } }
Object Press Handler:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик нажатия на объект | //+------------------------------------------------------------------+ void CCanvasBase::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Элемент в фокусе при щелчке по нему this.m_focused=true; //--- Если цвета объекта не для режима Pressed if(!this.CheckColor(COLOR_STATE_PRESSED)) { //--- устанавливаем цвета Pressed и перерисовываем объект this.ColorChange(COLOR_STATE_PRESSED); this.Draw(true); } //--- отправляем пользовательское событие на график с передаенными значениями в lparam, dparam, и именем объекта в sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_OBJECT_CLICK, lparam, dparam, this.NameBG()); }
Handler for the Graphic Object Creation Event:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик события создания графического объекта | //+------------------------------------------------------------------+ void CCanvasBase::OnCreateEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- если это объект, принадлежащий этой программе - уходим if(this.IsBelongsToThis(sparam)) return; //--- переносим объект на передний план this.BringToTop(true); }
The logic of all handlers is commented on in detail in the code. In fact, only a reaction to an event in the form of color changing of the graphical element is arranged here and, where necessary, sending custom events to the chart. The last handler reacts to the creation of a graphic object on the chart and transfers graphical elements to the foreground. This will allow, for example, the panel to always remain in the foreground.
All these handlers are virtual, and should be redefined in inherited classes if necessary.
We are done with the refinement of the base object of all graphical elements. Now, based on the created Controller component in the base object and the earlier created View component, start creating the simplest graphic elements (which are also part of the View component). And they will become the "building blocks" from which complex controls will eventually be created, and in particular, the Table View control, upon which implementation we have been working on for several articles.
Simple controls
In the same folder \MQL5\Indicators\Tables\Controls\ create a new include file Controls.mqh.
To the created file, connect the file of the base object of graphical elements Base.mqh and add some macro substitutions and enumerations:
//+------------------------------------------------------------------+ //| Controls.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Base.mqh" //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define DEF_LABEL_W 40 // Ширина текстовой метки по умолчанию #define DEF_LABEL_H 16 // Высота текстовой метки по умолчанию #define DEF_BUTTON_W 50 // Ширина кнопки по умолчанию #define DEF_BUTTON_H 16 // Высота кнопки по умолчанию //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_COMPARE_BY // Сравниваемые свойства { ELEMENT_SORT_BY_ID = 0, // Сравнение по идентификатору элемента ELEMENT_SORT_BY_NAME, // Сравнение по наименованию элемента ELEMENT_SORT_BY_TEXT, // Сравнение по тексту элемента ELEMENT_SORT_BY_COLOR, // Сравнение по цвету элемента ELEMENT_SORT_BY_ALPHA, // Сравнение по прозрачности элемента ELEMENT_SORT_BY_STATE, // Сравнение по состоянию элемента }; //+------------------------------------------------------------------+ //| Функции | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Классы | //+------------------------------------------------------------------+
In macro substitution, we have defined default sizes for text labels and buttons. In the enumeration, we have indicated the available properties of the base graphical element. You can use these properties to search for objects, sort them, and compare them. When adding new properties to any objects, add new constants to this enumeration.
Auxiliary Classes
Each graphic element can have an image in its composition. This will enable to draw icons for buttons, lines of text, etc.
Create a special class for drawing images, which will be an integral part of simple controls.
A Class for Drawing Images Within a Defined Area
//+------------------------------------------------------------------+ //| Классы | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Класс рисования изображений | //+------------------------------------------------------------------+ class CImagePainter : public CBaseObj { protected: CCanvas *m_canvas; // Указатель на канвас, где рисуем CBound m_bound; // Координаты и границы изображения uchar m_alpha; // Прозрачность //--- Проверяет валидность холста и корректность размеров bool CheckBound(void); public: //--- (1) Назначает канвас для рисования, (2) устанавливает, (3) возвращает прозрачность void CanvasAssign(CCanvas *canvas) { this.m_canvas=canvas; } void SetAlpha(const uchar value) { this.m_alpha=value; } uchar Alpha(void) const { return this.m_alpha; } //--- (1) Устанавливает координаты, (2) изменяет размеры области void SetXY(const int x,const int y) { this.m_bound.SetXY(x,y); } void SetSize(const int w,const int h) { this.m_bound.Resize(w,h); } //--- Устанавливает координаты и размеры области void SetBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.SetSize(w,h); } //--- Возвращает границы и размеры рисунка int X(void) const { return this.m_bound.X(); } int Y(void) const { return this.m_bound.Y(); } int Right(void) const { return this.m_bound.Right(); } int Bottom(void) const { return this.m_bound.Bottom(); } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } //--- Очищает область bool Clear(const int x,const int y,const int w,const int h,const bool update=true); //--- Рисует закрашенную стрелку (1) вверх, (2) вниз, (3) влево, (4) вправо bool ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует (1) отмеченный, (2) неотмеченный CheckBox bool CheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool UncheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует (1) отмеченный, (2) неотмеченный RadioButton bool CheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool UncheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_IMAGE_PAINTER); } //--- Конструкторы/деструктор CImagePainter(void) : m_canvas(NULL) { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter"); } CImagePainter(CCanvas *canvas) : m_canvas(canvas) { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter"); } CImagePainter(CCanvas *canvas,const int id,const string name) : m_canvas(canvas) { this.m_id=id; this.SetName(name); this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); } CImagePainter(CCanvas *canvas,const int id,const int dx,const int dy,const int w,const int h,const string name) : m_canvas(canvas) { this.m_id=id; this.SetName(name); this.SetBound(dx,dy,w,h); } ~CImagePainter(void) {} };
Let us consider class methods.
A Method for Comparing Two Drawing Objects:
//+------------------------------------------------------------------+ //| CImagePainter::Сравнение двух объектов | //+------------------------------------------------------------------+ int CImagePainter::Compare(const CObject *node,const int mode=0) const { const CImagePainter *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.Alpha()>obj.Alpha() ? 1 : this.Alpha()<obj.Alpha()? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
This method is necessary to find the required drawing object. By default, the search is performed by the object ID. The method will be required when objects of controls contain lists where drawing objects are stored. At the moment, one drawing object will be declared in each control. Such drawing object is intended for drawing the main icon of the element.
A Method That Verifies the Canvas Validity and the Correct Size of the Image Area:
//+------------------------------------------------------------------+ //|CImagePainter::Проверяет валидность холста и корректность размеров| //+------------------------------------------------------------------+ bool CImagePainter::CheckBound(void) { if(this.m_canvas==NULL) { ::PrintFormat("%s: Error. First you need to assign the canvas using the CanvasAssign() method",__FUNCTION__); return false; } if(this.Width()==0 || this.Height()==0) { ::PrintFormat("%s: Error. First you need to set the area size using the SetSize() or SetBound() methods",__FUNCTION__); return false; } return true; }
If a pointer to the canvas is not passed to the object, or the width and height of the image area are not set, the method returns false. Otherwise — true.
A Method That Clears the Image Area:
//+------------------------------------------------------------------+ //| CImagePainter::Очищает область | //+------------------------------------------------------------------+ bool CImagePainter::Clear(const int x,const int y,const int w,const int h,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Очищаем прозрачным цветом всю область изображения this.m_canvas.FillRectangle(x,y,x+w-1,y+h-1,clrNULL); //--- Если указано - обновляем канвас if(update) this.m_canvas.Update(false); //--- Всё успешно return true; }
The method completely clears the entire area of the image, filling it with a transparent color.
A Method That Draws a Shaded Up Arrow:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку вверх | //+------------------------------------------------------------------+ bool CImagePainter::ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Рассчитываем координаты углов стрелки внутри области изображения int hw=(int)::floor(w/2); // Половина ширины if(hw==0) hw=1; int x1 = x + 1; // X. Основание (левая точка) int y1 = y + h - 4; // Y. Левая точка основания int x2 = x1 + hw; // X. Вершина (центральная верхняя точка) int y2 = y + 3; // Y. Вершина (верхняя точка) int x3 = x1 + w - 1; // X. Основание (правая точка) int y3 = y1; // Y. Основание (правая точка) //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
A Method That Draws a Shaded Down Arrow:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку вниз | //+------------------------------------------------------------------+ bool CImagePainter::ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Рассчитываем координаты углов стрелки внутри области изображения int hw=(int)::floor(w/2); // Половина ширины if(hw==0) hw=1; int x1=x+1; // X. Основание (левая точка) int y1=y+4; // Y. Левая точка основания int x2=x1+hw; // X. Вершина (центральная нижняя точка) int y2=y+h-3; // Y. Вершина (нижняя точка) int x3=x1+w-1; // X. Основание (правая точка) int y3=y1; // Y. Основание (правая точка) //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
A Method That Draws a Shaded Left Arrow:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку влево | //+------------------------------------------------------------------+ bool CImagePainter::ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Рассчитываем координаты углов стрелки внутри области изображения int hh=(int)::floor(h/2); // Половина высоты if(hh==0) hh=1; int x1=x+w-4; // X. Основание (правая сторона) int y1=y+1; // Y. Верхний угол основания int x2=x+3; // X. Вершина (левая центральная точка) int y2=y1+hh; // Y. Центральная точка (вершина) int x3=x1; // X. Нижний угол основания int y3=y1+h-1; // Y. Нижний угол основания //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
A Method That Draws a Shaded Right Arrow:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку вправо | //+------------------------------------------------------------------+ bool CImagePainter::ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Рассчитываем координаты углов стрелки внутри области изображения int hh=(int)::floor(h/2); // Половина высоты if(hh==0) hh=1; int x1=x+4; // X. Основание треугольника (левая сторона) int y1=y+1; // Y. Верхний угол основания int x2=x+w-3; // X. Вершина (правая центральная точка) int y2=y1+hh; // Y. Центральная точка (вершина) int x3=x1; // X. Нижний угол основания int y3=y1+h-1; // Y. Нижний угол основания //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Inside the image area, an area for an arrow is defined with an indentation of one pixel on each side of the rectangular area, and a shaded arrow is drawn inside.
A Method That Draws a Checked CheckBox:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует отмеченный CheckBox | //+------------------------------------------------------------------+ bool CImagePainter::CheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты прямоугольника int x1=x+1; // Левый верхний угол, X int y1=y+1; // Левый верхний угол, Y int x2=x+w-2; // Правый нижний угол, X int y2=y+h-2; // Правый нижний угол, Y //--- Рисуем прямоугольник this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha)); //--- Координаты "галочки" int arrx[3], arry[3]; arrx[0]=x1+(x2-x1)/4; // X. Левая точка arrx[1]=x1+w/3; // X. Центральная точка arrx[2]=x2-(x2-x1)/4; // X. Правая точка arry[0]=y1+1+(y2-y1)/2; // Y. Левая точка arry[1]=y2-(y2-y1)/3; // Y. Центральная точка arry[2]=y1+(y2-y1)/3; // Y. Правая точка //--- Рисуем "галочку" линией двойной толщины this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha)); arrx[0]++; arrx[1]++; arrx[2]++; this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
A Method That Draws an Unchecked CheckBox:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует неотмеченный CheckBox | //+------------------------------------------------------------------+ bool CImagePainter::UncheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты прямоугольника int x1=x+1; // Левый верхний угол, X int y1=y+1; // Левый верхний угол, Y int x2=x+w-2; // Правый нижний угол, X int y2=y+h-2; // Правый нижний угол, Y //--- Рисуем прямоугольник this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
A Method That Draws a Checked RadioButton:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует отмеченный RadioButton | //+------------------------------------------------------------------+ bool CImagePainter::CheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты и радиус окружности int x1=x+1; // Левый верхний угол области окружности, X int y1=y+1; // Левый верхний угол области окружности, Y int x2=x+w-2; // Правый нижний угол области окружности, X int y2=y+h-2; // Правый нижний угол области окружности, Y //--- Координаты и радиус окружности int d=::fmin(x2-x1,y2-y1); // Диаметр по меньшей стороне (ширина или высота) int r=d/2; // Радиус int cx=x1+r; // Координата X центра int cy=y1+r; // Координата Y центра //--- Рисуем окружность this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha)); //--- Радиус "метки" r/=2; if(r<1) r=1; //--- Рисуем метку this.m_canvas.FillCircle(cx, cy, r, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
A Method That Draws an Unchecked RadioButton:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует неотмеченный RadioButton | //+------------------------------------------------------------------+ bool CImagePainter::UncheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты и радиус окружности int x1=x+1; // Левый верхний угол области окружности, X int y1=y+1; // Левый верхний угол области окружности, Y int x2=x+w-2; // Правый нижний угол области окружности, X int y2=y+h-2; // Правый нижний угол области окружности, Y //--- Координаты и радиус окружности int d=::fmin(x2-x1,y2-y1); // Диаметр по меньшей стороне (ширина или высота) int r=d/2; // Радиус int cx=x1+r; // Координата X центра int cy=y1+r; // Координата Y центра //--- Рисуем окружность this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
These are simple methods that simply make it possible to draw the desired shapes without having to implement them yourself. Next, add other methods here that draw other icons for the design of graphical elements.
Methods For Saving a Drawing Area to a File and Uploading It From a File:
//+------------------------------------------------------------------+ //| CImagePainter::Сохранение в файл | //+------------------------------------------------------------------+ bool CImagePainter::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CBaseObj::Save(file_handle)) return false; //--- Сохраняем прозрачность if(::FileWriteInteger(file_handle,this.m_alpha,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем данные области if(!this.m_bound.Save(file_handle)) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CImagePainter::Загрузка из файла | //+------------------------------------------------------------------+ bool CImagePainter::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CBaseObj::Load(file_handle)) return false; //--- Загружаем прозрачность this.m_alpha=(uchar)::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем данные области if(!this.m_bound.Load(file_handle)) return false; //--- Всё успешно return true; }
Now, we can start with the classes of simple controls. The minimum such object will be the text label class. Classes of other controls will be inherited from this element.
In the same Controls.mqh file, continue to write class codes.
The "Text Label" Control Class
This class will have a set of variables and methods that allow working with any control, setting and receiving object parameters, saving and loading its properties. The interactivity of all controls (the Controller component) has been added to the base class of all controls today. Now, discuss the text label class:
//+------------------------------------------------------------------+ //| Класс текстовой метки | //+------------------------------------------------------------------+ class CLabel : public CCanvasBase { protected: CImagePainter m_painter; // Класс рисования ushort m_text[]; // Текст ushort m_text_prev[]; // Прошлый текст int m_text_x; // Координата X текста (смещение относительно левой границы объекта) int m_text_y; // Координата Y текста (смещение относительно верхней границы объекта) //--- (1) Устанавливает, (2) возвращает прошлый текст void SetTextPrev(const string text) { ::StringToShortArray(text,this.m_text_prev); } string TextPrev(void) const { return ::ShortArrayToString(this.m_text_prev);} //--- Стирает текст void ClearText(void); public: //--- Возвращает указатель на класс рисования CImagePainter *Painter(void) { return &this.m_painter; } //--- (1) Устанавливает, (2) возвращает текст void SetText(const string text) { ::StringToShortArray(text,this.m_text); } string Text(void) const { return ::ShortArrayToString(this.m_text); } //--- Возвращает координату (1) X, (2) Y текста int TextX(void) const { return this.m_text_x; } int TextY(void) const { return this.m_text_y; } //--- Устанавливает координату (1) X, (2) Y текста void SetTextShiftH(const int x) { this.m_text_x=x; } void SetTextShiftV(const int y) { this.m_text_y=y; } //--- (1) Устанавливает координаты, (2) изменяет размеры области изображения void SetImageXY(const int x,const int y) { this.m_painter.SetXY(x,y); } void SetImageSize(const int w,const int h) { this.m_painter.SetSize(w,h); } //--- Устанавливает координаты и размеры области изображения void SetImageBound(const int x,const int y,const int w,const int h) { this.SetImageXY(x,y); this.SetImageSize(w,h); } //--- Возвращает координату (1) X, (2) Y, (3) ширину, (4) высоту, (5) правую, (6) нижнюю границу области изображения int ImageX(void) const { return this.m_painter.X(); } int ImageY(void) const { return this.m_painter.Y(); } int ImageWidth(void) const { return this.m_painter.Width(); } int ImageHeight(void) const { return this.m_painter.Height(); } int ImageRight(void) const { return this.m_painter.Right(); } int ImageBottom(void) const { return this.m_painter.Bottom(); } //--- Выводит текст void DrawText(const int dx, const int dy, const string text, const bool chart_redraw); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_LABEL); } //--- Конструкторы/деструктор CLabel(void); CLabel(const string object_name, const string text, const int x, const int y, const int w, const int h); CLabel(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CLabel(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CLabel(void) {} };
The class defines two ushort arrays of characters for the current and past label texts. This allows you to have access to dimensions of the previous text when drawing, and to correctly erase the area covered by the text before displaying a new text on the canvas.
Consider the declared methods.
The class has four constructors that allow to create an object using different sets of parameters:
//+------------------------------------------------------------------+ //| CLabel::Конструктор по умолчанию. Строит метку в главном окне | //| текущего графика в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CLabel::CLabel(void) : CCanvasBase("Label",::ChartID(),0,0,0,DEF_LABEL_W,DEF_LABEL_H), m_text_x(0), m_text_y(0) { //--- Объекту рисования назначаем канвас переднего плана и //--- обнуляем координаты и размеры, что делает его неактивным this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Устанавливаем текущий и предыдущий текст this.SetText("Label"); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); } //+------------------------------------------------------------------+ //| CLabel::Конструктор параметрический. Строит метку в главном окне | //| текущего графика с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CLabel::CLabel(const string object_name, const string text,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,::ChartID(),0,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Объекту рисования назначаем канвас переднего плана и //--- обнуляем координаты и размеры, что делает его неактивным this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Устанавливаем текущий и предыдущий текст this.SetText(text); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); } //+-------------------------------------------------------------------+ //| CLabel::Конструктор параметрический. Строит метку в указанном окне| //| текущего графика с указанными текстом, координами и размерами | //+-------------------------------------------------------------------+ CLabel::CLabel(const string object_name, const string text,const int wnd,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,::ChartID(),wnd,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Объекту рисования назначаем канвас переднего плана и //--- обнуляем координаты и размеры, что делает его неактивным this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Устанавливаем текущий и предыдущий текст this.SetText(text); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); } //+-------------------------------------------------------------------+ //| CLabel::Конструктор параметрический. Строит метку в указанном окне| //| указанного графика с указанными текстом, координами и размерами | //+-------------------------------------------------------------------+ CLabel::CLabel(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,chart_id,wnd,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Объекту рисования назначаем канвас переднего плана и //--- обнуляем координаты и размеры, что делает его неактивным this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Устанавливаем текущий и предыдущий текст this.SetText(text); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); }
The drawing (element icon) area is set to zero dimensions, which means that the element does not have an icon. The text of the element is set and full transparency is assigned for the background, while full opacity is assigned for the foreground.
A Method for Comparing Two Objects:
//+------------------------------------------------------------------+ //| CLabel::Сравнение двух объектов | //+------------------------------------------------------------------+ int CLabel::Compare(const CObject *node,const int mode=0) const { const CLabel *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_TEXT : return(this.Text() >obj.Text() ? 1 : this.Text() <obj.Text() ? -1 : 0); case ELEMENT_SORT_BY_COLOR : return(this.ForeColor()>obj.ForeColor() ? 1 : this.ForeColor()<obj.ForeColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaFG() >obj.AlphaFG() ? 1 : this.AlphaFG() <obj.AlphaFG() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Comparison is possible by object name, label text, color, transparency, and identifier. By default, objects are compared by the object ID, since when objects are in the same list, it is better to distinguish them by IDs for quick access to the required one.
A Method That Erases The Label Text:
//+------------------------------------------------------------------+ //| CLabel::Стирает текст | //+------------------------------------------------------------------+ void CLabel::ClearText(void) { int w=0, h=0; string text=this.TextPrev(); //--- Получаем размеры прошлого текста if(text!="") this.m_foreground.TextSize(text,w,h); //--- Если размеры получены - рисуем на месте текста прозрачный прямоугольник, стирая текст if(w>0 && h>0) this.m_foreground.FillRectangle(this.AdjX(this.m_text_x),this.AdjY(this.m_text_y),this.AdjX(this.m_text_x+w),this.AdjY(this.m_text_y+h),clrNULL); //--- Иначе - очищаем полностью весь передний план else this.m_foreground.Erase(clrNULL); }
If the text was written earlier, then you can erase it by painting it over with a completely transparent rectangle according to the size of the text. If there was no text before, the entire canvas area of the object is erased.
A method for displaying text on canvas:
//+------------------------------------------------------------------+ //| CLabel::Выводит текст | //+------------------------------------------------------------------+ void CLabel::DrawText(const int dx,const int dy,const string text,const bool chart_redraw) { //--- Очищаем прошлый текст и устанавливаем новый this.ClearText(); this.SetText(text); //--- Выводим установленный текст this.m_foreground.TextOut(this.AdjX(dx),this.AdjY(dy),this.Text(),::ColorToARGB(this.ForeColor(),this.AlphaFG())); //--- Если текст выходит за правую границу объекта if(this.Width()-dx<this.m_foreground.TextWidth(text)) { //--- Получаем размеры текста "троеточие" int w=0,h=0; this.m_foreground.TextSize("... ",w,h); if(w>0 && h>0) { //--- Стираем текст у правой границы объекта по размеру текста "троеточие" и заменяем троеточием окончание текста метки this.m_foreground.FillRectangle(this.AdjX(this.Width()-w),this.AdjY(this.m_text_y),this.AdjX(this.Width()),this.AdjY(this.m_text_y+h),clrNULL); this.m_foreground.TextOut(this.AdjX(this.Width()-w),this.AdjY(dy),"...",::ColorToARGB(this.ForeColor(),this.AlphaFG())); } } //--- Обновляем канвас переднего плана и запоминаем новые координаты текста this.m_foreground.Update(chart_redraw); this.m_text_x=dx; this.m_text_y=dy; //--- Запоминаем нарисованный текст как прошлый this.SetTextPrev(text); }
Here, the previous text on the canvas is first erased, and then a new one is displayed. If the new text overruns the bounds of the object, then a colon is displayed on the right where it overruns the element, indicating that the text does not fit into the object area, something like this: “This text does not fit...".
A method that draws the appearance:
//+------------------------------------------------------------------+ //| CLabel::Рисует внешний вид | //+------------------------------------------------------------------+ void CLabel::Draw(const bool chart_redraw) { this.DrawText(this.m_text_x,this.m_text_y,this.Text(),chart_redraw); }
Here, the method for drawing a label text is simply called.
Methods of Manipulations With Files:
//+------------------------------------------------------------------+ //| CLabel::Сохранение в файл | //+------------------------------------------------------------------+ bool CLabel::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CCanvasBase::Save(file_handle)) return false; //--- Сохраняем текст if(::FileWriteArray(file_handle,this.m_text)!=sizeof(this.m_text)) return false; //--- Сохраняем предыдущий текст if(::FileWriteArray(file_handle,this.m_text_prev)!=sizeof(this.m_text_prev)) return false; //--- Сохраняем координату X текста if(::FileWriteInteger(file_handle,this.m_text_x,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем координату Y текста if(::FileWriteInteger(file_handle,this.m_text_y,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CLabel::Загрузка из файла | //+------------------------------------------------------------------+ bool CLabel::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CCanvasBase::Load(file_handle)) return false; //--- Загружаем текст if(::FileReadArray(file_handle,this.m_text)!=sizeof(this.m_text)) return false; //--- Загружаем предыдущий текст if(::FileReadArray(file_handle,this.m_text_prev)!=sizeof(this.m_text_prev)) return false; //--- Загружаем координату X текста this.m_text_x=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем координату Y текста this.m_text_y=::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
Based on the considered class, create a simple button class.
The “Simple Button" Control Class
//+------------------------------------------------------------------+ //| Класс простой кнопки | //+------------------------------------------------------------------+ class CButton : public CLabel { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CLabel::Save(file_handle); } virtual bool Load(const int file_handle) { return CLabel::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON); } //--- Конструкторы/деструктор CButton(void); CButton(const string object_name, const string text, const int x, const int y, const int w, const int h); CButton(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CButton(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CButton (void) {} };
The simple button class differs from the text label class only by the method of drawing the appearance.
The class has four constructors that allow to create a button with specified parameters:
//+------------------------------------------------------------------+ //| CButton::Конструктор по умолчанию. Строит кнопку в главном окне | //| текущего графика в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButton::CButton(void) : CLabel("Button",::ChartID(),0,"Button",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+-------------------------------------------------------------------+ //| CButton::Конструктор параметрический. Строит кнопку в главном окне| //| текущего графика с указанными текстом, координами и размерами | //+-------------------------------------------------------------------+ CButton::CButton(const string object_name,const string text,const int x,const int y,const int w,const int h) : CLabel(object_name,::ChartID(),0,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+---------------------------------------------------------------------+ //| CButton::Конструктор параметрический. Строит кнопку в указанном окне| //| текущего графика с указанными текстом, координами и размерами | //+---------------------------------------------------------------------+ CButton::CButton(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CLabel(object_name,::ChartID(),wnd,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+---------------------------------------------------------------------+ //| CButton::Конструктор параметрический. Строит кнопку в указанном окне| //| указанного графика с указанными текстом, координами и размерами | //+---------------------------------------------------------------------+ CButton::CButton(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CLabel(object_name,chart_id,wnd,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); }
Set the "button not pressed" state and set full opacity for the background and foreground.
A method for comparing two objects:
//+------------------------------------------------------------------+ //| CButton::Сравнение двух объектов | //+------------------------------------------------------------------+ int CButton::Compare(const CObject *node,const int mode=0) const { const CButton *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_TEXT : return(this.Text() >obj.Text() ? 1 : this.Text() <obj.Text() ? -1 : 0); case ELEMENT_SORT_BY_COLOR : return(this.BackColor()>obj.BackColor() ? 1 : this.BackColor()<obj.BackColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaBG() >obj.AlphaBG() ? 1 : this.AlphaBG() <obj.AlphaBG() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
The method is identical to that of the text label class. Most likely, if the buttons do not have any other properties, this method can be removed from the class, and the parent class method will be used.
A Method That Draws the Appearance of the Button:
//+------------------------------------------------------------------+ //| CButton::Рисует внешний вид | //+------------------------------------------------------------------+ void CButton::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Выводим текст кнопки CLabel::Draw(false); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
First, fill the background with the set color, then draw a border and display the text of the button.
Based on this class, create a two-position button class.
The “Two-Position Button" Control Class
//+------------------------------------------------------------------+ //| Класс двухпозиционной кнопки | //+------------------------------------------------------------------+ class CButtonTriggered : public CButton { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Обработчик событий нажатий кнопок мышки (Press) virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON_TRIGGERED); } //--- Инициализация цветов объекта по умолчанию virtual void InitColors(void); //--- Конструкторы/деструктор CButtonTriggered(void); CButtonTriggered(const string object_name, const string text, const int x, const int y, const int w, const int h); CButtonTriggered(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CButtonTriggered(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CButtonTriggered (void) {} };
The object has four constructors that allow you to create a button with the specified parameters:
//+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(void) : CButton("Button",::ChartID(),0,"Button",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); } //+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(const string object_name,const string text,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,text,x,y,w,h) { this.InitColors(); } //+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,text,x,y,w,h) { this.InitColors(); } //+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,text,x,y,w,h) { this.InitColors(); }
The default color initialization method is called in each constructor:
//+------------------------------------------------------------------+ //| CButtonTriggered::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CButtonTriggered::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke); this.InitBackColorsAct(clrLightBlue); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrDarkGray); this.InitBorderColorsAct(clrGreen); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrLightGray); this.InitForeColorBlocked(clrSilver); }
These are the default colors that are set for the newly created button. After creating an object, all the colors can be customized at your discretion.
The comparison methodhas been added with a comparison by the state of the button:
//+------------------------------------------------------------------+ //| CButtonTriggered::Сравнение двух объектов | //+------------------------------------------------------------------+ int CButtonTriggered::Compare(const CObject *node,const int mode=0) const { const CButtonTriggered *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_TEXT : return(this.Text() >obj.Text() ? 1 : this.Text() <obj.Text() ? -1 : 0); case ELEMENT_SORT_BY_COLOR : return(this.BackColor()>obj.BackColor() ? 1 : this.BackColor()<obj.BackColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaBG() >obj.AlphaBG() ? 1 : this.AlphaBG() <obj.AlphaBG() ? -1 : 0); case ELEMENT_SORT_BY_STATE : return(this.State() >obj.State() ? 1 : this.State() <obj.State() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
A Method That Draws the Appearance of the Button:
//+------------------------------------------------------------------+ //| CButtonTriggered::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonTriggered::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Выводим текст кнопки CLabel::Draw(false); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
The method is identical to that of the parent class, and if there are no further improvements to the class, then the method can be deleted — the drawing method from the parent class will be used.
The two-position button has two states:
- Pressed,
- Released.
To track and switch its states, the mouse click handler OnPressEvent of the parent class has been redefined here:
//+------------------------------------------------------------------+ //| CButtonTriggered::Обработчик событий нажатий кнопок мышки (Press)| //+------------------------------------------------------------------+ void CButtonTriggered::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Устанавливаем состояние кнопки, обратное уже установленному ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF); this.SetState(state); //--- Вызываем обработчик родительского объекта с указанием идентификатора в lparam и состояния в dparam CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam); }
Based on the CButton class, create four arrow buttons: up, down, left and right. The objects will use the image drawing class to draw arrows.
“Up Arrow Button” Control Class
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой вверх | //+------------------------------------------------------------------+ class CButtonArrowUp : public CButton { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON_ARROW_UP);} //--- Конструкторы/деструктор CButtonArrowUp(void); CButtonArrowUp(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowUp(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowUp(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowUp (void) {} };
Four constructors allow you to create an object with the specified parameters:
//+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); }
Default colors are initialized in constructors and the coordinates as well as dimensions of the image area are set.
A Method That Draws the Appearance of the Button:
//+------------------------------------------------------------------+ //| CButtonArrowUp::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowUp::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Выводим текст кнопки CLabel::Draw(false); //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Задаём цвет стрелки для обычного и заблокированного состояний кнопки и рисуем стрелку вверх color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowUp(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
The method is similar to that of drawing a button, but additionally an up arrow is displayed using the ArrowUp method of the drawing object.
All other classes are identical to the one considered, but drawing methods use icons corresponding to the purpose of the button.
“Down Arrow Button” Control Class
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой вниз | //+------------------------------------------------------------------+ class CButtonArrowDown : public CButton { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); } //--- Конструкторы/деструктор CButtonArrowDown(void); CButtonArrowDown(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowDown(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowDown(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowDown (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowDown::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Выводим текст кнопки CLabel::Draw(false); //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Задаём цвет стрелки для обычного и заблокированного состояний кнопки и рисуем стрелку вниз color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowDown(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
“Left Arrow Button” Control Class
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой влево | //+------------------------------------------------------------------+ class CButtonArrowLeft : public CButton { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); } //--- Конструкторы/деструктор CButtonArrowLeft(void); CButtonArrowLeft(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowLeft(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowLeft(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowLeft (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowLeft::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Выводим текст кнопки CLabel::Draw(false); //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Задаём цвет стрелки для обычного и заблокированного состояний кнопки и рисуем стрелку влево color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowLeft(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
“Right Arrow Button” Control Class
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой вправо | //+------------------------------------------------------------------+ class CButtonArrowRight : public CButton { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); } //--- Конструкторы/деструктор CButtonArrowRight(void); CButtonArrowRight(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowRight(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowRight(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowRight (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowRight::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Выводим текст кнопки CLabel::Draw(false); //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Задаём цвет стрелки для обычного и заблокированного состояний кнопки и рисуем стрелку вправо color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowRight(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
The “Checkbox" Control Class
The "checkbox" control class is similar to the classes of arrow buttons. Here, the background will be completely transparent. I.e., only the text and the checkbox icon will be drawn. The checkbox has two states — checked and unchecked, which means it will be inherited from the two-position button class:
//+------------------------------------------------------------------+ //| Класс элемента управления Checkbox | //+------------------------------------------------------------------+ class CCheckBox : public CButtonTriggered { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_CHECKBOX); } //--- Инициализация цветов объекта по умолчанию virtual void InitColors(void); //--- Конструкторы/деструктор CCheckBox(void); CCheckBox(const string object_name, const string text, const int x, const int y, const int w, const int h); CCheckBox(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CCheckBox(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CCheckBox (void) {} };
All classes of controls have four constructors each:
//+------------------------------------------------------------------+ //| CCheckBox::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(void) : CButtonTriggered("CheckBox",::ChartID(),0,"CheckBox",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const string text,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,::ChartID(),0,text,x,y,w,h) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,::ChartID(),wnd,text,x,y,w,h) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,chart_id,wnd,text,x,y,w,h) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); }
Here, the default object colors are initialized, a fully transparent background and an opaque foreground are set. Then dimensions and coordinates of the image area are set.
The comparison method returns the result of calling the comparison method of the parent class:
//+------------------------------------------------------------------+ //| CCheckBox::Сравнение двух объектов | //+------------------------------------------------------------------+ int CCheckBox::Compare(const CObject *node,const int mode=0) const { return CButtonTriggered::Compare(node,mode); }
Default Object Color Initialization Method:
//+------------------------------------------------------------------+ //| CCheckBox::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CCheckBox::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrNULL); this.InitBackColorsAct(clrNULL); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.InitForeColorFocused(clrNavy); this.InitForeColorActFocused(clrNavy); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrNULL); this.InitBorderColorsAct(clrNULL); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrNULL); this.InitForeColorBlocked(clrSilver); }
The Method of Drawing the Checkbox Appearance:
//+------------------------------------------------------------------+ //| CCheckBox::Рисует внешний вид | //+------------------------------------------------------------------+ void CCheckBox::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Выводим текст кнопки CLabel::Draw(false); //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Рисуем отмеченный значок для активного состояния кнопки, if(this.m_state) this.m_painter.CheckedBox(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- и неотмеченный - для неактивного else this.m_painter.UncheckedBox(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Depending on the element state, either a square checked with a check mark or just an empty square is drawn.
Now, based on this object, create a “Radio button" control class.
The “Radio Button" Control Class
Since the radio button always works in a group — it can only be turned off when another group button is turned on, here we also should redefine the handler for clicking on an object of the parent class.
//+------------------------------------------------------------------+ //| Класс элоемента управления Radio Button | //+------------------------------------------------------------------+ class CRadioButton : public CCheckBox { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Обработчик событий нажатий кнопок мышки (Press) virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_RADIOBUTTON); } //--- Конструкторы/деструктор CRadioButton(void); CRadioButton(const string object_name, const string text, const int x, const int y, const int w, const int h); CRadioButton(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CRadioButton(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CRadioButton (void) {} };
Constructors:
//+------------------------------------------------------------------+ //| CRadioButton::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(void) : CCheckBox("RadioButton",::ChartID(),0,"",0,0,DEF_BUTTON_H,DEF_BUTTON_H) { } //+------------------------------------------------------------------+ //| CRadioButton::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const string text,const int x,const int y,const int w,const int h) : CCheckBox(object_name,::ChartID(),0,text,x,y,w,h) { } //+------------------------------------------------------------------+ //| CRadioButton::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CCheckBox(object_name,::ChartID(),wnd,text,x,y,w,h) { } //+------------------------------------------------------------------+ //| CRadioButton::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CCheckBox(object_name,chart_id,wnd,text,x,y,w,h) { }
There is no need for any additional actions after calling the constructor of the parent class. Therefore, constructors have an empty body.
The comparison method returns the result of calling the comparison method of the parent class:
//+------------------------------------------------------------------+ //| CRadioButton::Сравнение двух объектов | //+------------------------------------------------------------------+ int CRadioButton::Compare(const CObject *node,const int mode=0) const { return CCheckBox::Compare(node,mode); }
The Method of Drawing the Button Appearance:
//+------------------------------------------------------------------+ //| CRadioButton::Рисует внешний вид | //+------------------------------------------------------------------+ void CRadioButton::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Выводим текст кнопки CLabel::Draw(false); //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Рисуем отмеченный значок для активного состояния кнопки, if(this.m_state) this.m_painter.CheckedRadioButton(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- и неотмеченный - для неактивного else this.m_painter.UncheckedRadioButton(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
This method is identical to the method of the parent class, but the radio button icons are drawn here — selected and not selected.
Mouse Button Click Event Handler:
//+------------------------------------------------------------------+ //| CRadioButton::Обработчик событий нажатий кнопок мышки (Press) | //+------------------------------------------------------------------+ void CRadioButton::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Если кнопка уже отмечена - уходим if(this.m_state) return; //--- Устанавливаем состояние кнопки, обратное уже установленному ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF); this.SetState(state); //--- Вызываем обработчик родительского объекта с указанием идентификатора в lparam и состояния в dparam CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam); }
Here, if the button already has an enabled state, then no actions should be performed — leave the handler. If the button is disabled, invert its state and call the handler of the parent class (the base object of all CCanvasBase controls).
For today, these are all the controls that were minimally necessary to implement complex controls.
Let's test what we get here.
Testing the Result
In \MQL5\Indicators\Tables\ folder reate a new indicator named iTestLabel.mq5.
Set the number of calculated buffers and graphical series of the indicator to zero — no charts need to be drawn. Connect the created library of graphical elements. In its own separate window the indicator will draw graphical elements, which, when created, will be saved to a list, the class file of which is connected to the indicator file:
//+------------------------------------------------------------------+ //| iTestLabel.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 0 #property indicator_plots 0 //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include <Arrays\ArrayObj.mqh> #include "Controls\Controls.mqh" CArrayObj list; // Список для хранения тестируемых объектов CCanvasBase *base =NULL; // Указатель на базовый графический элемент CLabel *label1=NULL; // Указатель на графический элемент Label CLabel *label2=NULL; // Указатель на графический элемент Label CLabel *label3=NULL; // Указатель на графический элемент Label CButton *button1=NULL; // Указатель на графический элемент Button CButtonTriggered *button_t1=NULL; // Указатель на графический элемент ButtonTriggered CButtonTriggered *button_t2=NULL; // Указатель на графический элемент ButtonTriggered CButtonArrowUp *button_up=NULL; // Указатель на графический элемент CButtonArrowUp CButtonArrowDown *button_dn=NULL; // Указатель на графический элемент CButtonArrowDown CButtonArrowLeft *button_lt=NULL; // Указатель на графический элемент CButtonArrowLeft CButtonArrowRight*button_rt=NULL; // Указатель на графический элемент CButtonArrowRight CCheckBox *checkbox_lt=NULL; // Указатель на графический элемент CCheckBox CCheckBox *checkbox_rt=NULL; // Указатель на графический элемент CCheckBox CRadioButton *radio_bt_lt=NULL; // Указатель на графический элемент CRadioButton CRadioButton *radio_bt_rt=NULL; // Указатель на графический элемент CRadioButton //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+
Here, for simplification, pointers to the created graphical elements are immediately created. After creating the element, we will use these pointers to work with objects.
Create all the objects indicator’s handler OnInit(). Let's do this: create one base object and color it so that it resembles a panel.
Inside this "substrate" implement all the graphical elements and specify this base object for them as a container.
In OnInit() implement such a code:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Ищем подокно графика int wnd=ChartWindowFind(); //--- Создаём базовый графический элемент list.Add(base=new CCanvasBase("Rectangle",0,wnd,100,40,260,160)); base.SetAlphaBG(250); // Прозрачность base.SetBorderWidth(6); // Ширина рамки //--- Инициализируем цвет фона, указываем цвет для заблокированного элемента //--- и делаем текущим цветом фона элемента цвет фона, заданный по умолчанию base.InitBackColors(clrWhiteSmoke); base.InitBackColorBlocked(clrLightGray); base.BackColorToDefault(); //--- Заливаем цветом фон и рисуем рамку с отступом в один пиксель от установленной ширины рамки base.Fill(base.BackColor(),false); uint wd=base.BorderWidth(); base.GetBackground().Rectangle(0,0,base.Width()-1,base.Height()-1,ColorToARGB(clrDimGray)); base.GetBackground().Rectangle(wd-2,wd-2,base.Width()-wd+1,base.Height()-wd+1,ColorToARGB(clrLightGray)); base.Update(false); //--- Устанавливаем наименование и идентификатор элемента и выводим в журнал его описание base.SetName("Rectangle 1"); base.SetID(1); base.Print(); //--- Внутри базового объекта создаём текстовую метку //--- и указываем для метки в качестве контейнера базовый элемент string text="Simple button:"; int shift_x=20; int shift_y=8; int x=base.X()+shift_x-10; int y=base.Y()+shift_y+2; int w=base.GetForeground().TextWidth(text); int h=DEF_LABEL_H; list.Add(label1=new CLabel("Label 1",0,wnd,text,x,y,w,h)); label1.SetContainerObj(base); //--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный //--- (это изменение стандартных параметров текстовой метки после её создания). label1.InitForeColorFocused(clrRed); label1.InitForeColorPressed(clrRed); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. label1.SetID(2); label1.Draw(false); label1.Print(); //--- Внутри базового объекта создаём простую кнопку //--- и указываем для кнопки в качестве контейнера базовый элемент x=label1.Right()+shift_x; y=label1.Y(); w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button1=new CButton("Simple Button",0,wnd,"Button 1",x,y,w,h)); button1.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X button1.SetTextShiftH(2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button1.SetID(3); button1.Draw(false); button1.Print(); //--- Внутри базового объекта создаём текстовую метку //--- и указываем для метки в качестве контейнера базовый элемент text="Triggered button:"; x=label1.X(); y=label1.Bottom()+shift_y; w=base.GetForeground().TextWidth(text); h=DEF_LABEL_H; list.Add(label2=new CLabel("Label 2",0,wnd,text,x,y,w,h)); label2.SetContainerObj(base); //--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный //--- (это изменение стандартных параметров текстовой метки после её создания). label2.InitForeColorFocused(clrRed); label2.InitForeColorPressed(clrRed); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. label2.SetID(4); label2.Draw(false); label2.Print(); //--- Внутри базового объекта создаём двухпозиционную кнопку //--- и указываем для кнопки в качестве контейнера базовый элемент x=button1.X(); y=button1.Bottom()+shift_y; w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button_t1=new CButtonTriggered("Triggered Button 1",0,wnd,"Button 2",x,y,w,h)); button_t1.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X button_t1.SetTextShiftH(2); //--- Устанавливаем идентификатор и активированное состояние элемента, //--- рисуем элемент и выводим в журнал его описание. button_t1.SetID(5); button_t1.SetState(true); button_t1.Draw(false); button_t1.Print(); //--- Внутри базового объекта создаём двухпозиционную кнопку //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_t1.Right()+4; y=button_t1.Y(); w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button_t2=new CButtonTriggered("Triggered Button 2",0,wnd,"Button 3",x,y,w,h)); button_t2.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X button_t2.SetTextShiftH(2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_t2.SetID(6); button_t2.Draw(false); button_t2.Print(); //--- Внутри базового объекта создаём текстовую метку //--- и указываем для метки в качестве контейнера базовый элемент text="Arrowed buttons:"; x=label1.X(); y=label2.Bottom()+shift_y; w=base.GetForeground().TextWidth(text); h=DEF_LABEL_H; list.Add(label3=new CLabel("Label 3",0,wnd,text,x,y,w,h)); label3.SetContainerObj(base); //--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный //--- (это изменение стандартных параметров текстовой метки после её создания). label3.InitForeColorFocused(clrRed); label3.InitForeColorPressed(clrRed); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. label3.SetID(7); label3.Draw(false); label3.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой вверх //--- и указываем для кнопки в качестве контейнера базовый элемент x=button1.X(); y=button_t1.Bottom()+shift_y; w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_up=new CButtonArrowUp("Arrow Up Button",0,wnd,x,y,w,h)); button_up.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_up.SetImageBound(1,1,w-4,h-3); //--- Здесь можно настроить внешний вид кнопки, например, убрать рамку //button_up.InitBorderColors(button_up.BackColor(),button_up.BackColorFocused(),button_up.BackColorPressed(),button_up.BackColorBlocked()); //button_up.ColorsToDefault(); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_up.SetID(8); button_up.Draw(false); button_up.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой вниз //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_up.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_dn=new CButtonArrowDown("Arrow Down Button",0,wnd,x,y,w,h)); button_dn.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_dn.SetImageBound(1,1,w-4,h-3); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_dn.SetID(9); button_dn.Draw(false); button_dn.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой влево //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_dn.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_lt=new CButtonArrowLeft("Arrow Left Button",0,wnd,x,y,w,h)); button_lt.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_lt.SetImageBound(1,1,w-3,h-4); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_lt.SetID(10); button_lt.Draw(false); button_lt.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой вправо //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_lt.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_rt=new CButtonArrowRight("Arrow Right Button",0,wnd,x,y,w,h)); button_rt.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_rt.SetImageBound(1,1,w-3,h-4); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_rt.SetID(11); button_rt.Draw(false); button_rt.Print(); //--- Внутри базового объекта создаём чекбокс с заголовком справа (левый чекбокс) //--- и указываем для кнопки в качестве контейнера базовый элемент x=label1.X(); y=label3.Bottom()+shift_y; w=DEF_BUTTON_W+30; h=DEF_BUTTON_H; list.Add(checkbox_lt=new CCheckBox("CheckBoxL",0,wnd,"CheckBox L",x,y,w,h)); checkbox_lt.SetContainerObj(base); //--- Задаём координаты и размеры области изображения checkbox_lt.SetImageBound(2,1,h-2,h-2); //--- Задаём смещение текста кнопки по оси X checkbox_lt.SetTextShiftH(checkbox_lt.ImageRight()+2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. checkbox_lt.SetID(12); checkbox_lt.Draw(false); checkbox_lt.Print(); //--- Внутри базового объекта создаём чекбокс с заголовком слева (правый чекбокс) //--- и указываем для кнопки в качестве контейнера базовый элемент x=checkbox_lt.Right()+4; y=checkbox_lt.Y(); w=DEF_BUTTON_W+30; h=DEF_BUTTON_H; list.Add(checkbox_rt=new CCheckBox("CheckBoxR",0,wnd,"CheckBox R",x,y,w,h)); checkbox_rt.SetContainerObj(base); //--- Задаём координаты и размеры области изображения checkbox_rt.SetTextShiftH(2); //--- Задаём смещение текста кнопки по оси X checkbox_rt.SetImageBound(checkbox_rt.Width()-h+2,1,h-2,h-2); //--- Устанавливаем идентификатор и активированное состояние элемента, //--- рисуем элемент и выводим в журнал его описание. checkbox_rt.SetID(13); checkbox_rt.SetState(true); checkbox_rt.Draw(false); checkbox_rt.Print(); //--- Внутри базового объекта создаём радиокнопку с заголовком справа (левый RadioButton) //--- и указываем для кнопки в качестве контейнера базовый элемент x=checkbox_lt.X(); y=checkbox_lt.Bottom()+shift_y; w=DEF_BUTTON_W+46; h=DEF_BUTTON_H; list.Add(radio_bt_lt=new CRadioButton("RadioButtonL",0,wnd,"RadioButton L",x,y,w,h)); radio_bt_lt.SetContainerObj(base); //--- Задаём координаты и размеры области изображения radio_bt_lt.SetImageBound(2,1,h-2,h-2); //--- Задаём смещение текста кнопки по оси X radio_bt_lt.SetTextShiftH(radio_bt_lt.ImageRight()+2); //--- Устанавливаем идентификатор и активированное состояние элемента, //--- рисуем элемент и выводим в журнал его описание. radio_bt_lt.SetID(14); radio_bt_lt.SetState(true); radio_bt_lt.Draw(false); radio_bt_lt.Print(); //--- Внутри базового объекта создаём радиокнопку с заголовком слева (правый RadioButton) //--- и указываем для кнопки в качестве контейнера базовый элемент x=radio_bt_lt.Right()+4; y=radio_bt_lt.Y(); w=DEF_BUTTON_W+46; h=DEF_BUTTON_H; list.Add(radio_bt_rt=new CRadioButton("RadioButtonR",0,wnd,"RadioButton R",x,y,w,h)); radio_bt_rt.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X radio_bt_rt.SetTextShiftH(2); //--- Задаём координаты и размеры области изображения radio_bt_rt.SetImageBound(radio_bt_rt.Width()-h+2,1,h-2,h-2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. radio_bt_rt.SetID(15); radio_bt_rt.Draw(true); radio_bt_rt.Print(); //--- Успешная инициализация return(INIT_SUCCEEDED); }
Carefully study all the comments to the code. Here all the steps of creating objects are described in sufficient detail.
In indicator’s handler OnDeinit() destroy all the objects in the list:
//+------------------------------------------------------------------+ //| Custom deindicator initialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { list.Clear(); }
The OnCalculate() handler is empty — we are not calculating or displaying anything on the chart:
//+------------------------------------------------------------------+ //| 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); }
To animate the created graphical elements, go through the list of created objects in the OnChartEvent() handler and call the similar handler for each element. Since radio buttons are not connected in groups in any way yet (this will be in the following articles), emulate toggling radio buttons, as it should be in a group of elements:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Вызываем обработчик событий каждого из созданных объектов for(int i=0;i<list.Total();i++) { CCanvasBase *obj=list.At(i); if(obj!=NULL) obj.OnChartEvent(id,lparam,dparam,sparam); } //--- Эмулируем работу радиокнопок в группе --- //--- Если получено пользовательское событие if(id>=CHARTEVENT_CUSTOM) { //--- Если нажата левая радиокнопка if(sparam==radio_bt_lt.NameBG()) { //--- Если состояние кнопки изменено (было не выбрано) if(radio_bt_lt.State()) { //--- делаем правую радиокнопку невыбранной и перерисовываем её radio_bt_rt.SetState(false); radio_bt_rt.Draw(true); } } //--- Если нажата правая радиокнопка if(sparam==radio_bt_rt.NameBG()) { //--- Если состояние кнопки изменено (было не выбрано) if(radio_bt_rt.State()) { //--- делаем левую радиокнопку невыбранной и перерисовываем её radio_bt_lt.SetState(false); radio_bt_lt.Draw(true); } } } }
Let's compile the indicator and run it on the chart:

All controls respond to mouse interaction, and the radio buttons toggle as if they are grouped. Text labels were made so that they change color when the cursor is hovered over to visually represent that you can customize controls at your discretion. In the normal state, label texts are static.
But there is one omission here — when you hover the mouse cursor over a control, an unnecessary tooltip with the indicator name appears. To get rid of this behavior, it is necessary for each graphic object to enter "\n" value in its OBJPROP_TOOLTIP property. Fix it.
In the CCanvasBase class, in the Create method, enter two lines with the installation of tooltips for background and foreground graphic objects:
//+------------------------------------------------------------------+ //| CCanvasBase::Создаёт графические объекты фона и переднего плана | //+------------------------------------------------------------------+ bool CCanvasBase::Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h) { //--- Получаем скорректированный идентификатор графика long id=this.CorrectChartID(chart_id); //--- Корректируем переданное имя для объекта string nm=object_name; ::StringReplace(nm," ","_"); //--- Создаём имя графического объекта для фона и создаём канвас string obj_name=nm+"_BG"; if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- Создаём имя графического объекта для переднего плана и создаём канвас obj_name=nm+"_FG"; if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- При успешном создании в свойство графического объекта OBJPROP_TEXT вписываем наименование программы ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameBG(),OBJPROP_TOOLTIP,"\n"); ::ObjectSetString(id,this.NameFG(),OBJPROP_TOOLTIP,"\n"); //--- Устанавливаем размеры прямоугольной области и возвращаем true this.m_bound.SetXY(x,y); this.m_bound.Resize(w,h); return true; }
Recompile the indicator and check:

Now, everything is correct.
Conclusion
Today we have taken another step towards creating the Table Control. All complex controls will be assembled from such simple but highly functional objects.
Today, we have added the Controller component to all objects. This allows the user to interact with the controls, and the elements themselves can interact with each other.
In the next article, we will prepare the panel and container elements, which are the main components for placing other elements in them. At the same time, the container enables to scroll child elements inside itself.
Programs used in the article:
| # | Name | Type | Description |
|---|---|---|---|
| 1 | Base.mqh | Class Library | Classes for creating a base object of controls |
| 2 | Controls.mqh | Class Library | Control classes |
| 3 | iTestLabel.mq5 | Test indicator | Indicator for testing manipulations with classes of controls |
| 4 | MQL5.zip | Archive | An archive of the files above for unpacking into the MQL5 directory of the client terminal |
Classes within the Base.mqh library:
| # | Name | Description |
|---|---|---|
| 1 | CBaseObj | A base class for all the graphical objects |
| 2 | CColor | Color management class |
| 3 | CColorElement | A class for managing the colors of various states of a graphical element |
| 4 | CBound | Rectangular area control class |
| 5 | CCanvasBase | A base class for manipulating graphical elements on canvas |
Classes within the Controls.mqh library:
| # | Name | Description |
|---|---|---|
| 1 | CImagePainter | A class for drawing images in an area defined by coordinates and dimensions |
| 2 | CLabel | The "Text Label" Control Class |
| 3 | CButton | The “Simple Button" Control Class |
| 4 | CButtonTriggered | The “Two-Position Button" Control Class |
| 5 | CButtonArrowUp | “Up Arrow Button” Control Class |
| 6 | CButtonArrowDown | “Down Arrow Button” Control Class |
| 7 | CButtonArrowLeft | “Left Arrow Button” Control Class |
| 8 | CButtonArrowRight | “Right Arrow Button” Control Class |
| 9 | CCheckBox | The “Checkbox" Control Class |
| 10 | CRadioButton | The “Radio Button" Control Class |
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/18221
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.
Implementing Practical Modules from Other Languages in MQL5 (Part 05): The Logging module from Python, Log Like a Pro
From Basic to Intermediate: Structs (II)
Currency pair strength indicator in pure MQL5
Capital management in trading and the trader's home accounting program with a database
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use